aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-03-01 16:04:11 -0800
committerFuwn <[email protected]>2026-03-01 16:04:11 -0800
commit48f0c30d47d62e4f35706edb93a1bb2f97eba14c (patch)
tree44866d7a61adfdf01a780e0108c370294d3db78b /src/lib
parentchore(biome): re-enable useAltText rule (diff)
downloaddue.moe-48f0c30d47d62e4f35706edb93a1bb2f97eba14c.tar.xz
due.moe-48f0c30d47d62e4f35706edb93a1bb2f97eba14c.zip
chore(biome): enable svelte formatting
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Announcement.svelte64
-rw-r--r--src/lib/CommandPalette/CommandPalette.svelte216
-rw-r--r--src/lib/Error/AnimeRateLimited.svelte4
-rw-r--r--src/lib/Error/DotDotDot.svelte26
-rw-r--r--src/lib/Error/LogInRestricted.svelte6
-rw-r--r--src/lib/Error/RateLimited.svelte14
-rw-r--r--src/lib/Events/AniListBadges/EasterEvent2025/ClickableAreaPage.svelte18
-rw-r--r--src/lib/Events/AniListBadges/EasterEvent2025/EasterEgg.svelte148
-rw-r--r--src/lib/Events/AniListBadges/EasterEvent2025/MultipleChoicePage.svelte26
-rw-r--r--src/lib/Events/AniListBadges/EasterEvent2025/RiddlePage.svelte24
-rw-r--r--src/lib/Events/Event.svelte8
-rw-r--r--src/lib/Events/Group.svelte6
-rw-r--r--src/lib/Hololive/Lives.svelte102
-rw-r--r--src/lib/Hololive/Stream.svelte22
-rw-r--r--src/lib/Home/HeadTitle.svelte4
-rw-r--r--src/lib/Home/LastActivity.svelte94
-rw-r--r--src/lib/Home/Root.svelte10
-rw-r--r--src/lib/Image/FallbackImage.svelte32
-rw-r--r--src/lib/Image/ParallaxImage.svelte60
-rw-r--r--src/lib/Landing.svelte120
-rw-r--r--src/lib/LandingHero.svelte16
-rw-r--r--src/lib/Layout/Dropdown.svelte34
-rw-r--r--src/lib/Layout/NumberTicker.svelte28
-rw-r--r--src/lib/Layout/Popup.svelte64
-rw-r--r--src/lib/Layout/Spacer.svelte2
-rw-r--r--src/lib/Layout/TextTransition.svelte32
-rw-r--r--src/lib/Layout/Username.svelte2
-rw-r--r--src/lib/Lazy.svelte79
-rw-r--r--src/lib/List/Anime/AnimeListTemplate.svelte92
-rw-r--r--src/lib/List/Anime/CleanAnimeList.svelte301
-rw-r--r--src/lib/List/Anime/CompletedAnimeList.svelte190
-rw-r--r--src/lib/List/Anime/DueAnimeList.svelte223
-rw-r--r--src/lib/List/Anime/DueIndexColumn.svelte24
-rw-r--r--src/lib/List/Anime/PlaceholderList.svelte12
-rw-r--r--src/lib/List/Anime/UpcomingAnimeList.svelte140
-rw-r--r--src/lib/List/CleanGrid.svelte34
-rw-r--r--src/lib/List/CleanList.svelte24
-rw-r--r--src/lib/List/ListTitle.svelte22
-rw-r--r--src/lib/List/Manga/CleanMangaList.svelte154
-rw-r--r--src/lib/List/Manga/MangaListTemplate.svelte449
-rw-r--r--src/lib/List/MediaRoulette.svelte142
-rw-r--r--src/lib/List/MediaTitleDisplay.svelte22
-rw-r--r--src/lib/Loading/Ellipsis.svelte2
-rw-r--r--src/lib/Loading/Grid.svelte2
-rw-r--r--src/lib/Loading/Message.svelte20
-rw-r--r--src/lib/Loading/Ripple.svelte2
-rw-r--r--src/lib/Loading/Skeleton.svelte18
-rw-r--r--src/lib/MarkdownLink.svelte26
-rw-r--r--src/lib/Media/Anime/Airing/AiringTime.svelte208
-rw-r--r--src/lib/Media/Cover/HoverCover.svelte8
-rw-r--r--src/lib/Notification/Notification.svelte26
-rw-r--r--src/lib/Notification/NotificationsProvider.svelte12
-rw-r--r--src/lib/Reader/Chapters/MangaDex.svelte24
-rw-r--r--src/lib/Reader/Chapters/Rawkuma.svelte12
-rw-r--r--src/lib/Schedule/CoverBypass.svelte28
-rw-r--r--src/lib/Schedule/Crunchyroll.svelte68
-rw-r--r--src/lib/Schedule/Days.svelte170
-rw-r--r--src/lib/Settings/Categories/Attributions.svelte4
-rw-r--r--src/lib/Settings/Categories/Cache.svelte4
-rw-r--r--src/lib/Settings/Categories/Calculation.svelte10
-rw-r--r--src/lib/Settings/Categories/Debug.svelte38
-rw-r--r--src/lib/Settings/Categories/Display.svelte128
-rw-r--r--src/lib/Settings/Categories/RSSFeeds.svelte14
-rw-r--r--src/lib/Settings/Categories/SettingSync.svelte18
-rw-r--r--src/lib/Settings/Category.svelte10
-rw-r--r--src/lib/Settings/SettingCheckboxToggle.svelte106
-rw-r--r--src/lib/Settings/SettingHint.svelte2
-rw-r--r--src/lib/Settings/SettingToggle.svelte14
-rw-r--r--src/lib/Settings/Verbiage.svelte2
-rw-r--r--src/lib/Tools/ActivityHistory/Grid.svelte50
-rw-r--r--src/lib/Tools/ActivityHistory/Tool.svelte130
-rw-r--r--src/lib/Tools/Birthdays.svelte184
-rw-r--r--src/lib/Tools/BirthdaysTemplate.svelte52
-rw-r--r--src/lib/Tools/DumpProfile.svelte44
-rw-r--r--src/lib/Tools/EpisodeDiscussionCollector.svelte18
-rw-r--r--src/lib/Tools/FollowFix.svelte12
-rw-r--r--src/lib/Tools/Hayai.svelte118
-rw-r--r--src/lib/Tools/InputTemplate.svelte38
-rw-r--r--src/lib/Tools/Likes.svelte32
-rw-r--r--src/lib/Tools/Picker.svelte10
-rw-r--r--src/lib/Tools/RandomFollower.svelte16
-rw-r--r--src/lib/Tools/SequelCatcher/List.svelte42
-rw-r--r--src/lib/Tools/SequelCatcher/Tool.svelte53
-rw-r--r--src/lib/Tools/SequelSpy/Prequels.svelte18
-rw-r--r--src/lib/Tools/SequelSpy/Tool.svelte64
-rw-r--r--src/lib/Tools/Tracker/Tool.svelte96
-rw-r--r--src/lib/Tools/UmaMusumeBirthdays.svelte80
-rw-r--r--src/lib/Tools/Wrapped/ActivityHistory.svelte14
-rw-r--r--src/lib/Tools/Wrapped/DataLoader.svelte6
-rw-r--r--src/lib/Tools/Wrapped/Media.svelte24
-rw-r--r--src/lib/Tools/Wrapped/MediaExtras.svelte12
-rw-r--r--src/lib/Tools/Wrapped/Tool.svelte1396
-rw-r--r--src/lib/Tools/Wrapped/Top/Activity.svelte20
-rw-r--r--src/lib/Tools/Wrapped/Top/Anime.svelte8
-rw-r--r--src/lib/Tools/Wrapped/Top/Manga.svelte8
-rw-r--r--src/lib/Tooltip/LinkedTooltip.svelte348
-rw-r--r--src/lib/User/BadgeWall/AWC.svelte92
-rw-r--r--src/lib/User/BadgeWall/BadgePreview.svelte178
-rw-r--r--src/lib/User/BadgeWall/Badges.svelte32
-rw-r--r--src/lib/User/BadgeWall/FallbackBadge.svelte112
-rw-r--r--src/lib/Utility/Loading.svelte6
101 files changed, 3658 insertions, 3721 deletions
diff --git a/src/lib/Announcement.svelte b/src/lib/Announcement.svelte
index 4fbe9de0..1e6f428f 100644
--- a/src/lib/Announcement.svelte
+++ b/src/lib/Announcement.svelte
@@ -1,38 +1,38 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import Popup from './Layout/Popup.svelte';
- import announcementHash from '$stores/announcementHash';
- import { env } from '$env/dynamic/public';
- import identity from '$stores/identity';
-
- const announcement = env.PUBLIC_ANNOUNCEMENT;
- const dismissButton = env.PUBLIC_ANNOUNCEMENT_DISMISS;
- const loggedIn = $identity !== undefined && $identity.id !== -2;
-
- const hash = (s: string) =>
- s
- .split('')
- .reduce((previous, current) => ((previous << 5) - previous + current.charCodeAt(0)) | 0, 0);
-
- const dismiss = () => {
- if (announcement) announcementHash.set(hash(announcement));
- };
-
- const maxWidth = (input: string, max = 100) => {
- let output = '';
- let line = '';
-
- for (const word of input.split(' ')) {
- if (line.length + word.length > max) {
- output += line + '\n';
- line = '';
- }
-
- line += word + ' ';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import Popup from './Layout/Popup.svelte';
+import announcementHash from '$stores/announcementHash';
+import { env } from '$env/dynamic/public';
+import identity from '$stores/identity';
+
+const announcement = env.PUBLIC_ANNOUNCEMENT;
+const dismissButton = env.PUBLIC_ANNOUNCEMENT_DISMISS;
+const loggedIn = $identity !== undefined && $identity.id !== -2;
+
+const hash = (s: string) =>
+ s
+ .split('')
+ .reduce((previous, current) => ((previous << 5) - previous + current.charCodeAt(0)) | 0, 0);
+
+const dismiss = () => {
+ if (announcement) announcementHash.set(hash(announcement));
+};
+
+const maxWidth = (input: string, max = 100) => {
+ let output = '';
+ let line = '';
+
+ for (const word of input.split(' ')) {
+ if (line.length + word.length > max) {
+ output += line + '\n';
+ line = '';
}
- return output + line;
- };
+ line += word + ' ';
+ }
+
+ return output + line;
+};
</script>
{#if loggedIn && announcement && $announcementHash !== hash(announcement) && $announcementHash !== 0}
diff --git a/src/lib/CommandPalette/CommandPalette.svelte b/src/lib/CommandPalette/CommandPalette.svelte
index 735941fb..a42fab1a 100644
--- a/src/lib/CommandPalette/CommandPalette.svelte
+++ b/src/lib/CommandPalette/CommandPalette.svelte
@@ -1,137 +1,137 @@
<script lang="ts">
- import { onMount } from 'svelte';
- import { fly, fade } from 'svelte/transition';
- import { flip } from 'svelte/animate';
- import type { CommandPaletteAction } from './actions';
-
- export let items: CommandPaletteAction[] = [];
- export let open = false;
-
- let search = '';
- let filtered: (CommandPaletteAction & { id?: string })[] = [];
- let selectedIndex = -1;
- let inputRef: HTMLInputElement;
- let isVisible = false;
- let timeoutID: ReturnType<typeof setTimeout> | null = null;
- let itemIDs = new Map<string, number>();
-
- $: {
- items.forEach((item, index) => {
- if (!itemIDs.has(item.url)) itemIDs.set(item.url, index);
- });
-
- const doesActionMatch = (action: CommandPaletteAction) => {
- const doesActionIncludePattern = (query: string, action: string) => {
- const normalise = (input: string) => input.toLowerCase().replace(/\s+/g, '');
-
- return normalise(query).includes(normalise(action));
- };
-
- return (
- doesActionIncludePattern(action.name, search) ||
- action.tags?.some((tag) => doesActionIncludePattern(tag, search))
- );
+import { onMount } from 'svelte';
+import { fly, fade } from 'svelte/transition';
+import { flip } from 'svelte/animate';
+import type { CommandPaletteAction } from './actions';
+
+export let items: CommandPaletteAction[] = [];
+export let open = false;
+
+let search = '';
+let filtered: (CommandPaletteAction & { id?: string })[] = [];
+let selectedIndex = -1;
+let inputRef: HTMLInputElement;
+let isVisible = false;
+let timeoutID: ReturnType<typeof setTimeout> | null = null;
+let itemIDs = new Map<string, number>();
+
+$: {
+ items.forEach((item, index) => {
+ if (!itemIDs.has(item.url)) itemIDs.set(item.url, index);
+ });
+
+ const doesActionMatch = (action: CommandPaletteAction) => {
+ const doesActionIncludePattern = (query: string, action: string) => {
+ const normalise = (input: string) => input.toLowerCase().replace(/\s+/g, '');
+
+ return normalise(query).includes(normalise(action));
};
- filtered = [];
+ return (
+ doesActionIncludePattern(action.name, search) ||
+ action.tags?.some((tag) => doesActionIncludePattern(tag, search))
+ );
+ };
- items.forEach((action, idx) => {
- const actionMatches = doesActionMatch(action);
- let matchedParent = false;
+ filtered = [];
- if (actionMatches) {
- filtered.push({ ...action, id: `action-${idx}` });
+ items.forEach((action, idx) => {
+ const actionMatches = doesActionMatch(action);
+ let matchedParent = false;
- matchedParent = true;
- }
+ if (actionMatches) {
+ filtered.push({ ...action, id: `action-${idx}` });
- if (action.actions)
- action.actions.forEach((nestedAction, nestedIdx) => {
- if (doesActionMatch(nestedAction))
- filtered.push({
- ...nestedAction,
- id: `action-${idx}-nested-${nestedIdx}`,
- name: `${matchedParent ? '↳' : `${action.name} >`} ${nestedAction.name}`
- });
- });
- });
+ matchedParent = true;
+ }
- filtered = filtered.slice(0, 10);
- }
+ if (action.actions)
+ action.actions.forEach((nestedAction, nestedIdx) => {
+ if (doesActionMatch(nestedAction))
+ filtered.push({
+ ...nestedAction,
+ id: `action-${idx}-nested-${nestedIdx}`,
+ name: `${matchedParent ? '↳' : `${action.name} >`} ${nestedAction.name}`
+ });
+ });
+ });
- $: if (selectedIndex >= filtered.length) selectedIndex = filtered.length - 1;
- $: if (selectedIndex < 0 && filtered.length > 0) selectedIndex = 0;
+ filtered = filtered.slice(0, 10);
+}
- $: if (open && !isVisible) {
- isVisible = true;
+$: if (selectedIndex >= filtered.length) selectedIndex = filtered.length - 1;
+$: if (selectedIndex < 0 && filtered.length > 0) selectedIndex = 0;
- if (timeoutID !== null) {
- clearTimeout(timeoutID);
+$: if (open && !isVisible) {
+ isVisible = true;
+ if (timeoutID !== null) {
+ clearTimeout(timeoutID);
+
+ timeoutID = null;
+ }
+} else if (!open && isVisible) {
+ if (timeoutID === null) {
+ timeoutID = setTimeout(() => {
+ isVisible = false;
timeoutID = null;
- }
- } else if (!open && isVisible) {
- if (timeoutID === null) {
- timeoutID = setTimeout(() => {
- isVisible = false;
- timeoutID = null;
- }, 200);
- }
+ }, 200);
}
+}
- const executeItem = (item: CommandPaletteAction) => {
- if (item.onClick) item.onClick();
- if (!item.preventDefault) window.location.href = item.url;
+const executeItem = (item: CommandPaletteAction) => {
+ if (item.onClick) item.onClick();
+ if (!item.preventDefault) window.location.href = item.url;
- open = false;
- };
+ open = false;
+};
- const handleKey = (e: KeyboardEvent) => {
- if (e.key === 'ArrowDown') {
- e.preventDefault();
-
- selectedIndex = (selectedIndex + 1) % filtered.length;
- } else if (e.key === 'ArrowUp') {
- e.preventDefault();
-
- selectedIndex = (selectedIndex - 1 + filtered.length) % filtered.length;
- } else if (e.key === 'Enter') {
- if (filtered.length === 1) {
- executeItem(filtered[0]);
- } else if (filtered.length > 1 && selectedIndex >= 0) {
- executeItem(filtered[selectedIndex]);
- }
- } else if (e.key === 'Escape') {
- open = false;
- }
- };
+const handleKey = (e: KeyboardEvent) => {
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
- onMount(() => {
- window.addEventListener('keydown', handleGlobalKey);
+ selectedIndex = (selectedIndex + 1) % filtered.length;
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
- return () => {
- window.removeEventListener('keydown', handleGlobalKey);
+ selectedIndex = (selectedIndex - 1 + filtered.length) % filtered.length;
+ } else if (e.key === 'Enter') {
+ if (filtered.length === 1) {
+ executeItem(filtered[0]);
+ } else if (filtered.length > 1 && selectedIndex >= 0) {
+ executeItem(filtered[selectedIndex]);
+ }
+ } else if (e.key === 'Escape') {
+ open = false;
+ }
+};
- if (timeoutID !== null) clearTimeout(timeoutID);
- };
- });
+onMount(() => {
+ window.addEventListener('keydown', handleGlobalKey);
- const handleClickOutside = (event: MouseEvent) => {
- const target = event.target as HTMLElement;
+ return () => {
+ window.removeEventListener('keydown', handleGlobalKey);
- if (target.classList.contains('command-palette-overlay') || !target.closest('.dropdown'))
- open = false;
+ if (timeoutID !== null) clearTimeout(timeoutID);
};
+});
- const handleGlobalKey = (e: KeyboardEvent) => {
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
- e.preventDefault();
+const handleClickOutside = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
- open = !open;
+ if (target.classList.contains('command-palette-overlay') || !target.closest('.dropdown'))
+ open = false;
+};
- if (open) requestAnimationFrame(() => inputRef?.focus());
- }
- };
+const handleGlobalKey = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+ e.preventDefault();
+
+ open = !open;
+
+ if (open) requestAnimationFrame(() => inputRef?.focus());
+ }
+};
</script>
<svelte:window onclick={handleClickOutside} />
diff --git a/src/lib/Error/AnimeRateLimited.svelte b/src/lib/Error/AnimeRateLimited.svelte
index 5813ce51..752b8aed 100644
--- a/src/lib/Error/AnimeRateLimited.svelte
+++ b/src/lib/Error/AnimeRateLimited.svelte
@@ -1,6 +1,6 @@
<script>
- import Spacer from '$lib/Layout/Spacer.svelte';
- import Popup from '$lib/Layout/Popup.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import Popup from '$lib/Layout/Popup.svelte';
</script>
<Popup locked fullscreen>
diff --git a/src/lib/Error/DotDotDot.svelte b/src/lib/Error/DotDotDot.svelte
index 88b46374..cb8f95f2 100644
--- a/src/lib/Error/DotDotDot.svelte
+++ b/src/lib/Error/DotDotDot.svelte
@@ -1,22 +1,22 @@
<script lang="ts">
- import { onDestroy, onMount } from 'svelte';
+import { onDestroy, onMount } from 'svelte';
- export let max: number | undefined = undefined;
- export let perMs = 1000;
- export let start = '';
+export let max: number | undefined = undefined;
+export let perMs = 1000;
+export let start = '';
- let dots = start;
- let interval: ReturnType<typeof setInterval>;
+let dots = start;
+let interval: ReturnType<typeof setInterval>;
- onMount(() => {
- interval = setInterval(() => {
- dots += '.';
+onMount(() => {
+ interval = setInterval(() => {
+ dots += '.';
- if (max && dots.length > max) dots = '';
- }, perMs);
- });
+ if (max && dots.length > max) dots = '';
+ }, perMs);
+});
- onDestroy(() => clearInterval(interval));
+onDestroy(() => clearInterval(interval));
</script>
{dots}
diff --git a/src/lib/Error/LogInRestricted.svelte b/src/lib/Error/LogInRestricted.svelte
index ab48f180..17c9d62b 100644
--- a/src/lib/Error/LogInRestricted.svelte
+++ b/src/lib/Error/LogInRestricted.svelte
@@ -1,7 +1,7 @@
<script>
- import Popup from '$lib/Layout/Popup.svelte';
- import { env } from '$env/dynamic/public';
- import localforage from 'localforage';
+import Popup from '$lib/Layout/Popup.svelte';
+import { env } from '$env/dynamic/public';
+import localforage from 'localforage';
</script>
<Popup fullscreen locked>
diff --git a/src/lib/Error/RateLimited.svelte b/src/lib/Error/RateLimited.svelte
index 5a729c87..9c62759d 100644
--- a/src/lib/Error/RateLimited.svelte
+++ b/src/lib/Error/RateLimited.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- export let type = 'Media';
- export let loginSessionError = true;
- export let contact = true;
- export let list = true;
- export let card = false;
- export let might = true;
+import Spacer from '$lib/Layout/Spacer.svelte';
+export let type = 'Media';
+export let loginSessionError = true;
+export let contact = true;
+export let list = true;
+export let card = false;
+export let might = true;
</script>
<div class:card>
diff --git a/src/lib/Events/AniListBadges/EasterEvent2025/ClickableAreaPage.svelte b/src/lib/Events/AniListBadges/EasterEvent2025/ClickableAreaPage.svelte
index 494eb6be..976ebdfa 100644
--- a/src/lib/Events/AniListBadges/EasterEvent2025/ClickableAreaPage.svelte
+++ b/src/lib/Events/AniListBadges/EasterEvent2025/ClickableAreaPage.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
- export let prompt: string;
- export let images: string[] = [];
- export let correctIndex: number;
- export let onComplete: () => void;
+export let prompt: string;
+export let images: string[] = [];
+export let correctIndex: number;
+export let onComplete: () => void;
- let selectedIndex = -1;
+let selectedIndex = -1;
- const handleClick = (index: number) => {
- selectedIndex = index;
+const handleClick = (index: number) => {
+ selectedIndex = index;
- if (index === correctIndex) setTimeout(onComplete, 500);
- };
+ if (index === correctIndex) setTimeout(onComplete, 500);
+};
</script>
<div class="container">
diff --git a/src/lib/Events/AniListBadges/EasterEvent2025/EasterEgg.svelte b/src/lib/Events/AniListBadges/EasterEvent2025/EasterEgg.svelte
index 0d7ab010..1c7d4545 100644
--- a/src/lib/Events/AniListBadges/EasterEvent2025/EasterEgg.svelte
+++ b/src/lib/Events/AniListBadges/EasterEvent2025/EasterEgg.svelte
@@ -1,100 +1,100 @@
<script lang="ts">
- import { onMount, tick } from 'svelte';
- import { browser } from '$app/environment';
- import Popup from '$lib/Layout/Popup.svelte';
+import { onMount, tick } from 'svelte';
+import { browser } from '$app/environment';
+import Popup from '$lib/Layout/Popup.svelte';
- export let targetID = 'easter-target';
- export let id: number;
+export let targetID = 'easter-target';
+export let id: number;
- let visible = false;
- let showPopup = false;
+let visible = false;
+let showPopup = false;
- $: eggCount = browser
- ? JSON.parse(localStorage.getItem('easter2025ClickedEggs') || '[]').length
- : 0;
+$: eggCount = browser
+ ? JSON.parse(localStorage.getItem('easter2025ClickedEggs') || '[]').length
+ : 0;
- onMount(() => {
- let intervalId: number | undefined;
+onMount(() => {
+ let intervalId: number | undefined;
- const updatePosition = async () => {
- await tick();
+ const updatePosition = async () => {
+ await tick();
- const targetElement = document.getElementById(targetID);
+ const targetElement = document.getElementById(targetID);
- if (!targetElement) return;
+ if (!targetElement) return;
- const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
- const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
- const eggVisual = document.getElementById(`egg-visual-${targetID}-${id}`);
- const eggClick = document.getElementById(`egg-click-${targetID}-${id}`);
- const pageWidth = document.documentElement.clientWidth;
-
- visible = !clickedEggs.includes(id) && clickedEggs.length < 4;
-
- if (eggVisual && eggClick) {
- const verticalPosition = targetElement.offsetHeight * 0.9;
- const eggWidthPercent = (100 / pageWidth) * 100;
- const horizontalPosition = targetElement.offsetWidth - eggWidthPercent / 2;
-
- eggVisual.style.top = `${verticalPosition}px`;
- eggVisual.style.left = `${horizontalPosition}px`;
- eggVisual.style.zIndex = '-1';
- eggClick.style.top = `${verticalPosition}px`;
- eggClick.style.left = `${horizontalPosition}px`;
- eggClick.style.zIndex = '9999';
- }
- };
+ const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
+ const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
+ const eggVisual = document.getElementById(`egg-visual-${targetID}-${id}`);
+ const eggClick = document.getElementById(`egg-click-${targetID}-${id}`);
+ const pageWidth = document.documentElement.clientWidth;
+
+ visible = !clickedEggs.includes(id) && clickedEggs.length < 4;
+
+ if (eggVisual && eggClick) {
+ const verticalPosition = targetElement.offsetHeight * 0.9;
+ const eggWidthPercent = (100 / pageWidth) * 100;
+ const horizontalPosition = targetElement.offsetWidth - eggWidthPercent / 2;
+
+ eggVisual.style.top = `${verticalPosition}px`;
+ eggVisual.style.left = `${horizontalPosition}px`;
+ eggVisual.style.zIndex = '-1';
+ eggClick.style.top = `${verticalPosition}px`;
+ eggClick.style.left = `${horizontalPosition}px`;
+ eggClick.style.zIndex = '9999';
+ }
+ };
- intervalId = setInterval(updatePosition, 100) as unknown as number;
+ intervalId = setInterval(updatePosition, 100) as unknown as number;
- updatePosition();
+ updatePosition();
- return () => {
- if (intervalId) clearInterval(intervalId);
+ return () => {
+ if (intervalId) clearInterval(intervalId);
- window.removeEventListener('resize', updatePosition);
- window.removeEventListener('scroll', updatePosition);
- };
- });
+ window.removeEventListener('resize', updatePosition);
+ window.removeEventListener('scroll', updatePosition);
+ };
+});
- const handleClick = (event: MouseEvent) => {
- if (event.button === 0) {
- const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
- const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
+const handleClick = (event: MouseEvent) => {
+ if (event.button === 0) {
+ const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
+ const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
- if (!clickedEggs.includes(id)) {
- clickedEggs.push(id);
- localStorage.setItem('easter2025ClickedEggs', JSON.stringify(clickedEggs));
- }
+ if (!clickedEggs.includes(id)) {
+ clickedEggs.push(id);
+ localStorage.setItem('easter2025ClickedEggs', JSON.stringify(clickedEggs));
+ }
- visible = false;
+ visible = false;
- if (clickedEggs.length >= 3) showPopup = true;
+ if (clickedEggs.length >= 3) showPopup = true;
- // eslint-disable-next-line no-undef
- umami.track('Easter Egg Clicked', { id });
- } else if (event.button === 1) {
- visible = true;
+ // eslint-disable-next-line no-undef
+ umami.track('Easter Egg Clicked', { id });
+ } else if (event.button === 1) {
+ visible = true;
- localStorage.setItem('easter2025ClickedEggs', '[]');
- }
- };
+ localStorage.setItem('easter2025ClickedEggs', '[]');
+ }
+};
- const copyCode = (source: string) => {
- navigator.clipboard.writeText(
- `<img src="${source}" alt="due.moe × AniList Badges Badge Prize" width="200px">`
- );
- };
+const copyCode = (source: string) => {
+ navigator.clipboard.writeText(
+ `<img src="${source}" alt="due.moe × AniList Badges Badge Prize" width="200px">`
+ );
+};
- const onLeavePopup = () => {
- showPopup = false;
+const onLeavePopup = () => {
+ showPopup = false;
- const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
- const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
+ const storedClickedEggs = localStorage.getItem('easter2025ClickedEggs');
+ const clickedEggs = storedClickedEggs ? JSON.parse(storedClickedEggs) : [];
- clickedEggs.push(-1);
- localStorage.setItem('easter2025ClickedEggs', JSON.stringify(clickedEggs));
- };
+ clickedEggs.push(-1);
+ localStorage.setItem('easter2025ClickedEggs', JSON.stringify(clickedEggs));
+};
</script>
{#if visible}
diff --git a/src/lib/Events/AniListBadges/EasterEvent2025/MultipleChoicePage.svelte b/src/lib/Events/AniListBadges/EasterEvent2025/MultipleChoicePage.svelte
index 6ba477be..ebcc9678 100644
--- a/src/lib/Events/AniListBadges/EasterEvent2025/MultipleChoicePage.svelte
+++ b/src/lib/Events/AniListBadges/EasterEvent2025/MultipleChoicePage.svelte
@@ -1,20 +1,20 @@
<script lang="ts">
- export let prompt: string;
- export let answers: string[] = [];
- export let correctIndex: number;
- export let onComplete: () => void;
+export let prompt: string;
+export let answers: string[] = [];
+export let correctIndex: number;
+export let onComplete: () => void;
- let selected = -1;
+let selected = -1;
- const handleChoice = (index: number) => {
- if (index === correctIndex) {
- selected = index;
+const handleChoice = (index: number) => {
+ if (index === correctIndex) {
+ selected = index;
- setTimeout(onComplete, 500);
- } else {
- selected = index;
- }
- };
+ setTimeout(onComplete, 500);
+ } else {
+ selected = index;
+ }
+};
</script>
<div class="container">
diff --git a/src/lib/Events/AniListBadges/EasterEvent2025/RiddlePage.svelte b/src/lib/Events/AniListBadges/EasterEvent2025/RiddlePage.svelte
index 664d5e27..da01e5db 100644
--- a/src/lib/Events/AniListBadges/EasterEvent2025/RiddlePage.svelte
+++ b/src/lib/Events/AniListBadges/EasterEvent2025/RiddlePage.svelte
@@ -1,18 +1,18 @@
<script lang="ts">
- export let riddle: string;
- export let answer: string;
- export let onComplete: () => void;
- export let hint: string | undefined = undefined;
+export let riddle: string;
+export let answer: string;
+export let onComplete: () => void;
+export let hint: string | undefined = undefined;
- let userInput = '';
+let userInput = '';
- const checkAnswer = () => {
- if (userInput.toLowerCase() === answer.toLowerCase()) {
- setTimeout(onComplete, 500);
- } else {
- setTimeout(() => (userInput = ''), 500);
- }
- };
+const checkAnswer = () => {
+ if (userInput.toLowerCase() === answer.toLowerCase()) {
+ setTimeout(onComplete, 500);
+ } else {
+ setTimeout(() => (userInput = ''), 500);
+ }
+};
</script>
<div class="container">
diff --git a/src/lib/Events/Event.svelte b/src/lib/Events/Event.svelte
index e6e16586..e8a8d606 100644
--- a/src/lib/Events/Event.svelte
+++ b/src/lib/Events/Event.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import type { Event } from '$lib/Database/SB/events';
- import root from '$lib/Utility/root';
- import locale from '$stores/locale';
+import type { Event } from '$lib/Database/SB/events';
+import root from '$lib/Utility/root';
+import locale from '$stores/locale';
- let { event, avatar = false }: { event: Event; avatar?: boolean } = $props();
+let { event, avatar = false }: { event: Event; avatar?: boolean } = $props();
</script>
<div
diff --git a/src/lib/Events/Group.svelte b/src/lib/Events/Group.svelte
index 43eb4f26..a891060f 100644
--- a/src/lib/Events/Group.svelte
+++ b/src/lib/Events/Group.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
- import type { Group } from '$lib/Database/SB/groups';
- import tooltip from '$lib/Tooltip/tooltip';
+import type { Group } from '$lib/Database/SB/groups';
+import tooltip from '$lib/Tooltip/tooltip';
- let { group }: { group: Group } = $props();
+let { group }: { group: Group } = $props();
</script>
<div
diff --git a/src/lib/Hololive/Lives.svelte b/src/lib/Hololive/Lives.svelte
index 877dc316..9fb5585f 100644
--- a/src/lib/Hololive/Lives.svelte
+++ b/src/lib/Hololive/Lives.svelte
@@ -1,62 +1,62 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import root from '$lib/Utility/root';
- import type { Live, ParseResult } from './hololive';
- import Stream from './Stream.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import Message from '$lib/Loading/Message.svelte';
+import root from '$lib/Utility/root';
+import type { Live, ParseResult } from './hololive';
+import Stream from './Stream.svelte';
- export let schedule: ParseResult;
- export let pinnedStreams: string[];
- export let getPinnedStreams: () => void;
- export let filter: string | undefined;
+export let schedule: ParseResult;
+export let pinnedStreams: string[];
+export let getPinnedStreams: () => void;
+export let filter: string | undefined;
- const pinStream = (streamer: string) =>
- fetch(root(`/api/preferences/pin?stream=${encodeURIComponent(streamer)}`), {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- }
- }).then(getPinnedStreams);
+const pinStream = (streamer: string) =>
+ fetch(root(`/api/preferences/pin?stream=${encodeURIComponent(streamer)}`), {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }).then(getPinnedStreams);
- $: categorisedStreams = schedule.lives
- .filter((live) => (filter ? live.streamer === filter : true))
- .sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime())
- .sort((a, b) => {
- const aPinned = pinnedStreams.includes(a.streamer);
- const bPinned = pinnedStreams.includes(b.streamer);
+$: categorisedStreams = schedule.lives
+ .filter((live) => (filter ? live.streamer === filter : true))
+ .sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime())
+ .sort((a, b) => {
+ const aPinned = pinnedStreams.includes(a.streamer);
+ const bPinned = pinnedStreams.includes(b.streamer);
- if (aPinned && !bPinned) return -1;
- if (!aPinned && bPinned) return 1;
+ if (aPinned && !bPinned) return -1;
+ if (!aPinned && bPinned) return 1;
- return 0;
- })
- .reduce(
- (
- acc: {
- live: Live[];
- upcoming: Live[];
- ended: Live[];
- },
- live
- ) => {
- const now = Date.now();
- const time = new Date(live.time).getTime();
- const isLive = live.streaming;
- const isUpcoming = time > now && !isLive;
- const isEnded = time < now && !isLive;
+ return 0;
+ })
+ .reduce(
+ (
+ acc: {
+ live: Live[];
+ upcoming: Live[];
+ ended: Live[];
+ },
+ live
+ ) => {
+ const now = Date.now();
+ const time = new Date(live.time).getTime();
+ const isLive = live.streaming;
+ const isUpcoming = time > now && !isLive;
+ const isEnded = time < now && !isLive;
- if (isLive) {
- acc.live.push(live);
- } else if (isUpcoming) {
- acc.upcoming.push(live);
- } else if (isEnded) {
- acc.ended.push(live);
- }
+ if (isLive) {
+ acc.live.push(live);
+ } else if (isUpcoming) {
+ acc.upcoming.push(live);
+ } else if (isEnded) {
+ acc.ended.push(live);
+ }
- return acc;
- },
- { live: [], upcoming: [], ended: [] }
- );
+ return acc;
+ },
+ { live: [], upcoming: [], ended: [] }
+ );
</script>
{#if schedule.lives.length === 0}
diff --git a/src/lib/Hololive/Stream.svelte b/src/lib/Hololive/Stream.svelte
index afe400b7..5c833a50 100644
--- a/src/lib/Hololive/Stream.svelte
+++ b/src/lib/Hololive/Stream.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
- import root from '$lib/Utility/root';
- import identity from '$stores/identity';
- import locale from '$stores/locale';
- import Icon from '@iconify/svelte';
- import type { LiveInfo } from '$lib/Data/hololive';
-
- export let live: LiveInfo;
- export let pinStream: (streamer: string) => void;
- export let pinnedStreams: string[];
- export let icon: string;
+import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
+import root from '$lib/Utility/root';
+import identity from '$stores/identity';
+import locale from '$stores/locale';
+import Icon from '@iconify/svelte';
+import type { LiveInfo } from '$lib/Data/hololive';
+
+export let live: LiveInfo;
+export let pinStream: (streamer: string) => void;
+export let pinnedStreams: string[];
+export let icon: string;
</script>
<div class="stream card">
diff --git a/src/lib/Home/HeadTitle.svelte b/src/lib/Home/HeadTitle.svelte
index 1e86cab2..dbda7afe 100644
--- a/src/lib/Home/HeadTitle.svelte
+++ b/src/lib/Home/HeadTitle.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
- let { route = undefined, path = '/' }: { route?: string; path?: string } = $props();
- const title = $derived((route ? `${route} • ` : '') + 'due.moe');
+let { route = undefined, path = '/' }: { route?: string; path?: string } = $props();
+const title = $derived((route ? `${route} • ` : '') + 'due.moe');
</script>
<svelte:head>
diff --git a/src/lib/Home/LastActivity.svelte b/src/lib/Home/LastActivity.svelte
index 61cf6c7d..5c9ee4c2 100644
--- a/src/lib/Home/LastActivity.svelte
+++ b/src/lib/Home/LastActivity.svelte
@@ -1,53 +1,53 @@
<script lang="ts">
- import userIdentity from '$stores/identity';
- import { onMount } from 'svelte';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { lastActivityDate } from '../Data/AniList/activity';
- import settings from '$stores/settings';
-
- let { user }: { user: AniListAuthorisation } = $props();
- let lastActivityWasToday = $state(true);
-
- onMount(async () => {
- if (!$settings.displayDisableLastActivityWarning && user !== undefined) {
- lastActivityWasToday =
- (await lastActivityDate($userIdentity, user)).date.toDateString() >=
- new Date().toDateString();
-
- if (!lastActivityWasToday) {
- if ($settings.displayLimitListHeight) {
- document.querySelectorAll('.list').forEach((list) => {
- (list as HTMLElement).style.maxHeight = `calc((100vh - ${
- document.querySelector('#list-container')?.getBoundingClientRect().top
- }px) - 5rem)`;
- });
- }
+import userIdentity from '$stores/identity';
+import { onMount } from 'svelte';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { lastActivityDate } from '../Data/AniList/activity';
+import settings from '$stores/settings';
+
+let { user }: { user: AniListAuthorisation } = $props();
+let lastActivityWasToday = $state(true);
+
+onMount(async () => {
+ if (!$settings.displayDisableLastActivityWarning && user !== undefined) {
+ lastActivityWasToday =
+ (await lastActivityDate($userIdentity, user)).date.toDateString() >=
+ new Date().toDateString();
+
+ if (!lastActivityWasToday) {
+ if ($settings.displayLimitListHeight) {
+ document.querySelectorAll('.list').forEach((list) => {
+ (list as HTMLElement).style.maxHeight = `calc((100vh - ${
+ document.querySelector('#list-container')?.getBoundingClientRect().top
+ }px) - 5rem)`;
+ });
}
}
- });
-
- const timeLeftToday = () => {
- const now = new Date();
- const currentHour = now.getHours();
- const currentMinute = now.getMinutes();
- const hoursLeft = 24 - currentHour;
- let minutesLeft = 0;
- let timeLeft = '';
-
- if (hoursLeft > 0) {
- minutesLeft = hoursLeft * 60 - currentMinute;
- } else {
- minutesLeft = 24 * 60 - (currentHour * 60 + currentMinute);
- }
-
- if (minutesLeft > 60) {
- timeLeft = `${Math.round(minutesLeft / 60)} hours`;
- } else {
- timeLeft = `${minutesLeft} minutes`;
- }
-
- return timeLeft;
- };
+ }
+});
+
+const timeLeftToday = () => {
+ const now = new Date();
+ const currentHour = now.getHours();
+ const currentMinute = now.getMinutes();
+ const hoursLeft = 24 - currentHour;
+ let minutesLeft = 0;
+ let timeLeft = '';
+
+ if (hoursLeft > 0) {
+ minutesLeft = hoursLeft * 60 - currentMinute;
+ } else {
+ minutesLeft = 24 * 60 - (currentHour * 60 + currentMinute);
+ }
+
+ if (minutesLeft > 60) {
+ timeLeft = `${Math.round(minutesLeft / 60)} hours`;
+ } else {
+ timeLeft = `${minutesLeft} minutes`;
+ }
+
+ return timeLeft;
+};
</script>
{#if !$settings.displayDisableLastActivityWarning && !lastActivityWasToday}
diff --git a/src/lib/Home/Root.svelte b/src/lib/Home/Root.svelte
index eb174471..b3c2279e 100644
--- a/src/lib/Home/Root.svelte
+++ b/src/lib/Home/Root.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
- import settings from '$stores/settings';
- import { fly } from 'svelte/transition';
+import settings from '$stores/settings';
+import { fly } from 'svelte/transition';
- export let data: { url: string };
- export let way: number;
+export let data: { url: string };
+export let way: number;
- const animationDelay = 100;
+const animationDelay = 100;
</script>
{#if $settings.displayDisableAnimations}
diff --git a/src/lib/Image/FallbackImage.svelte b/src/lib/Image/FallbackImage.svelte
index 7e458339..27c427be 100644
--- a/src/lib/Image/FallbackImage.svelte
+++ b/src/lib/Image/FallbackImage.svelte
@@ -1,24 +1,24 @@
<script lang="ts">
- export let source: string | undefined | null;
- export let alternative: string | undefined | null;
- export let fallback: string | undefined | null;
- export let maxReplaceCount = 1;
- export let replaceDelay = 1000;
- export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg';
- export let hideOnError = false;
- export let style = '';
+export let source: string | undefined | null;
+export let alternative: string | undefined | null;
+export let fallback: string | undefined | null;
+export let maxReplaceCount = 1;
+export let replaceDelay = 1000;
+export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg';
+export let hideOnError = false;
+export let style = '';
- let replaceCount = 0;
+let replaceCount = 0;
- const delayedReplace = (event: Event, image: string | undefined | null) => {
- if (replaceCount >= maxReplaceCount) return;
+const delayedReplace = (event: Event, image: string | undefined | null) => {
+ if (replaceCount >= maxReplaceCount) return;
- setTimeout(() => {
- (event.target as HTMLImageElement).src = image || '';
+ setTimeout(() => {
+ (event.target as HTMLImageElement).src = image || '';
- replaceCount += 1;
- }, replaceDelay);
- };
+ replaceCount += 1;
+ }, replaceDelay);
+};
</script>
{#if replaceCount < maxReplaceCount}
diff --git a/src/lib/Image/ParallaxImage.svelte b/src/lib/Image/ParallaxImage.svelte
index eb0383ef..66906cbc 100644
--- a/src/lib/Image/ParallaxImage.svelte
+++ b/src/lib/Image/ParallaxImage.svelte
@@ -1,40 +1,40 @@
<script lang="ts">
- import { cubicOut } from 'svelte/easing';
- import { tweened } from 'svelte/motion';
+import { cubicOut } from 'svelte/easing';
+import { tweened } from 'svelte/motion';
- export let source: string;
- export let duration = 300 * 1.75;
- export let easing = cubicOut;
- export let alternativeText: string;
- export let classList = '';
- export let style = '';
- export let factor = 1.25;
- export let limit = 300 * 1.75;
- export let borderRadius = '8px';
- export let loading: 'lazy' | 'eager' = 'lazy';
- export let fetchpriority: 'high' | 'low' | 'auto' | undefined = undefined;
- export let width: number | undefined = undefined;
- export let height: number | undefined = undefined;
+export let source: string;
+export let duration = 300 * 1.75;
+export let easing = cubicOut;
+export let alternativeText: string;
+export let classList = '';
+export let style = '';
+export let factor = 1.25;
+export let limit = 300 * 1.75;
+export let borderRadius = '8px';
+export let loading: 'lazy' | 'eager' = 'lazy';
+export let fetchpriority: 'high' | 'low' | 'auto' | undefined = undefined;
+export let width: number | undefined = undefined;
+export let height: number | undefined = undefined;
- let badgeReference: HTMLImageElement;
- const mouse = tweened({ x: 0, y: 0 }, { duration, easing });
+let badgeReference: HTMLImageElement;
+const mouse = tweened({ x: 0, y: 0 }, { duration, easing });
- const handleMouseMove = (event: MouseEvent) => {
- const boundingRectangle = badgeReference.getBoundingClientRect();
+const handleMouseMove = (event: MouseEvent) => {
+ const boundingRectangle = badgeReference.getBoundingClientRect();
- if ($mouse.x === 0 && $mouse.y === 0) $mouse = { x: event.clientX, y: event.clientY };
+ if ($mouse.x === 0 && $mouse.y === 0) $mouse = { x: event.clientX, y: event.clientY };
- $mouse.x +=
- (-(event.clientX - boundingRectangle.left - boundingRectangle.width / 2) - $mouse.x) * factor;
- $mouse.y +=
- (-(event.clientY - boundingRectangle.top - boundingRectangle.height / 2) - $mouse.y) * factor;
- $mouse.x = Math.max(Math.min($mouse.x, limit), -limit);
- $mouse.y = Math.max(Math.min($mouse.y, limit), -limit);
- };
+ $mouse.x +=
+ (-(event.clientX - boundingRectangle.left - boundingRectangle.width / 2) - $mouse.x) * factor;
+ $mouse.y +=
+ (-(event.clientY - boundingRectangle.top - boundingRectangle.height / 2) - $mouse.y) * factor;
+ $mouse.x = Math.max(Math.min($mouse.x, limit), -limit);
+ $mouse.y = Math.max(Math.min($mouse.y, limit), -limit);
+};
- const handleMouseLeave = () => {
- $mouse = { x: 0, y: 0 };
- };
+const handleMouseLeave = () => {
+ $mouse = { x: 0, y: 0 };
+};
</script>
<img
diff --git a/src/lib/Landing.svelte b/src/lib/Landing.svelte
index 4a5c7df0..bddd16df 100644
--- a/src/lib/Landing.svelte
+++ b/src/lib/Landing.svelte
@@ -1,67 +1,67 @@
<script lang="ts">
- import root from './Utility/root';
- import { env } from '$env/dynamic/public';
- import CompletedAnimeList from './List/Anime/CompletedAnimeList.svelte';
- import MangaListTemplate from './List/Manga/MangaListTemplate.svelte';
- import localforage from 'localforage';
- import { onMount } from 'svelte';
-
- let sectionsVisible = $state<boolean[]>([false, false, false]);
- let mangaContainer: HTMLElement;
- let animeContainer: HTMLElement;
- let gridLimit = $state<number | undefined>(undefined);
- let demoFocused = $state(false);
- const COVER_WIDTH = 100;
- const COVER_GAP = 12;
- const dummyCount = 20;
-
- const calculateLimit = () => {
- const container = mangaContainer || animeContainer;
-
- if (!container) return;
-
- const containerWidth = container.clientWidth;
- const itemWidth = COVER_WIDTH + COVER_GAP;
- const padding = containerWidth < 500 ? 48 : 36;
-
- gridLimit = Math.max(2, Math.floor((containerWidth - padding) / itemWidth));
+import root from './Utility/root';
+import { env } from '$env/dynamic/public';
+import CompletedAnimeList from './List/Anime/CompletedAnimeList.svelte';
+import MangaListTemplate from './List/Manga/MangaListTemplate.svelte';
+import localforage from 'localforage';
+import { onMount } from 'svelte';
+
+let sectionsVisible = $state<boolean[]>([false, false, false]);
+let mangaContainer: HTMLElement;
+let animeContainer: HTMLElement;
+let gridLimit = $state<number | undefined>(undefined);
+let demoFocused = $state(false);
+const COVER_WIDTH = 100;
+const COVER_GAP = 12;
+const dummyCount = 20;
+
+const calculateLimit = () => {
+ const container = mangaContainer || animeContainer;
+
+ if (!container) return;
+
+ const containerWidth = container.clientWidth;
+ const itemWidth = COVER_WIDTH + COVER_GAP;
+ const padding = containerWidth < 500 ? 48 : 36;
+
+ gridLimit = Math.max(2, Math.floor((containerWidth - padding) / itemWidth));
+};
+
+onMount(() => {
+ const intersectionObserver = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ const index = Number(entry.target.getAttribute('data-section'));
+
+ sectionsVisible[index] = true;
+ }
+ });
+ },
+ { threshold: 0.1 }
+ );
+
+ document.querySelectorAll('.landing-section').forEach((el) => intersectionObserver.observe(el));
+
+ calculateLimit();
+
+ const resizeObserver = new ResizeObserver(calculateLimit);
+
+ if (mangaContainer) resizeObserver.observe(mangaContainer);
+ if (animeContainer) resizeObserver.observe(animeContainer);
+
+ const handleKeydown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape' && demoFocused) demoFocused = false;
};
- onMount(() => {
- const intersectionObserver = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- const index = Number(entry.target.getAttribute('data-section'));
+ document.addEventListener('keydown', handleKeydown);
- sectionsVisible[index] = true;
- }
- });
- },
- { threshold: 0.1 }
- );
-
- document.querySelectorAll('.landing-section').forEach((el) => intersectionObserver.observe(el));
-
- calculateLimit();
-
- const resizeObserver = new ResizeObserver(calculateLimit);
-
- if (mangaContainer) resizeObserver.observe(mangaContainer);
- if (animeContainer) resizeObserver.observe(animeContainer);
-
- const handleKeydown = (e: KeyboardEvent) => {
- if (e.key === 'Escape' && demoFocused) demoFocused = false;
- };
-
- document.addEventListener('keydown', handleKeydown);
-
- return () => {
- intersectionObserver.disconnect();
- resizeObserver.disconnect();
- document.removeEventListener('keydown', handleKeydown);
- };
- });
+ return () => {
+ intersectionObserver.disconnect();
+ resizeObserver.disconnect();
+ document.removeEventListener('keydown', handleKeydown);
+ };
+});
</script>
<div class="landing">
diff --git a/src/lib/LandingHero.svelte b/src/lib/LandingHero.svelte
index 29f9f556..fc87b5fe 100644
--- a/src/lib/LandingHero.svelte
+++ b/src/lib/LandingHero.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
- import { env } from '$env/dynamic/public';
- import localforage from 'localforage';
+import { env } from '$env/dynamic/public';
+import localforage from 'localforage';
- let heroSection = $state<HTMLElement>();
+let heroSection = $state<HTMLElement>();
- const scrollPastHero = () => {
- if (!heroSection) return;
+const scrollPastHero = () => {
+ if (!heroSection) return;
- const heroBottom = heroSection.getBoundingClientRect().bottom + window.scrollY;
+ const heroBottom = heroSection.getBoundingClientRect().bottom + window.scrollY;
- window.scrollTo({ top: heroBottom, behavior: 'smooth' });
- };
+ window.scrollTo({ top: heroBottom, behavior: 'smooth' });
+};
</script>
<section class="hero" bind:this={heroSection}>
diff --git a/src/lib/Layout/Dropdown.svelte b/src/lib/Layout/Dropdown.svelte
index e7e00272..324c9498 100644
--- a/src/lib/Layout/Dropdown.svelte
+++ b/src/lib/Layout/Dropdown.svelte
@@ -1,21 +1,21 @@
<script lang="ts">
- interface Item {
- name: string;
- url: string;
- onClick?: () => void;
- preventDefault?: boolean;
- }
-
- export let items: Item[] = [];
- export let title: string | undefined = undefined;
- export let header = true;
- export let center = false;
-
- let open = false;
-
- const handleClickOutside = (event: MouseEvent) => {
- if (!(event.target as HTMLElement).closest('.dropdown')) open = false;
- };
+interface Item {
+ name: string;
+ url: string;
+ onClick?: () => void;
+ preventDefault?: boolean;
+}
+
+export let items: Item[] = [];
+export let title: string | undefined = undefined;
+export let header = true;
+export let center = false;
+
+let open = false;
+
+const handleClickOutside = (event: MouseEvent) => {
+ if (!(event.target as HTMLElement).closest('.dropdown')) open = false;
+};
</script>
<svelte:window onclick={handleClickOutside} />
diff --git a/src/lib/Layout/NumberTicker.svelte b/src/lib/Layout/NumberTicker.svelte
index 226e89c9..61e1757d 100644
--- a/src/lib/Layout/NumberTicker.svelte
+++ b/src/lib/Layout/NumberTicker.svelte
@@ -1,21 +1,21 @@
<script>
- import { tweened } from 'svelte/motion';
- import { cubicOut } from 'svelte/easing';
+import { tweened } from 'svelte/motion';
+import { cubicOut } from 'svelte/easing';
- export let start = 0;
- export let end = 100;
- export let duration = 2000;
- export let delay = 0;
+export let start = 0;
+export let end = 100;
+export let duration = 2000;
+export let delay = 0;
- const count = tweened(start, {
- duration: duration,
- easing: cubicOut,
- delay: delay
- });
+const count = tweened(start, {
+ duration: duration,
+ easing: cubicOut,
+ delay: delay
+});
- $: {
- count.set(end);
- }
+$: {
+ count.set(end);
+}
</script>
<span class="counter" class:visible={$count !== start}>
diff --git a/src/lib/Layout/Popup.svelte b/src/lib/Layout/Popup.svelte
index dc1557e3..92261a5b 100644
--- a/src/lib/Layout/Popup.svelte
+++ b/src/lib/Layout/Popup.svelte
@@ -1,37 +1,37 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { onMount } from 'svelte';
-
- export let onLeave = () => {
- return;
- };
- export let card = true;
- export let smallCard = false;
- export let fullscreen = false;
- export let show = true;
- export let locked = false;
- export let center = false;
-
- const handleClickOutside = (event: MouseEvent) => {
- if (!locked && (event.target as HTMLElement).classList.contains('popup')) {
- show = false;
-
- onLeave();
- }
- };
-
- onMount(() => {
- if (browser) document.body.style.overflow = 'auto';
- });
-
- $: {
- if (browser) {
- document.body.style.overflow = 'auto';
-
- if (show) document.body.style.overflow = 'hidden';
- else document.body.style.overflow = 'auto';
- }
+import { browser } from '$app/environment';
+import { onMount } from 'svelte';
+
+export let onLeave = () => {
+ return;
+};
+export let card = true;
+export let smallCard = false;
+export let fullscreen = false;
+export let show = true;
+export let locked = false;
+export let center = false;
+
+const handleClickOutside = (event: MouseEvent) => {
+ if (!locked && (event.target as HTMLElement).classList.contains('popup')) {
+ show = false;
+
+ onLeave();
}
+};
+
+onMount(() => {
+ if (browser) document.body.style.overflow = 'auto';
+});
+
+$: {
+ if (browser) {
+ document.body.style.overflow = 'auto';
+
+ if (show) document.body.style.overflow = 'hidden';
+ else document.body.style.overflow = 'auto';
+ }
+}
</script>
<svelte:window onclick={handleClickOutside} />
diff --git a/src/lib/Layout/Spacer.svelte b/src/lib/Layout/Spacer.svelte
index a26796df..a4ecef18 100644
--- a/src/lib/Layout/Spacer.svelte
+++ b/src/lib/Layout/Spacer.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let size: 'sm' | 'md' | 'lg' = 'md';
+export let size: 'sm' | 'md' | 'lg' = 'md';
</script>
<div class="spacer {size}"></div>
diff --git a/src/lib/Layout/TextTransition.svelte b/src/lib/Layout/TextTransition.svelte
index 35cfe1ea..13a4ed9b 100644
--- a/src/lib/Layout/TextTransition.svelte
+++ b/src/lib/Layout/TextTransition.svelte
@@ -1,24 +1,24 @@
<script>
- import { tweened } from 'svelte/motion';
- import { cubicOut } from 'svelte/easing';
+import { tweened } from 'svelte/motion';
+import { cubicOut } from 'svelte/easing';
- export let text = '';
- export let opacityTransitionDuration = 50;
- export let blurTransitionDuration = opacityTransitionDuration;
- export let easing = cubicOut;
+export let text = '';
+export let opacityTransitionDuration = 50;
+export let blurTransitionDuration = opacityTransitionDuration;
+export let easing = cubicOut;
- let previousValue = '';
- let opacity = tweened(1, { duration: opacityTransitionDuration, easing });
- let blur = tweened(0, { duration: blurTransitionDuration, easing });
+let previousValue = '';
+let opacity = tweened(1, { duration: opacityTransitionDuration, easing });
+let blur = tweened(0, { duration: blurTransitionDuration, easing });
- $: {
- if (text !== previousValue)
- Promise.all([opacity.set(0), blur.set(10)]).then(() => {
- previousValue = text;
+$: {
+ if (text !== previousValue)
+ Promise.all([opacity.set(0), blur.set(10)]).then(() => {
+ previousValue = text;
- Promise.all([opacity.set(1), blur.set(0)]);
- });
- }
+ Promise.all([opacity.set(1), blur.set(0)]);
+ });
+}
</script>
<span
diff --git a/src/lib/Layout/Username.svelte b/src/lib/Layout/Username.svelte
index 8b89b708..bd902cda 100644
--- a/src/lib/Layout/Username.svelte
+++ b/src/lib/Layout/Username.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let username: string;
+export let username: string;
</script>
<a href={`https://anilist.co/user/${username}/`}>@{username}</a>
diff --git a/src/lib/Lazy.svelte b/src/lib/Lazy.svelte
index fd08ed07..6386177b 100644
--- a/src/lib/Lazy.svelte
+++ b/src/lib/Lazy.svelte
@@ -1,47 +1,44 @@
<script lang="ts">
- import { onMount, onDestroy } from 'svelte';
-
- export let top = 0;
- export let bottom = 0;
- export let left = 0;
- export let right = 0;
- export let steps = 100;
- export let threshold = 0.01;
- export let once = false;
-
- let element: Element;
- let percent = 0;
- let visible = false;
- let observer: IntersectionObserver;
-
- const intersectPercent = (
- entries: IntersectionObserverEntry[],
- observer: IntersectionObserver
- ) => {
- for (let entry of entries)
- if (entry.target === element) {
- percent = Math.round(entry.intersectionRatio * 100);
- visible = entry.intersectionRatio >= threshold;
-
- if (visible && once) observer.unobserve(element);
-
- break;
- }
- };
-
- onMount(() => {
- if ('IntersectionObserver' in window) {
- let options = {
- rootMargin: `${top}px ${right}px ${bottom}px ${left}px`,
- threshold: Array.from({ length: steps }, (_, i) => i / (steps - 1))
- };
-
- observer = new IntersectionObserver(intersectPercent, options);
- observer.observe(element);
+import { onMount, onDestroy } from 'svelte';
+
+export let top = 0;
+export let bottom = 0;
+export let left = 0;
+export let right = 0;
+export let steps = 100;
+export let threshold = 0.01;
+export let once = false;
+
+let element: Element;
+let percent = 0;
+let visible = false;
+let observer: IntersectionObserver;
+
+const intersectPercent = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
+ for (let entry of entries)
+ if (entry.target === element) {
+ percent = Math.round(entry.intersectionRatio * 100);
+ visible = entry.intersectionRatio >= threshold;
+
+ if (visible && once) observer.unobserve(element);
+
+ break;
}
- });
+};
- onDestroy(() => observer?.unobserve(element));
+onMount(() => {
+ if ('IntersectionObserver' in window) {
+ let options = {
+ rootMargin: `${top}px ${right}px ${bottom}px ${left}px`,
+ threshold: Array.from({ length: steps }, (_, i) => i / (steps - 1))
+ };
+
+ observer = new IntersectionObserver(intersectPercent, options);
+ observer.observe(element);
+ }
+});
+
+onDestroy(() => observer?.unobserve(element));
</script>
<div bind:this={element}>
diff --git a/src/lib/List/Anime/AnimeListTemplate.svelte b/src/lib/List/Anime/AnimeListTemplate.svelte
index 185419de..cb1d0fcd 100644
--- a/src/lib/List/Anime/AnimeListTemplate.svelte
+++ b/src/lib/List/Anime/AnimeListTemplate.svelte
@@ -1,55 +1,55 @@
<script lang="ts">
- /* eslint svelte/no-at-html-tags: "off" */
+/* eslint svelte/no-at-html-tags: "off" */
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import type { Media } from '$lib/Data/AniList/media';
- import Error from '$lib/Error/RateLimited.svelte';
- import settings from '$stores/settings';
- import CleanAnimeList from './CleanAnimeList.svelte';
- import ListTitle from '../ListTitle.svelte';
- import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
- import PlaceholderList from './PlaceholderList.svelte';
- import { browser } from '$app/environment';
- import { onMount } from 'svelte';
- import subsPlease from '$stores/subsPlease';
- import identity from '$stores/identity';
- import localforage from 'localforage';
- import type { Title } from '../mediaTitle';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import type { Media } from '$lib/Data/AniList/media';
+import Error from '$lib/Error/RateLimited.svelte';
+import settings from '$stores/settings';
+import CleanAnimeList from './CleanAnimeList.svelte';
+import ListTitle from '../ListTitle.svelte';
+import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+import PlaceholderList from './PlaceholderList.svelte';
+import { browser } from '$app/environment';
+import { onMount } from 'svelte';
+import subsPlease from '$stores/subsPlease';
+import identity from '$stores/identity';
+import localforage from 'localforage';
+import type { Title } from '../mediaTitle';
- export let endTime: number;
- export let cleanMedia: (
- media: Media[],
- displayUnresolved: boolean,
- subsPlease: SubsPlease | null,
- plannedOnly?: boolean
- ) => Media[];
- export let animeLists: Promise<Media[]>;
- export let user: AniListAuthorisation;
- export let title: Title;
- export let completed = false;
- export let plannedOnly = false;
- export let upcoming = false;
- export let notYetReleased = false;
- export let dummy = false;
- export let disableFilter = false;
- export let limit: number | undefined = undefined;
+export let endTime: number;
+export let cleanMedia: (
+ media: Media[],
+ displayUnresolved: boolean,
+ subsPlease: SubsPlease | null,
+ plannedOnly?: boolean
+) => Media[];
+export let animeLists: Promise<Media[]>;
+export let user: AniListAuthorisation;
+export let title: Title;
+export let completed = false;
+export let plannedOnly = false;
+export let upcoming = false;
+export let notYetReleased = false;
+export let dummy = false;
+export let disableFilter = false;
+export let limit: number | undefined = undefined;
- let lastUpdatedMedia = -1;
- let previousAnimeList: Media[];
- let pendingUpdate: number | null = null;
- let lastListSize = 8;
+let lastUpdatedMedia = -1;
+let previousAnimeList: Media[];
+let pendingUpdate: number | null = null;
+let lastListSize = 8;
- onMount(async () => {
- if (browser) {
- const lastStoredList = (await localforage.getItem(
- `last${
- notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : ''
- }AnimeListLength`
- )) as string | null;
+onMount(async () => {
+ if (browser) {
+ const lastStoredList = (await localforage.getItem(
+ `last${
+ notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : ''
+ }AnimeListLength`
+ )) as string | null;
- if (lastStoredList) lastListSize = parseInt(lastStoredList);
- }
- });
+ if (lastStoredList) lastListSize = parseInt(lastStoredList);
+ }
+});
</script>
{#if !$subsPlease && !dummy}
diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte
index aee035a4..14406f04 100644
--- a/src/lib/List/Anime/CleanAnimeList.svelte
+++ b/src/lib/List/Anime/CleanAnimeList.svelte
@@ -1,177 +1,176 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- /* eslint svelte/no-at-html-tags: "off" */
-
- import settings from '$stores/settings';
- import type { Media } from '$lib/Data/AniList/media';
- import { cleanCache, incrementMediaProgress } from '$lib/Media/Anime/cache';
- import { totalEpisodes } from '$lib/Media/Anime/episodes';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import ListTitle from '../ListTitle.svelte';
- import { onDestroy, onMount } from 'svelte';
- import AiringTime from '$lib/Media/Anime/Airing/AiringTime.svelte';
- import { browser } from '$app/environment';
- import identity from '$stores/identity';
- import '../covers.css';
- import revalidateAnime from '$stores/revalidateAnime';
- import CleanGrid from '$lib/List/CleanGrid.svelte';
- import CleanList from '../CleanList.svelte';
- import stateBin from '$stores/stateBin';
- import localforage from 'localforage';
- import MediaRoulette from '../MediaRoulette.svelte';
- import type { Title } from '../mediaTitle';
-
- export let media: Media[];
- export let title: Title;
- export let animeLists: Promise<Media[]>;
- export let user: AniListAuthorisation;
- export let endTime: number;
- export let lastUpdatedMedia: number;
- export let completed = false;
- export let previousAnimeList: Media[];
- export let pendingUpdate: number | null;
- export let upcoming = false;
- export let notYetReleased = false;
- export let dummy = false;
- export let disableFilter = false;
- export let limit: number | undefined = undefined;
-
- let showRoulette = false;
- let airingRefreshTimeout: ReturnType<typeof setTimeout> | undefined;
- let scheduledAiringAt: number | null = null;
- let totalEpisodeDueCount = media
- .map((anime) => {
- if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes) return 1;
-
- if ($settings.displayTotalDueEpisodes && completed && !$settings.displayTotalEpisodes)
- return 1;
-
- if ($settings.displayTotalEpisodes && anime.status === 'FINISHED')
- return anime.episodes - (anime.mediaListEntry?.progress || 0);
-
- if (anime.status === 'NOT_YET_RELEASED') return 1;
-
- return (
- (anime.nextAiringEpisode?.episode || 1) -
- (anime.mediaListEntry?.progress || 0) -
- (upcoming || notYetReleased ? 0 : 1)
- );
- })
- .reduce((a, b) => a + b, 0);
- const lists = Array.from(
- new Set(
- media
- .flatMap((m) => Object.entries(m.mediaListEntry?.customLists ?? {}))
- .filter(([_key, value]) => value)
- .map(([key]) => key)
- )
- );
- let filterKind = upcoming
- ? 'Upcoming'
- : notYetReleased
- ? 'NotYetReleased'
- : completed
- ? 'Completed'
- : 'Due';
- const filterKey = `${filterKind}AnimeListFilter`;
-
- $: selectedList = disableFilter ? 'All' : ($stateBin[filterKey] as string) || 'All';
-
- $: filteredMedia =
- selectedList === 'All' || !$settings.displayMediaListFilter
- ? media
- : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);
-
- const clearAiringRefreshTimeout = () => {
- if (airingRefreshTimeout) clearTimeout(airingRefreshTimeout);
+import Spacer from '$lib/Layout/Spacer.svelte';
+/* eslint svelte/no-at-html-tags: "off" */
+
+import settings from '$stores/settings';
+import type { Media } from '$lib/Data/AniList/media';
+import { cleanCache, incrementMediaProgress } from '$lib/Media/Anime/cache';
+import { totalEpisodes } from '$lib/Media/Anime/episodes';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import ListTitle from '../ListTitle.svelte';
+import { onDestroy, onMount } from 'svelte';
+import AiringTime from '$lib/Media/Anime/Airing/AiringTime.svelte';
+import { browser } from '$app/environment';
+import identity from '$stores/identity';
+import '../covers.css';
+import revalidateAnime from '$stores/revalidateAnime';
+import CleanGrid from '$lib/List/CleanGrid.svelte';
+import CleanList from '../CleanList.svelte';
+import stateBin from '$stores/stateBin';
+import localforage from 'localforage';
+import MediaRoulette from '../MediaRoulette.svelte';
+import type { Title } from '../mediaTitle';
+
+export let media: Media[];
+export let title: Title;
+export let animeLists: Promise<Media[]>;
+export let user: AniListAuthorisation;
+export let endTime: number;
+export let lastUpdatedMedia: number;
+export let completed = false;
+export let previousAnimeList: Media[];
+export let pendingUpdate: number | null;
+export let upcoming = false;
+export let notYetReleased = false;
+export let dummy = false;
+export let disableFilter = false;
+export let limit: number | undefined = undefined;
+
+let showRoulette = false;
+let airingRefreshTimeout: ReturnType<typeof setTimeout> | undefined;
+let scheduledAiringAt: number | null = null;
+let totalEpisodeDueCount = media
+ .map((anime) => {
+ if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes) return 1;
+
+ if ($settings.displayTotalDueEpisodes && completed && !$settings.displayTotalEpisodes) return 1;
+
+ if ($settings.displayTotalEpisodes && anime.status === 'FINISHED')
+ return anime.episodes - (anime.mediaListEntry?.progress || 0);
+
+ if (anime.status === 'NOT_YET_RELEASED') return 1;
+
+ return (
+ (anime.nextAiringEpisode?.episode || 1) -
+ (anime.mediaListEntry?.progress || 0) -
+ (upcoming || notYetReleased ? 0 : 1)
+ );
+ })
+ .reduce((a, b) => a + b, 0);
+const lists = Array.from(
+ new Set(
+ media
+ .flatMap((m) => Object.entries(m.mediaListEntry?.customLists ?? {}))
+ .filter(([_key, value]) => value)
+ .map(([key]) => key)
+ )
+);
+let filterKind = upcoming
+ ? 'Upcoming'
+ : notYetReleased
+ ? 'NotYetReleased'
+ : completed
+ ? 'Completed'
+ : 'Due';
+const filterKey = `${filterKind}AnimeListFilter`;
+
+$: selectedList = disableFilter ? 'All' : ($stateBin[filterKey] as string) || 'All';
+
+$: filteredMedia =
+ selectedList === 'All' || !$settings.displayMediaListFilter
+ ? media
+ : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);
+
+const clearAiringRefreshTimeout = () => {
+ if (airingRefreshTimeout) clearTimeout(airingRefreshTimeout);
+
+ airingRefreshTimeout = undefined;
+ scheduledAiringAt = null;
+};
+
+const scheduleAiringRefresh = () => {
+ if (!browser) return;
+
+ if (dummy || media.length === 0) {
+ clearAiringRefreshTimeout();
- airingRefreshTimeout = undefined;
- scheduledAiringAt = null;
- };
+ return;
+ }
- const scheduleAiringRefresh = () => {
- if (!browser) return;
+ const nextAiringAt = media.reduce<number | null>((closest, currentMedia) => {
+ if (currentMedia.status !== 'RELEASING' && currentMedia.status !== 'NOT_YET_RELEASED')
+ return closest;
- if (dummy || media.length === 0) {
- clearAiringRefreshTimeout();
+ const airingAt = currentMedia.nextAiringEpisode?.airingAt;
- return;
- }
+ if (!airingAt) return closest;
+ if (closest === null) return airingAt;
- const nextAiringAt = media.reduce<number | null>((closest, currentMedia) => {
- if (currentMedia.status !== 'RELEASING' && currentMedia.status !== 'NOT_YET_RELEASED')
- return closest;
+ return airingAt < closest ? airingAt : closest;
+ }, null);
- const airingAt = currentMedia.nextAiringEpisode?.airingAt;
+ if (!nextAiringAt) {
+ clearAiringRefreshTimeout();
- if (!airingAt) return closest;
- if (closest === null) return airingAt;
+ return;
+ }
- return airingAt < closest ? airingAt : closest;
- }, null);
+ if (airingRefreshTimeout && scheduledAiringAt === nextAiringAt) return;
- if (!nextAiringAt) {
- clearAiringRefreshTimeout();
+ clearAiringRefreshTimeout();
+ scheduledAiringAt = nextAiringAt;
+ airingRefreshTimeout = setTimeout(
+ () => {
+ const now = Date.now() / 1000;
- return;
- }
+ if (media.some((m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < now))
+ animeLists = cleanCache(user, $identity);
- if (airingRefreshTimeout && scheduledAiringAt === nextAiringAt) return;
+ scheduleAiringRefresh();
+ },
+ Math.max(1000, nextAiringAt * 1000 - Date.now() + 250)
+ );
+};
- clearAiringRefreshTimeout();
- scheduledAiringAt = nextAiringAt;
- airingRefreshTimeout = setTimeout(
- () => {
- const now = Date.now() / 1000;
+onMount(async () => {
+ if (dummy) return;
- if (media.some((m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < now))
- animeLists = cleanCache(user, $identity);
+ scheduleAiringRefresh();
- scheduleAiringRefresh();
- },
- Math.max(1000, nextAiringAt * 1000 - Date.now() + 250)
+ if (browser)
+ await localforage.setItem(
+ `last${
+ notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : ''
+ }AnimeListLength`,
+ media.length.toString()
);
- };
+});
- onMount(async () => {
- if (dummy) return;
+$: if (browser && !dummy) {
+ media;
- scheduleAiringRefresh();
+ scheduleAiringRefresh();
+}
- if (browser)
- await localforage.setItem(
- `last${
- notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : ''
- }AnimeListLength`,
- media.length.toString()
- );
- });
+onDestroy(() => clearAiringRefreshTimeout());
- $: if (browser && !dummy) {
- media;
-
- scheduleAiringRefresh();
- }
+const increment = (anime: Media, progress: number) => {
+ if (!dummy && pendingUpdate !== anime.id) {
+ $revalidateAnime = true;
+ lastUpdatedMedia = anime.id;
+ pendingUpdate = anime.id;
- onDestroy(() => clearAiringRefreshTimeout());
+ incrementMediaProgress(anime.id, anime.mediaListEntry?.progress, user, () => {
+ const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry;
- const increment = (anime: Media, progress: number) => {
- if (!dummy && pendingUpdate !== anime.id) {
- $revalidateAnime = true;
- lastUpdatedMedia = anime.id;
- pendingUpdate = anime.id;
+ if (mediaListEntry) mediaListEntry.progress = progress + 1;
- incrementMediaProgress(anime.id, anime.mediaListEntry?.progress, user, () => {
- const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry;
-
- if (mediaListEntry) mediaListEntry.progress = progress + 1;
-
- previousAnimeList = media;
- animeLists = cleanCache(user, $identity);
- pendingUpdate = null;
- });
- }
- };
+ previousAnimeList = media;
+ animeLists = cleanCache(user, $identity);
+ pendingUpdate = null;
+ });
+ }
+};
</script>
<ListTitle
diff --git a/src/lib/List/Anime/CompletedAnimeList.svelte b/src/lib/List/Anime/CompletedAnimeList.svelte
index b9e86ba0..240d386d 100644
--- a/src/lib/List/Anime/CompletedAnimeList.svelte
+++ b/src/lib/List/Anime/CompletedAnimeList.svelte
@@ -1,112 +1,112 @@
<script lang="ts">
- import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { onMount } from 'svelte';
- import anime from '$stores/anime';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import settings from '$stores/settings';
- import AnimeList from './AnimeListTemplate.svelte';
- import { addNotification } from '$lib/Notification/store';
- import locale from '$stores/locale';
- import identity from '$stores/identity';
- import sampleAnime from '$lib/Data/Static/SampleMedia/anime.json';
+import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { onMount } from 'svelte';
+import anime from '$stores/anime';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import settings from '$stores/settings';
+import AnimeList from './AnimeListTemplate.svelte';
+import { addNotification } from '$lib/Notification/store';
+import locale from '$stores/locale';
+import identity from '$stores/identity';
+import sampleAnime from '$lib/Data/Static/SampleMedia/anime.json';
- export let user: AniListAuthorisation = {
- accessToken: '',
- refreshToken: '',
- expiresIn: 0,
- tokenType: ''
- };
- export let dummy = false;
- export let dummyCount = 7;
- export let disableFilter = false;
- export let limit: number | undefined = undefined;
- let animeLists: Promise<Media[]>;
- let startTime: number;
- let endTime: number;
+export let user: AniListAuthorisation = {
+ accessToken: '',
+ refreshToken: '',
+ expiresIn: 0,
+ tokenType: ''
+};
+export let dummy = false;
+export let dummyCount = 7;
+export let disableFilter = false;
+export let limit: number | undefined = undefined;
+let animeLists: Promise<Media[]>;
+let startTime: number;
+let endTime: number;
- onMount(async () => {
- startTime = performance.now();
+onMount(async () => {
+ startTime = performance.now();
- if (dummy) {
- // Use deterministic selection for consistent display
- const filtered = sampleAnime.filter(
- (anime) =>
- anime.episodes &&
- !anime.tags.some((tag) => tag.name === 'Nudity') &&
- !anime.tags.some((tag) => tag.name === 'Rape') &&
- !anime.tags.some((tag) => tag.name === 'Tragedy') &&
- !anime.tags.some((tag) => tag.name === 'Bondage') &&
- !anime.genres.some((genre) => genre === 'Hentai') &&
- anime.genres.some((genre) => genre === 'Comedy') &&
- anime.status !== 'NOT_YET_RELEASED' &&
- anime.episodes > 1
- );
- animeLists = Promise.resolve(
- filtered.slice(0, dummyCount).map((anime, index) => {
- anime.status = 'FINISHED';
- anime.nextAiringEpisode = {
- airingAt: Math.floor(Date.now() / 1000) + (index + 1) * 24 * 60 * 60,
- episode: Math.floor((anime.episodes || 12) * 0.8)
- };
- anime.mediaListEntry.progress = Math.floor((anime.nextAiringEpisode.episode || 5) * 0.6);
- return anime;
- }) as unknown as Media[]
- );
- } else {
- animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
- addNotification
- });
- }
- });
+ if (dummy) {
+ // Use deterministic selection for consistent display
+ const filtered = sampleAnime.filter(
+ (anime) =>
+ anime.episodes &&
+ !anime.tags.some((tag) => tag.name === 'Nudity') &&
+ !anime.tags.some((tag) => tag.name === 'Rape') &&
+ !anime.tags.some((tag) => tag.name === 'Tragedy') &&
+ !anime.tags.some((tag) => tag.name === 'Bondage') &&
+ !anime.genres.some((genre) => genre === 'Hentai') &&
+ anime.genres.some((genre) => genre === 'Comedy') &&
+ anime.status !== 'NOT_YET_RELEASED' &&
+ anime.episodes > 1
+ );
+ animeLists = Promise.resolve(
+ filtered.slice(0, dummyCount).map((anime, index) => {
+ anime.status = 'FINISHED';
+ anime.nextAiringEpisode = {
+ airingAt: Math.floor(Date.now() / 1000) + (index + 1) * 24 * 60 * 60,
+ episode: Math.floor((anime.episodes || 12) * 0.8)
+ };
+ anime.mediaListEntry.progress = Math.floor((anime.nextAiringEpisode.episode || 5) * 0.6);
+ return anime;
+ }) as unknown as Media[]
+ );
+ } else {
+ animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ addNotification
+ });
+ }
+});
- const cleanMedia = (anime: Media[]) => {
- if (anime && dummy) return anime;
+const cleanMedia = (anime: Media[]) => {
+ if (anime && dummy) return anime;
- if (anime === undefined) return [];
+ if (anime === undefined) return [];
- const outdatedCompletedAnime = anime.filter(
- (media: Media) =>
- media.status === 'FINISHED' &&
- (media.mediaListEntry || { status: 'DROPPED' }).status != 'DROPPED' &&
- (media.mediaListEntry || { status: 'DROPPED' }).status !=
- ($settings.displayPausedMedia ? '' : 'PAUSED') &&
- (media.mediaListEntry || { progress: 0 }).progress >= ($settings.displayNotStarted ? 0 : 1)
- );
+ const outdatedCompletedAnime = anime.filter(
+ (media: Media) =>
+ media.status === 'FINISHED' &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status != 'DROPPED' &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status !=
+ ($settings.displayPausedMedia ? '' : 'PAUSED') &&
+ (media.mediaListEntry || { progress: 0 }).progress >= ($settings.displayNotStarted ? 0 : 1)
+ );
- outdatedCompletedAnime.sort((a: Media, b: Media) => {
- switch ($settings.displayAnimeSort) {
- case 'difference': {
- const difference = (anime: Media) =>
- (anime.nextAiringEpisode?.episode === -1
- ? 99999
- : anime.nextAiringEpisode?.episode || -1) -
- (anime.mediaListEntry || { progress: 0 }).progress;
+ outdatedCompletedAnime.sort((a: Media, b: Media) => {
+ switch ($settings.displayAnimeSort) {
+ case 'difference': {
+ const difference = (anime: Media) =>
+ (anime.nextAiringEpisode?.episode === -1
+ ? 99999
+ : anime.nextAiringEpisode?.episode || -1) -
+ (anime.mediaListEntry || { progress: 0 }).progress;
- return difference(a) - difference(b);
- }
+ return difference(a) - difference(b);
+ }
- case 'end_date':
- return (
- new Date(a.endDate.year, a.endDate.month - 1).getTime() -
- new Date(b.endDate.year, b.endDate.month - 1).getTime()
- );
+ case 'end_date':
+ return (
+ new Date(a.endDate.year, a.endDate.month - 1).getTime() -
+ new Date(b.endDate.year, b.endDate.month - 1).getTime()
+ );
- case 'start_date':
- return (
- new Date(a.startDate.year, a.startDate.month - 1).getTime() -
- new Date(b.startDate.year, b.startDate.month - 1).getTime()
- );
+ case 'start_date':
+ return (
+ new Date(a.startDate.year, a.startDate.month - 1).getTime() -
+ new Date(b.startDate.year, b.startDate.month - 1).getTime()
+ );
- case 'time_remaining':
- return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999);
- }
- });
+ case 'time_remaining':
+ return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999);
+ }
+ });
- if (!endTime) endTime = performance.now() - startTime;
+ if (!endTime) endTime = performance.now() - startTime;
- return outdatedCompletedAnime;
- };
+ return outdatedCompletedAnime;
+};
</script>
<AnimeList
diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte
index da1f6c48..e19bcd0c 100644
--- a/src/lib/List/Anime/DueAnimeList.svelte
+++ b/src/lib/List/Anime/DueAnimeList.svelte
@@ -1,130 +1,119 @@
<script lang="ts">
- import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { onDestroy, onMount } from 'svelte';
- import anime from '$stores/anime';
- import settings from '$stores/settings';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import AnimeList from './AnimeListTemplate.svelte';
- import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
- import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match';
- import { hasDueEpisodes, hasNoAiredEpisodes } from '$lib/Media/Anime/Airing/classify';
- import { addNotification } from '$lib/Notification/store';
- import locale from '$stores/locale';
- import identity from '$stores/identity';
-
- export let user: AniListAuthorisation;
- let animeLists: Promise<Media[]>;
- let startTime: number;
- let endTime: number;
- let keyCacher: ReturnType<typeof setInterval> | undefined;
- let keyCacheMinutes = -1;
-
- const restartKeyCacher = (cacheMinutes: number) => {
- if (keyCacher) clearInterval(keyCacher);
-
- keyCacheMinutes = cacheMinutes;
- keyCacher = setInterval(
- () => {
- startTime = performance.now();
- endTime = -1;
- animeLists = mediaListCollection(
- user,
- $identity,
- Type.Anime,
- $anime,
- $lastPruneTimes.anime,
- {
- forcePrune: true,
- addNotification
- }
- );
- },
- cacheMinutes * 1000 * 60
- );
- };
+import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { onDestroy, onMount } from 'svelte';
+import anime from '$stores/anime';
+import settings from '$stores/settings';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import AnimeList from './AnimeListTemplate.svelte';
+import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match';
+import { hasDueEpisodes, hasNoAiredEpisodes } from '$lib/Media/Anime/Airing/classify';
+import { addNotification } from '$lib/Notification/store';
+import locale from '$stores/locale';
+import identity from '$stores/identity';
+
+export let user: AniListAuthorisation;
+let animeLists: Promise<Media[]>;
+let startTime: number;
+let endTime: number;
+let keyCacher: ReturnType<typeof setInterval> | undefined;
+let keyCacheMinutes = -1;
+
+const restartKeyCacher = (cacheMinutes: number) => {
+ if (keyCacher) clearInterval(keyCacher);
+
+ keyCacheMinutes = cacheMinutes;
+ keyCacher = setInterval(
+ () => {
+ startTime = performance.now();
+ endTime = -1;
+ animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ forcePrune: true,
+ addNotification
+ });
+ },
+ cacheMinutes * 1000 * 60
+ );
+};
- onMount(async () => {
- restartKeyCacher($settings.cacheMinutes);
+onMount(async () => {
+ restartKeyCacher($settings.cacheMinutes);
- startTime = performance.now();
- animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
- addNotification
- });
+ startTime = performance.now();
+ animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ addNotification
});
+});
+
+$: if (keyCacher && keyCacheMinutes !== $settings.cacheMinutes)
+ restartKeyCacher($settings.cacheMinutes);
+
+onDestroy(() => {
+ if (keyCacher) clearInterval(keyCacher);
+});
+
+const cleanMedia = (anime: Media[], displayUnresolved: boolean, subsPlease: SubsPlease | null) => {
+ if (anime === undefined) return [];
+
+ let dueAnime = anime
+ .map((media) => injectAiringTime(media, subsPlease))
+ .filter(
+ // Releasing media
+ (media: Media) =>
+ media.status === 'RELEASING' &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status !=
+ ($settings.displayPausedMedia ? '' : 'PAUSED') &&
+ (media.mediaListEntry || { progress: 0 }).progress >=
+ ($settings.displayNotStarted === true ? 0 : 1) &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status !== 'DROPPED'
+ )
+ .filter((media: Media) =>
+ // Outdated media
+ hasDueEpisodes(media)
+ )
+ .map((media: Media) => {
+ if (hasNoAiredEpisodes(media)) media.nextAiringEpisode = { episode: -1 };
+
+ return media;
+ });
- $: if (keyCacher && keyCacheMinutes !== $settings.cacheMinutes)
- restartKeyCacher($settings.cacheMinutes);
-
- onDestroy(() => {
- if (keyCacher) clearInterval(keyCacher);
- });
+ if (!displayUnresolved)
+ dueAnime = dueAnime.filter((media: Media) => media.nextAiringEpisode?.episode !== -1);
- const cleanMedia = (
- anime: Media[],
- displayUnresolved: boolean,
- subsPlease: SubsPlease | null
- ) => {
- if (anime === undefined) return [];
-
- let dueAnime = anime
- .map((media) => injectAiringTime(media, subsPlease))
- .filter(
- // Releasing media
- (media: Media) =>
- media.status === 'RELEASING' &&
- (media.mediaListEntry || { status: 'DROPPED' }).status !=
- ($settings.displayPausedMedia ? '' : 'PAUSED') &&
- (media.mediaListEntry || { progress: 0 }).progress >=
- ($settings.displayNotStarted === true ? 0 : 1) &&
- (media.mediaListEntry || { status: 'DROPPED' }).status !== 'DROPPED'
- )
- .filter((media: Media) =>
- // Outdated media
- hasDueEpisodes(media)
- )
- .map((media: Media) => {
- if (hasNoAiredEpisodes(media)) media.nextAiringEpisode = { episode: -1 };
-
- return media;
- });
+ dueAnime.sort((a: Media, b: Media) => {
+ switch ($settings.displayAnimeSort) {
+ case 'difference': {
+ const difference = (anime: Media) =>
+ (anime.nextAiringEpisode?.episode === -1
+ ? 99999
+ : anime.nextAiringEpisode?.episode || -1) -
+ (anime.mediaListEntry || { progress: 0 }).progress;
- if (!displayUnresolved)
- dueAnime = dueAnime.filter((media: Media) => media.nextAiringEpisode?.episode !== -1);
-
- dueAnime.sort((a: Media, b: Media) => {
- switch ($settings.displayAnimeSort) {
- case 'difference': {
- const difference = (anime: Media) =>
- (anime.nextAiringEpisode?.episode === -1
- ? 99999
- : anime.nextAiringEpisode?.episode || -1) -
- (anime.mediaListEntry || { progress: 0 }).progress;
-
- return difference(a) - difference(b);
- }
-
- case 'end_date':
- return (
- new Date(a.endDate.year, a.endDate.month - 1).getTime() -
- new Date(b.endDate.year, b.endDate.month - 1).getTime()
- );
-
- case 'start_date':
- return (
- new Date(a.startDate.year, a.startDate.month - 1).getTime() -
- new Date(b.startDate.year, b.startDate.month - 1).getTime()
- );
-
- case 'time_remaining':
- return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999);
+ return difference(a) - difference(b);
}
- });
- if (!endTime || endTime === -1) endTime = performance.now() - startTime;
+ case 'end_date':
+ return (
+ new Date(a.endDate.year, a.endDate.month - 1).getTime() -
+ new Date(b.endDate.year, b.endDate.month - 1).getTime()
+ );
+
+ case 'start_date':
+ return (
+ new Date(a.startDate.year, a.startDate.month - 1).getTime() -
+ new Date(b.startDate.year, b.startDate.month - 1).getTime()
+ );
+
+ case 'time_remaining':
+ return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999);
+ }
+ });
+
+ if (!endTime || endTime === -1) endTime = performance.now() - startTime;
- return dueAnime;
- };
+ return dueAnime;
+};
</script>
<AnimeList {endTime} {cleanMedia} bind:animeLists {user} title={$locale().lists.due.episodes} />
diff --git a/src/lib/List/Anime/DueIndexColumn.svelte b/src/lib/List/Anime/DueIndexColumn.svelte
index 920035bc..6ffc17be 100644
--- a/src/lib/List/Anime/DueIndexColumn.svelte
+++ b/src/lib/List/Anime/DueIndexColumn.svelte
@@ -1,18 +1,18 @@
<script lang="ts">
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import locale from '$stores/locale';
- import ListTitle from '../ListTitle.svelte';
- import AnimeList from '$lib/List/Anime/DueAnimeList.svelte';
- import { onMount } from 'svelte';
- import stateBin from '$stores/stateBin';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import locale from '$stores/locale';
+import ListTitle from '../ListTitle.svelte';
+import AnimeList from '$lib/List/Anime/DueAnimeList.svelte';
+import { onMount } from 'svelte';
+import stateBin from '$stores/stateBin';
- export let userIdentity: { id: number };
- export let user: AniListAuthorisation;
+export let userIdentity: { id: number };
+export let user: AniListAuthorisation;
- onMount(() => {
- $stateBin.dueAnimeListOpen ??= true;
- });
+onMount(() => {
+ $stateBin.dueAnimeListOpen ??= true;
+});
</script>
<details bind:open={$stateBin.dueAnimeListOpen} class="list list-due">
diff --git a/src/lib/List/Anime/PlaceholderList.svelte b/src/lib/List/Anime/PlaceholderList.svelte
index 1f701d79..4ac0d8ff 100644
--- a/src/lib/List/Anime/PlaceholderList.svelte
+++ b/src/lib/List/Anime/PlaceholderList.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import settings from '$stores/settings';
- import ListTitle from '../ListTitle.svelte';
- import type { Title } from '../mediaTitle';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import settings from '$stores/settings';
+import ListTitle from '../ListTitle.svelte';
+import type { Title } from '../mediaTitle';
- export let title: Title;
- export let count = 8;
+export let title: Title;
+export let count = 8;
</script>
<ListTitle {title} />
diff --git a/src/lib/List/Anime/UpcomingAnimeList.svelte b/src/lib/List/Anime/UpcomingAnimeList.svelte
index a2cc963d..109584f0 100644
--- a/src/lib/List/Anime/UpcomingAnimeList.svelte
+++ b/src/lib/List/Anime/UpcomingAnimeList.svelte
@@ -1,88 +1,86 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { onMount } from 'svelte';
- import anime from '$stores/anime';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import AnimeList from './AnimeListTemplate.svelte';
- import settings from '$stores/settings';
- import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
- import { addNotification } from '$lib/Notification/store';
- import locale from '$stores/locale';
- import identity from '$stores/identity';
- import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match';
- import { hasDueEpisodes, hasNoAiredEpisodes } from '$lib/Media/Anime/Airing/classify';
- import revalidateAnime from '$stores/revalidateAnime';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { onMount } from 'svelte';
+import anime from '$stores/anime';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import AnimeList from './AnimeListTemplate.svelte';
+import settings from '$stores/settings';
+import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+import { addNotification } from '$lib/Notification/store';
+import locale from '$stores/locale';
+import identity from '$stores/identity';
+import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match';
+import { hasDueEpisodes, hasNoAiredEpisodes } from '$lib/Media/Anime/Airing/classify';
+import revalidateAnime from '$stores/revalidateAnime';
- export let user: AniListAuthorisation;
- let animeLists: Promise<Media[]>;
- let startTime: number;
- let endTime: number;
+export let user: AniListAuthorisation;
+let animeLists: Promise<Media[]>;
+let startTime: number;
+let endTime: number;
- onMount(async () => {
- startTime = performance.now();
- animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
- addNotification,
- notificationType: 'Upcoming Episodes'
- });
+onMount(async () => {
+ startTime = performance.now();
+ animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ addNotification,
+ notificationType: 'Upcoming Episodes'
});
+});
- const cleanMedia = (
- anime: Media[],
- displayUnresolved: boolean,
- subsPlease: SubsPlease | null,
- plannedOnly = true
- ) => {
- if (anime === undefined) return [];
+const cleanMedia = (
+ anime: Media[],
+ displayUnresolved: boolean,
+ subsPlease: SubsPlease | null,
+ plannedOnly = true
+) => {
+ if (anime === undefined) return [];
- const filterAnime = (status: 'RELEASING' | 'NOT_YET_RELEASED') =>
- anime
- .filter((media: Media) => media.status === status && media.nextAiringEpisode !== null)
- .map((media) => injectAiringTime(media, subsPlease))
- .filter(
- (media: Media) =>
- // Outdated media
- ($settings.displayPlannedAnime ? media.mediaListEntry?.status === 'PLANNING' : false) ||
- !hasDueEpisodes(media)
+ const filterAnime = (status: 'RELEASING' | 'NOT_YET_RELEASED') =>
+ anime
+ .filter((media: Media) => media.status === status && media.nextAiringEpisode !== null)
+ .map((media) => injectAiringTime(media, subsPlease))
+ .filter(
+ (media: Media) =>
+ // Outdated media
+ ($settings.displayPlannedAnime ? media.mediaListEntry?.status === 'PLANNING' : false) ||
+ !hasDueEpisodes(media)
+ )
+ .map((media: Media) => {
+ // Adjust for planned anime
+ if (
+ ($settings.displayPlannedAnime ? media.episodes !== 1 : true) &&
+ hasNoAiredEpisodes(media)
)
- .map((media: Media) => {
- // Adjust for planned anime
- if (
- ($settings.displayPlannedAnime ? media.episodes !== 1 : true) &&
- hasNoAiredEpisodes(media)
- )
- media.nextAiringEpisode = { episode: -1 };
+ media.nextAiringEpisode = { episode: -1 };
- return media;
- });
- let upcomingAnime = filterAnime(plannedOnly ? 'NOT_YET_RELEASED' : 'RELEASING');
+ return media;
+ });
+ let upcomingAnime = filterAnime(plannedOnly ? 'NOT_YET_RELEASED' : 'RELEASING');
- if (!displayUnresolved)
- upcomingAnime = upcomingAnime.filter(
- (media: Media) => media.nextAiringEpisode?.episode !== -1
- );
+ if (!displayUnresolved)
+ upcomingAnime = upcomingAnime.filter((media: Media) => media.nextAiringEpisode?.episode !== -1);
- upcomingAnime.sort(
- (a: Media, b: Media) =>
- (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999)
- );
+ upcomingAnime.sort(
+ (a: Media, b: Media) =>
+ (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999)
+ );
- if (!endTime) endTime = performance.now() - startTime;
+ if (!endTime) endTime = performance.now() - startTime;
- return upcomingAnime;
- };
+ return upcomingAnime;
+};
- $: {
- if ($revalidateAnime) {
- $revalidateAnime = false;
- $lastPruneTimes.anime = -1;
- animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
- addNotification,
- notificationType: 'Upcoming Episodes'
- });
- }
+$: {
+ if ($revalidateAnime) {
+ $revalidateAnime = false;
+ $lastPruneTimes.anime = -1;
+ animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ addNotification,
+ notificationType: 'Upcoming Episodes'
+ });
}
+}
</script>
<AnimeList
diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte
index 4f628c3c..08a7ef83 100644
--- a/src/lib/List/CleanGrid.svelte
+++ b/src/lib/List/CleanGrid.svelte
@@ -1,24 +1,24 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
- import { outboundLink } from '$lib/Media/links';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import settings from '$stores/settings';
- import { mediaTitle } from './mediaTitle';
- import './covers.css';
+import type { Media } from '$lib/Data/AniList/media';
+import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
+import { outboundLink } from '$lib/Media/links';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import settings from '$stores/settings';
+import { mediaTitle } from './mediaTitle';
+import './covers.css';
- export let media: Media[];
- export let dummy = false;
- export let type: 'anime' | 'manga';
- export let upcoming = false;
- export let notYetReleased = false;
- export let reverseSort = false;
- export let limit: number | undefined = undefined;
+export let media: Media[];
+export let dummy = false;
+export let type: 'anime' | 'manga';
+export let upcoming = false;
+export let notYetReleased = false;
+export let reverseSort = false;
+export let limit: number | undefined = undefined;
- let uniqueID = new Date().getTime();
+let uniqueID = new Date().getTime();
- $: sortedMedia = reverseSort ? [...media].reverse() : media;
- $: processedMedia = limit !== undefined ? sortedMedia.slice(0, limit) : sortedMedia;
+$: sortedMedia = reverseSort ? [...media].reverse() : media;
+$: processedMedia = limit !== undefined ? sortedMedia.slice(0, limit) : sortedMedia;
</script>
<div
diff --git a/src/lib/List/CleanList.svelte b/src/lib/List/CleanList.svelte
index bf8c44ff..377b9302 100644
--- a/src/lib/List/CleanList.svelte
+++ b/src/lib/List/CleanList.svelte
@@ -1,18 +1,18 @@
<script lang="ts">
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import type { Media } from '$lib/Data/AniList/media';
- import { outboundLink } from '$lib/Media/links';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import settings from '$stores/settings';
+import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+import type { Media } from '$lib/Data/AniList/media';
+import { outboundLink } from '$lib/Media/links';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import settings from '$stores/settings';
- export let media: Media[];
- export let type: 'anime' | 'manga';
- export let upcoming = false;
- export let notYetReleased = false;
- export let lastUpdatedMedia: number;
- export let reverseSort = false;
+export let media: Media[];
+export let type: 'anime' | 'manga';
+export let upcoming = false;
+export let notYetReleased = false;
+export let lastUpdatedMedia: number;
+export let reverseSort = false;
- $: processedMedia = reverseSort ? [...media].reverse() : media;
+$: processedMedia = reverseSort ? [...media].reverse() : media;
</script>
<ul>
diff --git a/src/lib/List/ListTitle.svelte b/src/lib/List/ListTitle.svelte
index 21013b52..71b5e649 100644
--- a/src/lib/List/ListTitle.svelte
+++ b/src/lib/List/ListTitle.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
- import tooltip from '$lib/Tooltip/tooltip';
- import type { Title } from './mediaTitle';
+import tooltip from '$lib/Tooltip/tooltip';
+import type { Title } from './mediaTitle';
- export let time: number | undefined = undefined;
- export let count = -1337;
- export let title: Title = {
- title: 'Media List',
- hint: 'This is a media list.'
- };
- export let progress: undefined | number = undefined;
- export let hideTime = false;
- export let hideCount = false;
+export let time: number | undefined = undefined;
+export let count = -1337;
+export let title: Title = {
+ title: 'Media List',
+ hint: 'This is a media list.'
+};
+export let progress: undefined | number = undefined;
+export let hideTime = false;
+export let hideCount = false;
</script>
<summary>
diff --git a/src/lib/List/Manga/CleanMangaList.svelte b/src/lib/List/Manga/CleanMangaList.svelte
index bba0fb08..8964c15e 100644
--- a/src/lib/List/Manga/CleanMangaList.svelte
+++ b/src/lib/List/Manga/CleanMangaList.svelte
@@ -1,83 +1,79 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import type { Media } from '$lib/Data/AniList/media';
- import Error from '$lib/Error/RateLimited.svelte';
- import { volumeCount } from '$lib/Media/Manga/volumes';
- import settings from '$stores/settings';
- import ListTitle from '../ListTitle.svelte';
- import { onMount } from 'svelte';
- import root from '$lib/Utility/root';
- import locale from '$stores/locale';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import { browser } from '$app/environment';
- import proxy from '$lib/Utility/proxy';
- import '../covers.css';
- import CleanGrid from '../CleanGrid.svelte';
- import CleanList from '../CleanList.svelte';
- import stateBin from '$stores/stateBin';
- import localforage from 'localforage';
- import MediaRoulette from '../MediaRoulette.svelte';
-
- export let media: Media[];
- export let cleanCache: () => void;
- export let endTime: number;
- export let lastUpdatedMedia: number;
- export let updateMedia: (
- id: number,
- progress: number | undefined,
- media: Media[]
- ) => Promise<void>;
- export let pendingUpdate: number | null;
- export let due: boolean;
- export let rateLimited: boolean;
- export let authorised: boolean;
- export let dummy = false;
- export let disableFilter = false;
- export let limit: number | undefined = undefined;
-
- let showRoulette = false;
- let serviceStatusResponse: Promise<Response>;
- let totalEpisodeDueCount = media
- .map((manga) => {
- if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes) return 1;
-
- if (!due && !$settings.displayTotalEpisodes) return 1;
-
- return (manga.episodes || 1) - (manga.mediaListEntry?.progress || 0);
- })
- .reduce((a, b) => a + b, 0);
- const lists = Array.from(
- new Set(
- media
- .flatMap((m) => Object.entries(m.mediaListEntry?.customLists ?? {}))
- .filter(([_key, value]) => value)
- .map(([key]) => key)
- )
- );
- const filterKind = due ? 'due' : 'completed';
- const filterKey = `${filterKind}MangaListFilter`;
-
- $: selectedList = disableFilter ? 'All' : ($stateBin[filterKey] as string) || 'All';
-
- $: filteredMedia =
- selectedList === 'All' || !$settings.displayMediaListFilter
- ? media
- : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);
-
- onMount(async () => {
- serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping'));
-
- if (browser)
- await localforage.setItem(
- `last${due ? '' : 'Completed'}MangaListLength`,
- media.length.toString()
- );
- });
-
- const increment = (manga: Media) => {
- if (!(pendingUpdate === manga.id || dummy))
- updateMedia(manga.id, manga.mediaListEntry?.progress, media);
- };
+import Spacer from '$lib/Layout/Spacer.svelte';
+import type { Media } from '$lib/Data/AniList/media';
+import Error from '$lib/Error/RateLimited.svelte';
+import { volumeCount } from '$lib/Media/Manga/volumes';
+import settings from '$stores/settings';
+import ListTitle from '../ListTitle.svelte';
+import { onMount } from 'svelte';
+import root from '$lib/Utility/root';
+import locale from '$stores/locale';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import { browser } from '$app/environment';
+import proxy from '$lib/Utility/proxy';
+import '../covers.css';
+import CleanGrid from '../CleanGrid.svelte';
+import CleanList from '../CleanList.svelte';
+import stateBin from '$stores/stateBin';
+import localforage from 'localforage';
+import MediaRoulette from '../MediaRoulette.svelte';
+
+export let media: Media[];
+export let cleanCache: () => void;
+export let endTime: number;
+export let lastUpdatedMedia: number;
+export let updateMedia: (id: number, progress: number | undefined, media: Media[]) => Promise<void>;
+export let pendingUpdate: number | null;
+export let due: boolean;
+export let rateLimited: boolean;
+export let authorised: boolean;
+export let dummy = false;
+export let disableFilter = false;
+export let limit: number | undefined = undefined;
+
+let showRoulette = false;
+let serviceStatusResponse: Promise<Response>;
+let totalEpisodeDueCount = media
+ .map((manga) => {
+ if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes) return 1;
+
+ if (!due && !$settings.displayTotalEpisodes) return 1;
+
+ return (manga.episodes || 1) - (manga.mediaListEntry?.progress || 0);
+ })
+ .reduce((a, b) => a + b, 0);
+const lists = Array.from(
+ new Set(
+ media
+ .flatMap((m) => Object.entries(m.mediaListEntry?.customLists ?? {}))
+ .filter(([_key, value]) => value)
+ .map(([key]) => key)
+ )
+);
+const filterKind = due ? 'due' : 'completed';
+const filterKey = `${filterKind}MangaListFilter`;
+
+$: selectedList = disableFilter ? 'All' : ($stateBin[filterKey] as string) || 'All';
+
+$: filteredMedia =
+ selectedList === 'All' || !$settings.displayMediaListFilter
+ ? media
+ : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);
+
+onMount(async () => {
+ serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping'));
+
+ if (browser)
+ await localforage.setItem(
+ `last${due ? '' : 'Completed'}MangaListLength`,
+ media.length.toString()
+ );
+});
+
+const increment = (manga: Media) => {
+ if (!(pendingUpdate === manga.id || dummy))
+ updateMedia(manga.id, manga.mediaListEntry?.progress, media);
+};
</script>
{#if authorised}
diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte
index f549496d..740a1341 100644
--- a/src/lib/List/Manga/MangaListTemplate.svelte
+++ b/src/lib/List/Manga/MangaListTemplate.svelte
@@ -1,271 +1,262 @@
<script lang="ts">
- import sampleManga from '$lib/Data/Static/SampleMedia/manga.json';
- import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { onDestroy, onMount } from 'svelte';
- import { chapterCount } from '$lib/Media/Manga/chapters';
- import { pruneAllManga } from '$lib/Media/Manga/cache';
- import manga from '$stores/manga';
- import { database } from '$lib/Database/IDB/chapters';
- import settings from '$stores/settings';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import ListTitle from '../ListTitle.svelte';
- import Error from '$lib/Error/RateLimited.svelte';
- import CleanMangaList from './CleanMangaList.svelte';
- import { incrementMediaProgress } from '$lib/Media/Anime/cache';
- import { addNotification } from '$lib/Notification/store';
- import { options } from '$lib/Notification/options';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import locale from '$stores/locale';
- import { browser } from '$app/environment';
- import identity from '$stores/identity';
- import privilegedUser from '$lib/Utility/privilegedUser';
- import localforage from 'localforage';
-
- export let user: AniListAuthorisation = {
- accessToken: '',
- refreshToken: '',
- expiresIn: 0,
- tokenType: ''
- };
- export let displayUnresolved: boolean;
- export let due: boolean;
- export let dummy = $settings.debugDummyLists || false;
- export let dummyCount = 7;
- export let disableFilter = false;
- export let limit: number | undefined = undefined;
- const authorised = privilegedUser($identity.id);
- let mangaLists: Promise<Media[]>;
- let startTime: number;
- let endTime: number;
- let lastUpdatedMedia = -1;
- let previousMangaList: Media[];
- let pendingUpdate: number | null = null;
- let progress = 0;
- let rateLimited = false;
- let forceFlag = false;
- let lastListSize = 5;
- let keyCacher: ReturnType<typeof setInterval> | undefined;
- let keyCacheMinutes = -1;
-
- const restartKeyCacher = (cacheMinutes: number) => {
- if (keyCacher) clearInterval(keyCacher);
-
- keyCacheMinutes = cacheMinutes;
- keyCacher = setInterval(
- () => {
- startTime = performance.now();
- endTime = -1;
- mangaLists = mediaListCollection(
- user,
- $identity,
- Type.Manga,
- $manga,
- $lastPruneTimes.manga,
- {
- addNotification
- }
- );
- },
- cacheMinutes * 1000 * 60
- );
- };
-
- onMount(async () => {
- restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
-
- if (browser) {
- const lastStoredList = (await localforage.getItem(
- `last${due ? '' : 'Completed'}MangaListLength`
- )) as number | null;
-
- if (lastStoredList) lastListSize = parseInt(String(lastStoredList));
- }
-
- startTime = performance.now();
-
- if (dummy) {
- // Use deterministic selection for consistent display
- const filtered = sampleManga.filter(
- (manga) =>
- manga.chapters &&
- !manga.tags.some((tag) => tag.name === 'Nudity') &&
- !manga.tags.some((tag) => tag.name === 'Rape') &&
- !manga.tags.some((tag) => tag.name === 'Tragedy') &&
- !manga.tags.some((tag) => tag.name === 'Bondage') &&
- !manga.genres.some((genre) => genre === 'Hentai') &&
- manga.genres.some((genre) => genre === 'Comedy') &&
- manga.status !== 'NOT_YET_RELEASED'
- );
- mangaLists = Promise.resolve(
- filtered.slice(0, dummyCount).map((manga) => {
- manga.status = 'FINISHED';
- manga.episodes = Math.floor((manga.chapters || 10) * 0.7) as unknown as null;
- manga.mediaListEntry.progress = Math.floor((manga.episodes || 5) * 0.5) + 1;
- return manga;
- }) as unknown as Media[]
- );
- } else {
+import sampleManga from '$lib/Data/Static/SampleMedia/manga.json';
+import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { onDestroy, onMount } from 'svelte';
+import { chapterCount } from '$lib/Media/Manga/chapters';
+import { pruneAllManga } from '$lib/Media/Manga/cache';
+import manga from '$stores/manga';
+import { database } from '$lib/Database/IDB/chapters';
+import settings from '$stores/settings';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import ListTitle from '../ListTitle.svelte';
+import Error from '$lib/Error/RateLimited.svelte';
+import CleanMangaList from './CleanMangaList.svelte';
+import { incrementMediaProgress } from '$lib/Media/Anime/cache';
+import { addNotification } from '$lib/Notification/store';
+import { options } from '$lib/Notification/options';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import locale from '$stores/locale';
+import { browser } from '$app/environment';
+import identity from '$stores/identity';
+import privilegedUser from '$lib/Utility/privilegedUser';
+import localforage from 'localforage';
+
+export let user: AniListAuthorisation = {
+ accessToken: '',
+ refreshToken: '',
+ expiresIn: 0,
+ tokenType: ''
+};
+export let displayUnresolved: boolean;
+export let due: boolean;
+export let dummy = $settings.debugDummyLists || false;
+export let dummyCount = 7;
+export let disableFilter = false;
+export let limit: number | undefined = undefined;
+const authorised = privilegedUser($identity.id);
+let mangaLists: Promise<Media[]>;
+let startTime: number;
+let endTime: number;
+let lastUpdatedMedia = -1;
+let previousMangaList: Media[];
+let pendingUpdate: number | null = null;
+let progress = 0;
+let rateLimited = false;
+let forceFlag = false;
+let lastListSize = 5;
+let keyCacher: ReturnType<typeof setInterval> | undefined;
+let keyCacheMinutes = -1;
+
+const restartKeyCacher = (cacheMinutes: number) => {
+ if (keyCacher) clearInterval(keyCacher);
+
+ keyCacheMinutes = cacheMinutes;
+ keyCacher = setInterval(
+ () => {
+ startTime = performance.now();
+ endTime = -1;
mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
addNotification
});
- }
- });
+ },
+ cacheMinutes * 1000 * 60
+ );
+};
+
+onMount(async () => {
+ restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
+
+ if (browser) {
+ const lastStoredList = (await localforage.getItem(
+ `last${due ? '' : 'Completed'}MangaListLength`
+ )) as number | null;
+
+ if (lastStoredList) lastListSize = parseInt(String(lastStoredList));
+ }
+
+ startTime = performance.now();
+
+ if (dummy) {
+ // Use deterministic selection for consistent display
+ const filtered = sampleManga.filter(
+ (manga) =>
+ manga.chapters &&
+ !manga.tags.some((tag) => tag.name === 'Nudity') &&
+ !manga.tags.some((tag) => tag.name === 'Rape') &&
+ !manga.tags.some((tag) => tag.name === 'Tragedy') &&
+ !manga.tags.some((tag) => tag.name === 'Bondage') &&
+ !manga.genres.some((genre) => genre === 'Hentai') &&
+ manga.genres.some((genre) => genre === 'Comedy') &&
+ manga.status !== 'NOT_YET_RELEASED'
+ );
+ mangaLists = Promise.resolve(
+ filtered.slice(0, dummyCount).map((manga) => {
+ manga.status = 'FINISHED';
+ manga.episodes = Math.floor((manga.chapters || 10) * 0.7) as unknown as null;
+ manga.mediaListEntry.progress = Math.floor((manga.episodes || 5) * 0.5) + 1;
+ return manga;
+ }) as unknown as Media[]
+ );
+ } else {
+ mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
+ addNotification
+ });
+ }
+});
- $: if (keyCacher && keyCacheMinutes !== Math.max($settings.cacheMangaMinutes, 5))
- restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
+$: if (keyCacher && keyCacheMinutes !== Math.max($settings.cacheMangaMinutes, 5))
+ restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
- onDestroy(() => {
- if (keyCacher) clearInterval(keyCacher);
- });
+onDestroy(() => {
+ if (keyCacher) clearInterval(keyCacher);
+});
- const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => {
- progress = 0;
+const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => {
+ progress = 0;
- if (manga && dummy) return manga;
+ if (manga && dummy) return manga;
- if (manga === undefined) return [];
+ if (manga === undefined) return [];
- if (!authorised && (await database.chapters.toArray()).length <= 0 && !force) return [];
+ if (!authorised && (await database.chapters.toArray()).length <= 0 && !force) return [];
- if (authorised) {
- let refreshing = false;
+ if (authorised) {
+ let refreshing = false;
- if ($lastPruneTimes.chapters === 1) {
- refreshing = true;
+ if ($lastPruneTimes.chapters === 1) {
+ refreshing = true;
- lastPruneTimes.setKey('chapters', new Date().getTime());
- } else {
- const currentDate = new Date();
-
- if (
- (currentDate.getTime() - $lastPruneTimes.chapters) / 1000 / 60 >
- Math.max($settings.cacheMangaMinutes, 5)
- ) {
- refreshing = true;
-
- lastPruneTimes.setKey('chapters', currentDate.getTime());
- (async () => {
- await database.chapters.bulkDelete(
- (await database.chapters.toArray()).map((m) => m.id)
- );
- })();
- }
- }
+ lastPruneTimes.setKey('chapters', new Date().getTime());
+ } else {
+ const currentDate = new Date();
- if (refreshing) {
- addNotification(
- options({
- heading: 'Manga',
- description: 'Re-freshing manga data ...'
- })
- );
+ if (
+ (currentDate.getTime() - $lastPruneTimes.chapters) / 1000 / 60 >
+ Math.max($settings.cacheMangaMinutes, 5)
+ ) {
+ refreshing = true;
+
+ lastPruneTimes.setKey('chapters', currentDate.getTime());
+ (async () => {
+ await database.chapters.bulkDelete((await database.chapters.toArray()).map((m) => m.id));
+ })();
}
}
- const releasingMedia = manga.filter(
- (media: Media) =>
- (due ? media.status === 'RELEASING' : media.status === 'FINISHED') &&
- (media.mediaListEntry || { status: 'DROPPED' }).status !==
- ($settings.displayPausedMedia ? '' : 'PAUSED') &&
- (media.mediaListEntry || { status: 'DROPPED' }).status !== 'DROPPED' &&
- (media.mediaListEntry || { progress: 0 }).progress >=
- ($settings.displayNotStarted === true ? 0 : 1)
- );
- let finalMedia = releasingMedia;
- const progressStep = 100 / finalMedia.length / 2;
- const chapterPromises = finalMedia.map((m: Media) =>
- database.chapters.get(m.id).then((c) => {
- if (progress < 100) progress += progressStep;
-
- if (!due) return new Promise((resolve) => resolve(m.chapters)) as Promise<number | null>;
-
- if (c !== undefined) return chapterCount($identity, m, $settings.calculateGuessingDisabled);
- else {
- // A = On 1 second interval,
- // B = a maximum of 5 requests per second are allowed.
- // C = chapterCount makes 3 requests per call.
- // F = A / (B / C) = 0.6 seconds
- return new Promise((resolve) => setTimeout(resolve, 600)).then(() =>
- chapterCount($identity, m, $settings.calculateGuessingDisabled)
- );
- }
- })
- );
- const chapterCounts: (number | null)[] = [];
-
- for (let i = 0; i < chapterPromises.length; i++) {
- const count = await chapterPromises[i];
+ if (refreshing) {
+ addNotification(
+ options({
+ heading: 'Manga',
+ description: 'Re-freshing manga data ...'
+ })
+ );
+ }
+ }
+
+ const releasingMedia = manga.filter(
+ (media: Media) =>
+ (due ? media.status === 'RELEASING' : media.status === 'FINISHED') &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status !==
+ ($settings.displayPausedMedia ? '' : 'PAUSED') &&
+ (media.mediaListEntry || { status: 'DROPPED' }).status !== 'DROPPED' &&
+ (media.mediaListEntry || { progress: 0 }).progress >=
+ ($settings.displayNotStarted === true ? 0 : 1)
+ );
+ let finalMedia = releasingMedia;
+ const progressStep = 100 / finalMedia.length / 2;
+ const chapterPromises = finalMedia.map((m: Media) =>
+ database.chapters.get(m.id).then((c) => {
+ if (progress < 100) progress += progressStep;
- if (count === -22) {
- rateLimited = true;
+ if (!due) return new Promise((resolve) => resolve(m.chapters)) as Promise<number | null>;
- break;
+ if (c !== undefined) return chapterCount($identity, m, $settings.calculateGuessingDisabled);
+ else {
+ // A = On 1 second interval,
+ // B = a maximum of 5 requests per second are allowed.
+ // C = chapterCount makes 3 requests per call.
+ // F = A / (B / C) = 0.6 seconds
+ return new Promise((resolve) => setTimeout(resolve, 600)).then(() =>
+ chapterCount($identity, m, $settings.calculateGuessingDisabled)
+ );
}
+ })
+ );
+ const chapterCounts: (number | null)[] = [];
- chapterCounts.push(count);
+ for (let i = 0; i < chapterPromises.length; i++) {
+ const count = await chapterPromises[i];
- if (progress < 100) progress += progressStep;
+ if (count === -22) {
+ rateLimited = true;
+
+ break;
}
- finalMedia.forEach((m: Media, i) => (m.episodes = chapterCounts[i] || -1337));
+ chapterCounts.push(count);
- if (!displayUnresolved) finalMedia = finalMedia.filter((m: Media) => m.episodes !== -1337);
+ if (progress < 100) progress += progressStep;
+ }
- finalMedia.sort(
- (a: Media, b: Media) =>
- (a.episodes || 9999) -
- (a.mediaListEntry || { progress: 0 }).progress -
- ((b.episodes || 9999) - (b.mediaListEntry || { progress: 0 }).progress)
- );
+ finalMedia.forEach((m: Media, i) => (m.episodes = chapterCounts[i] || -1337));
- finalMedia = finalMedia.filter(
- (item, index, array) =>
- array.findIndex((i) => i.id === item.id) === index &&
- (item.episodes === -1337 && displayUnresolved
- ? true
- : (item.mediaListEntry?.progress || 0) <
- ($settings.calculateChaptersRoundedDown === true
- ? Math.floor(item.episodes)
- : item.episodes))
- );
+ if (!displayUnresolved) finalMedia = finalMedia.filter((m: Media) => m.episodes !== -1337);
- if (!endTime || endTime === -1) endTime = performance.now() - startTime;
+ finalMedia.sort(
+ (a: Media, b: Media) =>
+ (a.episodes || 9999) -
+ (a.mediaListEntry || { progress: 0 }).progress -
+ ((b.episodes || 9999) - (b.mediaListEntry || { progress: 0 }).progress)
+ );
- return finalMedia;
- };
+ finalMedia = finalMedia.filter(
+ (item, index, array) =>
+ array.findIndex((i) => i.id === item.id) === index &&
+ (item.episodes === -1337 && displayUnresolved
+ ? true
+ : (item.mediaListEntry?.progress || 0) <
+ ($settings.calculateChaptersRoundedDown === true
+ ? Math.floor(item.episodes)
+ : item.episodes))
+ );
- const updateMedia = async (id: number, progress: number | undefined, media: Media[]) => {
- pendingUpdate = id;
- lastUpdatedMedia = id;
+ if (!endTime || endTime === -1) endTime = performance.now() - startTime;
- await database.chapters.delete(id);
+ return finalMedia;
+};
- incrementMediaProgress(id, progress, user, () => {
- previousMangaList = media;
+const updateMedia = async (id: number, progress: number | undefined, media: Media[]) => {
+ pendingUpdate = id;
+ lastUpdatedMedia = id;
- const foundEntry = media.find((m) => m.id === id);
+ await database.chapters.delete(id);
- if (foundEntry && foundEntry.mediaListEntry)
- foundEntry.mediaListEntry.progress = (progress || 0) + 1;
+ incrementMediaProgress(id, progress, user, () => {
+ previousMangaList = media;
- mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
- forcePrune: true
- });
- pendingUpdate = null;
+ const foundEntry = media.find((m) => m.id === id);
+
+ if (foundEntry && foundEntry.mediaListEntry)
+ foundEntry.mediaListEntry.progress = (progress || 0) + 1;
+
+ mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
+ forcePrune: true
});
- };
+ pendingUpdate = null;
+ });
+};
- const cleanCache = () => {
- startTime = performance.now();
- endTime = -1;
+const cleanCache = () => {
+ startTime = performance.now();
+ endTime = -1;
- pruneAllManga().then(() => {
- mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
- forcePrune: true
- });
+ pruneAllManga().then(() => {
+ mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
+ forcePrune: true
});
- };
+ });
+};
</script>
{#await mangaLists}
diff --git a/src/lib/List/MediaRoulette.svelte b/src/lib/List/MediaRoulette.svelte
index 4b498d4f..b4a6d527 100644
--- a/src/lib/List/MediaRoulette.svelte
+++ b/src/lib/List/MediaRoulette.svelte
@@ -1,85 +1,85 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
- import { outboundLink } from '$lib/Media/links';
- import settings from '$stores/settings';
- import { mediaTitle } from './mediaTitle';
-
- interface Props {
- media: Media[];
- type: 'anime' | 'manga';
- onClose: () => void;
- spinDuration?: number;
- }
-
- let { media, type, onClose, spinDuration = 2 }: Props = $props();
- let isSpinning = $state(false);
- let selectedIndex = $state(0);
- let displayIndex = $state(0);
- let spinTimeout: ReturnType<typeof setTimeout> | null = $state(null);
- let showResult = $state(false);
- let isClosing = $state(false);
- let currentMedia = $derived(media[displayIndex]);
-
- const startRoulette = () => {
- if (media.length === 0 || isSpinning) return;
-
- isSpinning = true;
- showResult = false;
- selectedIndex = Math.floor(Math.random() * media.length);
-
- const startTime = Date.now();
- const durationMs = spinDuration * 1000;
- const minSpeed = 50;
- const maxSpeed = 350;
- const slowdownStart = 0.8;
-
- const spin = () => {
- displayIndex = (displayIndex + 1) % media.length;
-
- const elapsed = Date.now() - startTime;
- const progress = Math.min(elapsed / durationMs, 1);
- let speed = minSpeed;
-
- if (progress > slowdownStart) {
- const slowdownProgress = (progress - slowdownStart) / (1 - slowdownStart);
-
- speed = minSpeed + slowdownProgress * (maxSpeed - minSpeed);
- }
-
- if (progress >= 1 && displayIndex === selectedIndex) {
- spinTimeout = null;
- isSpinning = false;
- showResult = true;
+import type { Media } from '$lib/Data/AniList/media';
+import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
+import { outboundLink } from '$lib/Media/links';
+import settings from '$stores/settings';
+import { mediaTitle } from './mediaTitle';
+
+interface Props {
+ media: Media[];
+ type: 'anime' | 'manga';
+ onClose: () => void;
+ spinDuration?: number;
+}
+
+let { media, type, onClose, spinDuration = 2 }: Props = $props();
+let isSpinning = $state(false);
+let selectedIndex = $state(0);
+let displayIndex = $state(0);
+let spinTimeout: ReturnType<typeof setTimeout> | null = $state(null);
+let showResult = $state(false);
+let isClosing = $state(false);
+let currentMedia = $derived(media[displayIndex]);
+
+const startRoulette = () => {
+ if (media.length === 0 || isSpinning) return;
+
+ isSpinning = true;
+ showResult = false;
+ selectedIndex = Math.floor(Math.random() * media.length);
+
+ const startTime = Date.now();
+ const durationMs = spinDuration * 1000;
+ const minSpeed = 50;
+ const maxSpeed = 350;
+ const slowdownStart = 0.8;
+
+ const spin = () => {
+ displayIndex = (displayIndex + 1) % media.length;
+
+ const elapsed = Date.now() - startTime;
+ const progress = Math.min(elapsed / durationMs, 1);
+ let speed = minSpeed;
+
+ if (progress > slowdownStart) {
+ const slowdownProgress = (progress - slowdownStart) / (1 - slowdownStart);
+
+ speed = minSpeed + slowdownProgress * (maxSpeed - minSpeed);
+ }
- return;
- }
+ if (progress >= 1 && displayIndex === selectedIndex) {
+ spinTimeout = null;
+ isSpinning = false;
+ showResult = true;
- spinTimeout = setTimeout(spin, speed);
- };
+ return;
+ }
- spinTimeout = setTimeout(spin, minSpeed);
+ spinTimeout = setTimeout(spin, speed);
};
- const handleClose = () => {
- if (isClosing) return;
+ spinTimeout = setTimeout(spin, minSpeed);
+};
- if (spinTimeout) {
- clearTimeout(spinTimeout);
+const handleClose = () => {
+ if (isClosing) return;
- spinTimeout = null;
- }
+ if (spinTimeout) {
+ clearTimeout(spinTimeout);
- isSpinning = false;
- showResult = false;
- isClosing = true;
+ spinTimeout = null;
+ }
- setTimeout(() => onClose(), 200);
- };
+ isSpinning = false;
+ showResult = false;
+ isClosing = true;
- const handleOverlayClick = (e: MouseEvent) => {
- if (e.target === e.currentTarget) handleClose();
- };
+ setTimeout(() => onClose(), 200);
+};
+
+const handleOverlayClick = (e: MouseEvent) => {
+ if (e.target === e.currentTarget) handleClose();
+};
</script>
<svelte:window onkeydown={(e) => e.key === 'Escape' && handleClose()} />
diff --git a/src/lib/List/MediaTitleDisplay.svelte b/src/lib/List/MediaTitleDisplay.svelte
index 6a886704..4d379ee9 100644
--- a/src/lib/List/MediaTitleDisplay.svelte
+++ b/src/lib/List/MediaTitleDisplay.svelte
@@ -1,17 +1,17 @@
<script lang="ts">
- import type { MediaTitle } from '$lib/Data/AniList/media';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import { abbreviate as abbreviated } from '$lib/Utility/string';
- import settings from '$stores/settings';
- import LZString from 'lz-string';
- import * as wanakana from 'wanakana';
+import type { MediaTitle } from '$lib/Data/AniList/media';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import { abbreviate as abbreviated } from '$lib/Utility/string';
+import settings from '$stores/settings';
+import LZString from 'lz-string';
+import * as wanakana from 'wanakana';
- export let title: MediaTitle;
- export let abbreviate = false;
- export let abbreviateTo = 20;
- export let tooltip = false;
+export let title: MediaTitle;
+export let abbreviate = false;
+export let abbreviateTo = 20;
+export let tooltip = false;
- const compressToBase64 = (string: string) => LZString.compressToBase64(string);
+const compressToBase64 = (string: string) => LZString.compressToBase64(string);
</script>
<span id={`title-display-${compressToBase64(title.native)}`}>
diff --git a/src/lib/Loading/Ellipsis.svelte b/src/lib/Loading/Ellipsis.svelte
index dfc8bcf4..674404fb 100644
--- a/src/lib/Loading/Ellipsis.svelte
+++ b/src/lib/Loading/Ellipsis.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let colour = 'var(--fg)';
+export let colour = 'var(--fg)';
</script>
<div class="ellipsis" style={`--loader-colour: ${colour};`}>
diff --git a/src/lib/Loading/Grid.svelte b/src/lib/Loading/Grid.svelte
index 543c5e5f..51fbd67e 100644
--- a/src/lib/Loading/Grid.svelte
+++ b/src/lib/Loading/Grid.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let colour = 'var(--fg)';
+export let colour = 'var(--fg)';
</script>
<div class="grid" style={`--loader-colour: ${colour};`}>
diff --git a/src/lib/Loading/Message.svelte b/src/lib/Loading/Message.svelte
index 53c993d3..9ae91704 100644
--- a/src/lib/Loading/Message.svelte
+++ b/src/lib/Loading/Message.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import Ellipsis from './Ellipsis.svelte';
- import Ripple from './Ripple.svelte';
- import Grid from './Grid.svelte';
- import Popup from '$lib/Layout/Popup.svelte';
+import Ellipsis from './Ellipsis.svelte';
+import Ripple from './Ripple.svelte';
+import Grid from './Grid.svelte';
+import Popup from '$lib/Layout/Popup.svelte';
- export let message: string | undefined = undefined;
- export let loader: 'ellipsis' | 'ripple' | 'grid' = 'ellipsis';
- export let colour = 'var(--fg)';
- export let slot = false;
- export let withReload = false;
- export let fullscreen = true;
+export let message: string | undefined = undefined;
+export let loader: 'ellipsis' | 'ripple' | 'grid' = 'ellipsis';
+export let colour = 'var(--fg)';
+export let slot = false;
+export let withReload = false;
+export let fullscreen = true;
</script>
<Popup {fullscreen} locked>
diff --git a/src/lib/Loading/Ripple.svelte b/src/lib/Loading/Ripple.svelte
index 0ce3d23c..74cf11b1 100644
--- a/src/lib/Loading/Ripple.svelte
+++ b/src/lib/Loading/Ripple.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let colour = 'var(--fg)';
+export let colour = 'var(--fg)';
</script>
<div class="ripple" style={`--loader-colour: ${colour};`}>
diff --git a/src/lib/Loading/Skeleton.svelte b/src/lib/Loading/Skeleton.svelte
index a0e1ab0e..03637c28 100644
--- a/src/lib/Loading/Skeleton.svelte
+++ b/src/lib/Loading/Skeleton.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- export let count = 3;
- export let width = '100%';
- export let height = '100px';
- export let card = true;
- export let grid = false;
- export let list = false;
- export let pad = false;
- export let bigCard = false;
+import Spacer from '$lib/Layout/Spacer.svelte';
+export let count = 3;
+export let width = '100%';
+export let height = '100px';
+export let card = true;
+export let grid = false;
+export let list = false;
+export let pad = false;
+export let bigCard = false;
</script>
{#if grid}
diff --git a/src/lib/MarkdownLink.svelte b/src/lib/MarkdownLink.svelte
index ee8c4900..0e01db41 100644
--- a/src/lib/MarkdownLink.svelte
+++ b/src/lib/MarkdownLink.svelte
@@ -1,20 +1,20 @@
<script lang="ts">
- let { href, text }: { href: string; text: string } = $props();
- let safeHref = $derived.by(() => {
- try {
- let url = new URL(href);
+let { href, text }: { href: string; text: string } = $props();
+let safeHref = $derived.by(() => {
+ try {
+ let url = new URL(href);
- switch (url.protocol) {
- case 'javascript:':
- return '#';
+ switch (url.protocol) {
+ case 'javascript:':
+ return '#';
- default:
- return href;
- }
- } catch (error) {
- return '#';
+ default:
+ return href;
}
- });
+ } catch (error) {
+ return '#';
+ }
+});
</script>
<a href={safeHref} target="_blank">{text}</a>
diff --git a/src/lib/Media/Anime/Airing/AiringTime.svelte b/src/lib/Media/Anime/Airing/AiringTime.svelte
index ff888629..0278de1c 100644
--- a/src/lib/Media/Anime/Airing/AiringTime.svelte
+++ b/src/lib/Media/Anime/Airing/AiringTime.svelte
@@ -1,120 +1,118 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import settings from '$stores/settings';
- import type { MediaPrequel } from '$lib/Data/AniList/prequels';
- import tooltip from '$lib/Tooltip/tooltip';
- import locale from '$stores/locale';
- import airingNow from '$stores/airingNow';
-
- export let originalAnime: Media;
- export let upcoming = false;
-
- const anime = originalAnime;
- let opacity = 100;
- let timeFrame = '';
- let time = '';
- let nextEpisode = anime.nextAiringEpisode?.episode || 0;
- let few = true;
- let dateString = '';
- const setAiringTime = () => {
- time = '';
- timeFrame = '';
- dateString = '';
-
- const airingAt = anime.nextAiringEpisode?.airingAt;
- const untilAiring = airingAt
- ? Math.round((airingAt - $airingNow / 1000) * 100) / 100
- : undefined;
- let hours = null;
- const shortenCountdown = $settings.displayShortCountdown;
-
- time = new Date(airingAt ? airingAt * 1000 : 0).toLocaleTimeString([], {
- hour12: !$settings.display24HourTime,
- hour: 'numeric',
- minute: '2-digit'
- });
-
- if (
- (anime as unknown as MediaPrequel).startDate &&
- new Date(
- anime.startDate.year,
- (anime as unknown as MediaPrequel).startDate.month,
- (anime as unknown as MediaPrequel).startDate.day
- ) < new Date()
- )
- return `<span class="opaque">on ${new Date(
- anime.startDate.year,
- (anime as unknown as MediaPrequel).startDate.month,
- (anime as unknown as MediaPrequel).startDate.day
- ).toLocaleDateString()}</span>`;
-
- if (untilAiring !== undefined) {
- let minutes = Math.round(untilAiring / 60);
-
- few = true;
-
- if (minutes > 60) {
- hours = minutes / 60;
-
- if (hours > 24) {
- const days = Math.floor(hours / 24);
- const weeks = Math.floor(days / 7);
-
- few = false;
-
- if (weeks >= 1.5) {
- timeFrame = `${weeks}${shortenCountdown ? 'w' : ' week'}${
- weeks === 1 || shortenCountdown ? '' : 's'
- }`;
-
- const residualDays = days % 7;
-
- if (residualDays > 0)
- timeFrame += `${shortenCountdown ? '' : ' '}${residualDays}${
- shortenCountdown ? 'd' : ' day'
- }${residualDays === 1 || shortenCountdown ? '' : 's'}`;
- } else {
- timeFrame += `${days}${shortenCountdown ? 'd' : ' day'}${
- days === 1 || shortenCountdown ? '' : 's'
- }`;
- }
-
- const residualHours = Math.floor(hours - days * 24);
-
- if (residualHours > 0)
- timeFrame += `${shortenCountdown ? '' : ' '}${residualHours}${
- shortenCountdown ? 'h' : ' hour'
- }${residualHours === 1 || shortenCountdown ? '' : 's'}`;
- } else {
- const residualMinutes = Math.round(minutes - Math.floor(hours) * 60);
-
- timeFrame += `${Math.floor(hours).toFixed(0)}${shortenCountdown ? 'h' : ' hour'}${
- Math.floor(hours) === 1 || shortenCountdown ? '' : 's'
+import type { Media } from '$lib/Data/AniList/media';
+import settings from '$stores/settings';
+import type { MediaPrequel } from '$lib/Data/AniList/prequels';
+import tooltip from '$lib/Tooltip/tooltip';
+import locale from '$stores/locale';
+import airingNow from '$stores/airingNow';
+
+export let originalAnime: Media;
+export let upcoming = false;
+
+const anime = originalAnime;
+let opacity = 100;
+let timeFrame = '';
+let time = '';
+let nextEpisode = anime.nextAiringEpisode?.episode || 0;
+let few = true;
+let dateString = '';
+const setAiringTime = () => {
+ time = '';
+ timeFrame = '';
+ dateString = '';
+
+ const airingAt = anime.nextAiringEpisode?.airingAt;
+ const untilAiring = airingAt ? Math.round((airingAt - $airingNow / 1000) * 100) / 100 : undefined;
+ let hours = null;
+ const shortenCountdown = $settings.displayShortCountdown;
+
+ time = new Date(airingAt ? airingAt * 1000 : 0).toLocaleTimeString([], {
+ hour12: !$settings.display24HourTime,
+ hour: 'numeric',
+ minute: '2-digit'
+ });
+
+ if (
+ (anime as unknown as MediaPrequel).startDate &&
+ new Date(
+ anime.startDate.year,
+ (anime as unknown as MediaPrequel).startDate.month,
+ (anime as unknown as MediaPrequel).startDate.day
+ ) < new Date()
+ )
+ return `<span class="opaque">on ${new Date(
+ anime.startDate.year,
+ (anime as unknown as MediaPrequel).startDate.month,
+ (anime as unknown as MediaPrequel).startDate.day
+ ).toLocaleDateString()}</span>`;
+
+ if (untilAiring !== undefined) {
+ let minutes = Math.round(untilAiring / 60);
+
+ few = true;
+
+ if (minutes > 60) {
+ hours = minutes / 60;
+
+ if (hours > 24) {
+ const days = Math.floor(hours / 24);
+ const weeks = Math.floor(days / 7);
+
+ few = false;
+
+ if (weeks >= 1.5) {
+ timeFrame = `${weeks}${shortenCountdown ? 'w' : ' week'}${
+ weeks === 1 || shortenCountdown ? '' : 's'
}`;
- if (residualMinutes > 0)
- timeFrame += `${shortenCountdown ? '' : ' '}${residualMinutes}${
- shortenCountdown ? 'm' : ' minute'
- }${residualMinutes === 1 || shortenCountdown ? '' : 's'}`;
+ const residualDays = days % 7;
+
+ if (residualDays > 0)
+ timeFrame += `${shortenCountdown ? '' : ' '}${residualDays}${
+ shortenCountdown ? 'd' : ' day'
+ }${residualDays === 1 || shortenCountdown ? '' : 's'}`;
+ } else {
+ timeFrame += `${days}${shortenCountdown ? 'd' : ' day'}${
+ days === 1 || shortenCountdown ? '' : 's'
+ }`;
}
+
+ const residualHours = Math.floor(hours - days * 24);
+
+ if (residualHours > 0)
+ timeFrame += `${shortenCountdown ? '' : ' '}${residualHours}${
+ shortenCountdown ? 'h' : ' hour'
+ }${residualHours === 1 || shortenCountdown ? '' : 's'}`;
} else {
- minutes = Math.round(minutes);
+ const residualMinutes = Math.round(minutes - Math.floor(hours) * 60);
- timeFrame += `${minutes}${shortenCountdown ? 'm' : ' minute'}${
- minutes === 1 || shortenCountdown ? '' : 's'
+ timeFrame += `${Math.floor(hours).toFixed(0)}${shortenCountdown ? 'h' : ' hour'}${
+ Math.floor(hours) === 1 || shortenCountdown ? '' : 's'
}`;
+
+ if (residualMinutes > 0)
+ timeFrame += `${shortenCountdown ? '' : ' '}${residualMinutes}${
+ shortenCountdown ? 'm' : ' minute'
+ }${residualMinutes === 1 || shortenCountdown ? '' : 's'}`;
}
+ } else {
+ minutes = Math.round(minutes);
- opacity = Math.max(50, 100 - (untilAiring / 60 / 60 / 24 / 7) * 50);
- dateString = $locale().dateFormatter(new Date(airingAt ? airingAt * 1000 : 0));
+ timeFrame += `${minutes}${shortenCountdown ? 'm' : ' minute'}${
+ minutes === 1 || shortenCountdown ? '' : 's'
+ }`;
}
- };
-
- $: {
- $airingNow;
- setAiringTime();
+ opacity = Math.max(50, 100 - (untilAiring / 60 / 60 / 24 / 7) * 50);
+ dateString = $locale().dateFormatter(new Date(airingAt ? airingAt * 1000 : 0));
}
+};
+
+$: {
+ $airingNow;
+
+ setAiringTime();
+}
</script>
{#if upcoming}
diff --git a/src/lib/Media/Cover/HoverCover.svelte b/src/lib/Media/Cover/HoverCover.svelte
index 81d6d3fc..48027e44 100644
--- a/src/lib/Media/Cover/HoverCover.svelte
+++ b/src/lib/Media/Cover/HoverCover.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import settings from '$stores/settings';
- import type { HoverCoverResponse } from './hoverCover';
+import settings from '$stores/settings';
+import type { HoverCoverResponse } from './hoverCover';
- export let options: HoverCoverResponse;
- export let width = 250;
+export let options: HoverCoverResponse;
+export let width = 250;
</script>
{#if options.hovering}
diff --git a/src/lib/Notification/Notification.svelte b/src/lib/Notification/Notification.svelte
index 864d6568..4e6fd2e7 100644
--- a/src/lib/Notification/Notification.svelte
+++ b/src/lib/Notification/Notification.svelte
@@ -1,21 +1,21 @@
<script lang="ts">
- import settings from '$stores/settings';
- import { onMount } from 'svelte';
- import type { Notification } from './store';
+import settings from '$stores/settings';
+import { onMount } from 'svelte';
+import type { Notification } from './store';
- export let notification: Notification;
- export let onRemove: () => void = () => {
- return;
- };
- export let removed = false;
+export let notification: Notification;
+export let onRemove: () => void = () => {
+ return;
+};
+export let removed = false;
- onMount(() => setTimeout(remove, notification.duration));
+onMount(() => setTimeout(remove, notification.duration));
- const remove = () => {
- removed = true;
+const remove = () => {
+ removed = true;
- setTimeout(onRemove, 150);
- };
+ setTimeout(onRemove, 150);
+};
</script>
{#if !$settings.displayDisableNotifications}
diff --git a/src/lib/Notification/NotificationsProvider.svelte b/src/lib/Notification/NotificationsProvider.svelte
index 964317e9..15e5f2b2 100644
--- a/src/lib/Notification/NotificationsProvider.svelte
+++ b/src/lib/Notification/NotificationsProvider.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import { notifications } from './store';
- import EventNotification from './Notification.svelte';
+import { notifications } from './store';
+import EventNotification from './Notification.svelte';
- export let zIndex = 5000;
+export let zIndex = 5000;
- const handleRemove = (id: string) => {
- notifications.remove(id);
- };
+const handleRemove = (id: string) => {
+ notifications.remove(id);
+};
</script>
<div class="notifications-container" style="z-index: {zIndex};">
diff --git a/src/lib/Reader/Chapters/MangaDex.svelte b/src/lib/Reader/Chapters/MangaDex.svelte
index ac5ce151..f1a90338 100644
--- a/src/lib/Reader/Chapters/MangaDex.svelte
+++ b/src/lib/Reader/Chapters/MangaDex.svelte
@@ -1,18 +1,18 @@
<script lang="ts">
- interface MangaDexChapter {
- id: string;
- attributes: {
- volume?: string;
- chapter: string;
- title?: string;
- };
- }
+interface MangaDexChapter {
+ id: string;
+ attributes: {
+ volume?: string;
+ chapter: string;
+ title?: string;
+ };
+}
- interface MangaDexData {
- data: MangaDexChapter[];
- }
+interface MangaDexData {
+ data: MangaDexChapter[];
+}
- export let data: MangaDexData;
+export let data: MangaDexData;
</script>
<ul>
diff --git a/src/lib/Reader/Chapters/Rawkuma.svelte b/src/lib/Reader/Chapters/Rawkuma.svelte
index 720c65d5..02e0ed18 100644
--- a/src/lib/Reader/Chapters/Rawkuma.svelte
+++ b/src/lib/Reader/Chapters/Rawkuma.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import { getChaptersFromText } from '$lib/Data/Manga/raw';
- import { onMount } from 'svelte';
+import { getChaptersFromText } from '$lib/Data/Manga/raw';
+import { onMount } from 'svelte';
- export let data: string;
+export let data: string;
- onMount(() => {
- console.log();
- });
+onMount(() => {
+ console.log();
+});
</script>
<ul>
diff --git a/src/lib/Schedule/CoverBypass.svelte b/src/lib/Schedule/CoverBypass.svelte
index 1f166e06..74810105 100644
--- a/src/lib/Schedule/CoverBypass.svelte
+++ b/src/lib/Schedule/CoverBypass.svelte
@@ -1,22 +1,22 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import type { SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+import type { Media } from '$lib/Data/AniList/media';
+import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+import type { SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
- import { outboundLink } from '$lib/Media/links';
- import tooltip from '$lib/Tooltip/tooltip';
- import { abbreviate } from '$lib/Utility/string';
- import settings from '$stores/settings';
+import { outboundLink } from '$lib/Media/links';
+import tooltip from '$lib/Tooltip/tooltip';
+import { abbreviate } from '$lib/Utility/string';
+import settings from '$stores/settings';
- export let media: Media | null;
- export let entry: SubsPleaseEpisode;
- export let cover = true;
- export let showTooltip = true;
+export let media: Media | null;
+export let entry: SubsPleaseEpisode;
+export let cover = true;
+export let showTooltip = true;
- const abbreviateTo = 40;
+const abbreviateTo = 40;
- const titleSelect = (media: Media | null) =>
- media ? media.title.english || media.title.romaji || media.title.native : null;
+const titleSelect = (media: Media | null) =>
+ media ? media.title.english || media.title.romaji || media.title.native : null;
</script>
<a
diff --git a/src/lib/Schedule/Crunchyroll.svelte b/src/lib/Schedule/Crunchyroll.svelte
index 9d7c8e70..becabebc 100644
--- a/src/lib/Schedule/Crunchyroll.svelte
+++ b/src/lib/Schedule/Crunchyroll.svelte
@@ -1,47 +1,47 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import crunchyroll from '$lib/Data/Static/crunchyroll.json';
- import './container.css';
-
- interface CrunchyrollMedia<T = number | 'soon' | 'continuing'> {
- year: number;
- month: number;
- day: T;
- title: string;
- }
+import Spacer from '$lib/Layout/Spacer.svelte';
+import crunchyroll from '$lib/Data/Static/crunchyroll.json';
+import './container.css';
+
+interface CrunchyrollMedia<T = number | 'soon' | 'continuing'> {
+ year: number;
+ month: number;
+ day: T;
+ title: string;
+}
- type KnownMedia = { [key: string]: CrunchyrollMedia<number>[] };
+type KnownMedia = { [key: string]: CrunchyrollMedia<number>[] };
- const days: KnownMedia = crunchyroll
- .filter((media) => media.day !== 'soon' && media.day !== 'continuing')
- .reduce((acc: KnownMedia, media) => {
- const date = new Date(media.year, media.month - 1, media.day as number).toLocaleDateString();
+const days: KnownMedia = crunchyroll
+ .filter((media) => media.day !== 'soon' && media.day !== 'continuing')
+ .reduce((acc: KnownMedia, media) => {
+ const date = new Date(media.year, media.month - 1, media.day as number).toLocaleDateString();
- if (!acc[date]) acc[date] = [];
+ if (!acc[date]) acc[date] = [];
- acc[date].push(media as CrunchyrollMedia<number>);
+ acc[date].push(media as CrunchyrollMedia<number>);
- return acc;
- }, {});
- const continuing: CrunchyrollMedia<number | string>[] = crunchyroll.filter(
- (media) => media.day === 'continuing'
- );
- const soon: CrunchyrollMedia<number | string>[] = crunchyroll.filter(
- (media) => media.day === 'soon'
- );
+ return acc;
+ }, {});
+const continuing: CrunchyrollMedia<number | string>[] = crunchyroll.filter(
+ (media) => media.day === 'continuing'
+);
+const soon: CrunchyrollMedia<number | string>[] = crunchyroll.filter(
+ (media) => media.day === 'soon'
+);
- $: columnCount = Math.ceil(Object.keys(days).length / 2);
+$: columnCount = Math.ceil(Object.keys(days).length / 2);
- const ordinalSuffix = (i: number) => {
- const j = i % 10;
- const k = i % 100;
+const ordinalSuffix = (i: number) => {
+ const j = i % 10;
+ const k = i % 100;
- if (j === 1 && k !== 11) return i + 'st';
- if (j === 2 && k !== 12) return i + 'nd';
- if (j === 3 && k !== 13) return i + 'rd';
+ if (j === 1 && k !== 11) return i + 'st';
+ if (j === 2 && k !== 12) return i + 'nd';
+ if (j === 3 && k !== 13) return i + 'rd';
- return i + 'th';
- };
+ return i + 'th';
+};
</script>
<div class="list-container" id="crunchyroll" style={`column-count: ${columnCount};`}>
diff --git a/src/lib/Schedule/Days.svelte b/src/lib/Schedule/Days.svelte
index 99955897..416c0cce 100644
--- a/src/lib/Schedule/Days.svelte
+++ b/src/lib/Schedule/Days.svelte
@@ -1,104 +1,102 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
- import { findClosestMedia } from '$lib/Media/Anime/Airing/Subtitled/match';
- import type { SubsPlease, SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
- import { outboundLink } from '$lib/Media/links';
- import { parseOrDefault } from '$lib/Utility/parameters';
- import settings from '$stores/settings';
- import CoverBypass from './CoverBypass.svelte';
- import '$lib/List/covers.css';
- import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import { onMount } from 'svelte';
- import identity from '$stores/identity';
- import anime from '$stores/anime';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import Message from '$lib/Loading/Message.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Error from '$lib/Error/RateLimited.svelte';
-
- export let subsPlease: SubsPlease;
- export let scheduledMedia: Partial<Media[]>;
- export let forceListMode = false;
- export let user;
-
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- let day: string | null = parseOrDefault(urlParameters, 'day', null);
-
- let mediaListPromise: Promise<Media[]>;
-
- onMount(async () => {
- if (user === undefined || $identity.id === -2) mediaListPromise = Promise.resolve([]);
- else
- mediaListPromise = mediaListCollection(
- user,
- $identity,
- Type.Anime,
- $anime,
- $lastPruneTimes.anime,
- {
- all: true
- }
- );
- });
-
- const shiftSubsPleaseSchedule = (schedule: SubsPlease['schedule']) => {
- const shiftedSchedule: { [key: string]: SubsPleaseEpisode[] } = {};
+import { browser } from '$app/environment';
+import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media';
+import { findClosestMedia } from '$lib/Media/Anime/Airing/Subtitled/match';
+import type { SubsPlease, SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+import { outboundLink } from '$lib/Media/links';
+import { parseOrDefault } from '$lib/Utility/parameters';
+import settings from '$stores/settings';
+import CoverBypass from './CoverBypass.svelte';
+import '$lib/List/covers.css';
+import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import { onMount } from 'svelte';
+import identity from '$stores/identity';
+import anime from '$stores/anime';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import Message from '$lib/Loading/Message.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import Error from '$lib/Error/RateLimited.svelte';
+
+export let subsPlease: SubsPlease;
+export let scheduledMedia: Partial<Media[]>;
+export let forceListMode = false;
+export let user;
+
+const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+let day: string | null = parseOrDefault(urlParameters, 'day', null);
+
+let mediaListPromise: Promise<Media[]>;
+
+onMount(async () => {
+ if (user === undefined || $identity.id === -2) mediaListPromise = Promise.resolve([]);
+ else
+ mediaListPromise = mediaListCollection(
+ user,
+ $identity,
+ Type.Anime,
+ $anime,
+ $lastPruneTimes.anime,
+ {
+ all: true
+ }
+ );
+});
- if (day && Object.keys(schedule).includes(day)) {
- shiftedSchedule[day] = schedule[
- day as keyof typeof schedule
- ] as unknown as SubsPleaseEpisode[];
+const shiftSubsPleaseSchedule = (schedule: SubsPlease['schedule']) => {
+ const shiftedSchedule: { [key: string]: SubsPleaseEpisode[] } = {};
- return shiftedSchedule;
- }
+ if (day && Object.keys(schedule).includes(day)) {
+ shiftedSchedule[day] = schedule[day as keyof typeof schedule] as unknown as SubsPleaseEpisode[];
- const days = Object.keys(schedule);
- const currentDayIndex = days.indexOf(new Date().toLocaleString('en-us', { weekday: 'long' }));
+ return shiftedSchedule;
+ }
- days
- .slice(currentDayIndex)
- .concat(days.slice(0, currentDayIndex))
- .forEach((day) => {
- const scheduleEntry = schedule[day as keyof typeof schedule];
+ const days = Object.keys(schedule);
+ const currentDayIndex = days.indexOf(new Date().toLocaleString('en-us', { weekday: 'long' }));
- shiftedSchedule[day] = Array.isArray(scheduleEntry)
- ? scheduleEntry
- : ([scheduleEntry] as unknown as SubsPleaseEpisode[]);
- });
+ days
+ .slice(currentDayIndex)
+ .concat(days.slice(0, currentDayIndex))
+ .forEach((day) => {
+ const scheduleEntry = schedule[day as keyof typeof schedule];
- Object.entries(shiftedSchedule).forEach(([day, scheduleEntry]) => {
- if (scheduleEntry.length === 0) {
- delete shiftedSchedule[day];
- }
+ shiftedSchedule[day] = Array.isArray(scheduleEntry)
+ ? scheduleEntry
+ : ([scheduleEntry] as unknown as SubsPleaseEpisode[]);
});
- return shiftedSchedule;
- };
+ Object.entries(shiftedSchedule).forEach(([day, scheduleEntry]) => {
+ if (scheduleEntry.length === 0) {
+ delete shiftedSchedule[day];
+ }
+ });
+
+ return shiftedSchedule;
+};
- const associateMedia = (media: (Media | undefined)[], title: string, mediaList: Media[]) => {
- const closestMedia = findClosestMedia(media as Media[], title);
+const associateMedia = (media: (Media | undefined)[], title: string, mediaList: Media[]) => {
+ const closestMedia = findClosestMedia(media as Media[], title);
- if ($settings.displayScheduleFilterList && closestMedia && mediaList)
- return mediaList.find((m) => m.id === closestMedia?.id) || null;
+ if ($settings.displayScheduleFilterList && closestMedia && mediaList)
+ return mediaList.find((m) => m.id === closestMedia?.id) || null;
- return closestMedia;
- };
+ return closestMedia;
+};
- const episode = (media: Media, weekday: string) => {
- if (media.nextAiringEpisode?.episode === 1) return 1;
+const episode = (media: Media, weekday: string) => {
+ if (media.nextAiringEpisode?.episode === 1) return 1;
- if (
- media.nextAiringEpisode?.airingAt &&
- weekday === new Date().toLocaleString('en-us', { weekday: 'long' }) &&
- new Date(media.nextAiringEpisode.airingAt * 1000).getTime() - new Date().getTime() >
- 24 * 60 * 60 * 1000
- )
- return media.nextAiringEpisode?.episode - 1;
+ if (
+ media.nextAiringEpisode?.airingAt &&
+ weekday === new Date().toLocaleString('en-us', { weekday: 'long' }) &&
+ new Date(media.nextAiringEpisode.airingAt * 1000).getTime() - new Date().getTime() >
+ 24 * 60 * 60 * 1000
+ )
+ return media.nextAiringEpisode?.episode - 1;
- return media.nextAiringEpisode?.episode || 1;
- };
+ return media.nextAiringEpisode?.episode || 1;
+};
</script>
{#await mediaListPromise}
diff --git a/src/lib/Settings/Categories/Attributions.svelte b/src/lib/Settings/Categories/Attributions.svelte
index e0a77f1f..7d2939d1 100644
--- a/src/lib/Settings/Categories/Attributions.svelte
+++ b/src/lib/Settings/Categories/Attributions.svelte
@@ -1,6 +1,6 @@
<script>
- import Spacer from '$lib/Layout/Spacer.svelte';
- import root from '$lib/Utility/root';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import root from '$lib/Utility/root';
</script>
<ul>
diff --git a/src/lib/Settings/Categories/Cache.svelte b/src/lib/Settings/Categories/Cache.svelte
index 68783bdf..c400e6ae 100644
--- a/src/lib/Settings/Categories/Cache.svelte
+++ b/src/lib/Settings/Categories/Cache.svelte
@@ -1,6 +1,6 @@
<script>
- import Spacer from '$lib/Layout/Spacer.svelte';
- import settings from '$stores/settings';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import settings from '$stores/settings';
</script>
<small class="opaque">
diff --git a/src/lib/Settings/Categories/Calculation.svelte b/src/lib/Settings/Categories/Calculation.svelte
index 99e42463..b8b27fc7 100644
--- a/src/lib/Settings/Categories/Calculation.svelte
+++ b/src/lib/Settings/Categories/Calculation.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import { pruneAllManga } from '$lib/Media/Manga/cache';
- import locale from '$stores/locale';
- import settings from '$stores/settings';
- import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
- import SettingHint from '../SettingHint.svelte';
+import { pruneAllManga } from '$lib/Media/Manga/cache';
+import locale from '$stores/locale';
+import settings from '$stores/settings';
+import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
+import SettingHint from '../SettingHint.svelte';
</script>
<SettingCheckboxToggle
diff --git a/src/lib/Settings/Categories/Debug.svelte b/src/lib/Settings/Categories/Debug.svelte
index 8bae4c84..6967f49f 100644
--- a/src/lib/Settings/Categories/Debug.svelte
+++ b/src/lib/Settings/Categories/Debug.svelte
@@ -1,25 +1,25 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import settings from '$stores/settings';
- import { addNotification } from '$lib/Notification/store';
- import SettingHint from '../SettingHint.svelte';
- import { options } from '$lib/Notification/options';
- import locale from '$stores/locale';
- import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
- import localforage from 'localforage';
- import { browser } from '$app/environment';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import settings from '$stores/settings';
+import { addNotification } from '$lib/Notification/store';
+import SettingHint from '../SettingHint.svelte';
+import { options } from '$lib/Notification/options';
+import locale from '$stores/locale';
+import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
+import localforage from 'localforage';
+import { browser } from '$app/environment';
- const clearCaches = async () => {
- if (!browser) return;
+const clearCaches = async () => {
+ if (!browser) return;
- await localforage.removeItem('anime');
- await localforage.removeItem('manga');
- addNotification(
- options({
- heading: 'Anime and manga list caches successfully cleared'
- })
- );
- };
+ await localforage.removeItem('anime');
+ await localforage.removeItem('manga');
+ addNotification(
+ options({
+ heading: 'Anime and manga list caches successfully cleared'
+ })
+ );
+};
</script>
<SettingCheckboxToggle setting="debugDummyLists" text={$locale().debug.dummyLists} />
diff --git a/src/lib/Settings/Categories/Display.svelte b/src/lib/Settings/Categories/Display.svelte
index a5f16f97..783dd075 100644
--- a/src/lib/Settings/Categories/Display.svelte
+++ b/src/lib/Settings/Categories/Display.svelte
@@ -1,70 +1,70 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import settings from '$stores/settings';
- import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
- import SettingHint from '../SettingHint.svelte';
- import root from '$lib/Utility/root';
- import locale from '$stores/locale';
- import { requestNotifications } from '$lib/Utility/notifications';
- import { getFingerprint } from '$lib/Utility/fingerprint';
-
- const onHelperChange = () => {
- const mai = document.getElementById('mai') as HTMLImageElement;
-
- if (!mai) return;
-
- mai.style.display = 'block';
-
- switch ($settings.displayAoButa) {
- case 'random':
- case 'mai_2':
- {
- mai.src = '/aobuta/mai.png';
- }
- break;
- case 'mai':
- {
- mai.src = '/aobuta/mai_2.webp';
- }
- break;
- case 'nodoka':
- {
- mai.src = '/aobuta/nodoka.webp';
- }
- break;
- case 'kaede':
- {
- mai.src = '/aobuta/kaede.png';
- }
- break;
- case 'rio':
- {
- mai.src = '/aobuta/rio.webp';
- }
- break;
- case 'sakuta':
- {
- mai.src = '/aobuta/sakuta.webp';
- }
- break;
- case 'shouko':
- {
- mai.src = '/aobuta/shouko.webp';
- }
- break;
- case 'tomoe':
- {
- mai.src = '/aobuta/tomoe.webp';
- }
- break;
- case 'none': {
- {
- mai.style.display = 'none';
- }
- break;
+import Spacer from '$lib/Layout/Spacer.svelte';
+import settings from '$stores/settings';
+import SettingCheckboxToggle from '../SettingCheckboxToggle.svelte';
+import SettingHint from '../SettingHint.svelte';
+import root from '$lib/Utility/root';
+import locale from '$stores/locale';
+import { requestNotifications } from '$lib/Utility/notifications';
+import { getFingerprint } from '$lib/Utility/fingerprint';
+
+const onHelperChange = () => {
+ const mai = document.getElementById('mai') as HTMLImageElement;
+
+ if (!mai) return;
+
+ mai.style.display = 'block';
+
+ switch ($settings.displayAoButa) {
+ case 'random':
+ case 'mai_2':
+ {
+ mai.src = '/aobuta/mai.png';
}
+ break;
+ case 'mai':
+ {
+ mai.src = '/aobuta/mai_2.webp';
+ }
+ break;
+ case 'nodoka':
+ {
+ mai.src = '/aobuta/nodoka.webp';
+ }
+ break;
+ case 'kaede':
+ {
+ mai.src = '/aobuta/kaede.png';
+ }
+ break;
+ case 'rio':
+ {
+ mai.src = '/aobuta/rio.webp';
+ }
+ break;
+ case 'sakuta':
+ {
+ mai.src = '/aobuta/sakuta.webp';
+ }
+ break;
+ case 'shouko':
+ {
+ mai.src = '/aobuta/shouko.webp';
+ }
+ break;
+ case 'tomoe':
+ {
+ mai.src = '/aobuta/tomoe.webp';
+ }
+ break;
+ case 'none': {
+ {
+ mai.style.display = 'none';
+ }
+ break;
}
- };
+ }
+};
</script>
<b>{$locale().settings.display.categories.includeAdditionalMedia}</b><br />
diff --git a/src/lib/Settings/Categories/RSSFeeds.svelte b/src/lib/Settings/Categories/RSSFeeds.svelte
index 51554884..452a068b 100644
--- a/src/lib/Settings/Categories/RSSFeeds.svelte
+++ b/src/lib/Settings/Categories/RSSFeeds.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import { options } from '$lib/Notification/options';
- import { addNotification } from '$lib/Notification/store';
- import { env } from '$env/dynamic/public';
- import locale from '$stores/locale';
- import SettingHint from '../SettingHint.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
+import { options } from '$lib/Notification/options';
+import { addNotification } from '$lib/Notification/store';
+import { env } from '$env/dynamic/public';
+import locale from '$stores/locale';
+import SettingHint from '../SettingHint.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
- export let user: { accessToken: string; refreshToken: string };
+export let user: { accessToken: string; refreshToken: string };
</script>
<button
diff --git a/src/lib/Settings/Categories/SettingSync.svelte b/src/lib/Settings/Categories/SettingSync.svelte
index d69eab09..39ba9d87 100644
--- a/src/lib/Settings/Categories/SettingSync.svelte
+++ b/src/lib/Settings/Categories/SettingSync.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { options } from '$lib/Notification/options';
- import root from '$lib/Utility/root';
- import identity from '$stores/identity';
- import settings from '$stores/settings';
- import { addNotification } from '$lib/Notification/store';
- import SettingHint from '../SettingHint.svelte';
- import locale from '$stores/locale';
- import settingsSyncTimes from '$stores/settingsSyncTimes';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { options } from '$lib/Notification/options';
+import root from '$lib/Utility/root';
+import identity from '$stores/identity';
+import settings from '$stores/settings';
+import { addNotification } from '$lib/Notification/store';
+import SettingHint from '../SettingHint.svelte';
+import locale from '$stores/locale';
+import settingsSyncTimes from '$stores/settingsSyncTimes';
</script>
{#if !$settings.settingsSync}
diff --git a/src/lib/Settings/Category.svelte b/src/lib/Settings/Category.svelte
index f4936e8a..58930cbb 100644
--- a/src/lib/Settings/Category.svelte
+++ b/src/lib/Settings/Category.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- export let title = '';
- export let id = title.toLowerCase();
- export let open = true;
- export let newLine = true;
+import Spacer from '$lib/Layout/Spacer.svelte';
+export let title = '';
+export let id = title.toLowerCase();
+export let open = true;
+export let newLine = true;
</script>
<details {open} {id}>
diff --git a/src/lib/Settings/SettingCheckboxToggle.svelte b/src/lib/Settings/SettingCheckboxToggle.svelte
index c9d82907..b5ef4989 100644
--- a/src/lib/Settings/SettingCheckboxToggle.svelte
+++ b/src/lib/Settings/SettingCheckboxToggle.svelte
@@ -1,57 +1,57 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import settings, { type Settings } from '$stores/settings';
-
- type BooleanSettingsKeys<T> = {
- [K in keyof T]: T[K] extends boolean ? K : never;
- };
- type SettingsBooleanKeys = BooleanSettingsKeys<Settings>;
-
- export let sectionBreak = false;
- export let disabled = false;
- export let text: string | (() => string);
- export let setting: SettingsBooleanKeys[keyof SettingsBooleanKeys];
- export let lineBreak = true;
- export let onChange: () => void = () => {
- return;
- };
- export let invert = false;
- export let id: string | null = null;
-
- $: checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false;
- $: field = text instanceof Function ? text() : text;
-
- // const toggler = (key: keyof Settings) => [
- // () =>
- // settings.update((s) => {
- // (s[key] as boolean) = true;
-
- // $settings = s;
-
- // return s;
- // }),
- // () =>
- // settings.update((s) => {
- // (s[key] as boolean) = false;
-
- // $settings = s;
-
- // return s;
- // })
- // ];
-
- const check = (e: Event & { currentTarget: EventTarget & HTMLInputElement }): void => {
- const checked = (e.target as HTMLInputElement).checked;
-
- if (setting) {
- settings.setKey(setting, invert ? !checked : checked);
- onChange();
- }
- };
-
- const flip = () => {
- if (setting) $settings[setting] = !$settings[setting];
- };
+import Spacer from '$lib/Layout/Spacer.svelte';
+import settings, { type Settings } from '$stores/settings';
+
+type BooleanSettingsKeys<T> = {
+ [K in keyof T]: T[K] extends boolean ? K : never;
+};
+type SettingsBooleanKeys = BooleanSettingsKeys<Settings>;
+
+export let sectionBreak = false;
+export let disabled = false;
+export let text: string | (() => string);
+export let setting: SettingsBooleanKeys[keyof SettingsBooleanKeys];
+export let lineBreak = true;
+export let onChange: () => void = () => {
+ return;
+};
+export let invert = false;
+export let id: string | null = null;
+
+$: checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false;
+$: field = text instanceof Function ? text() : text;
+
+// const toggler = (key: keyof Settings) => [
+// () =>
+// settings.update((s) => {
+// (s[key] as boolean) = true;
+
+// $settings = s;
+
+// return s;
+// }),
+// () =>
+// settings.update((s) => {
+// (s[key] as boolean) = false;
+
+// $settings = s;
+
+// return s;
+// })
+// ];
+
+const check = (e: Event & { currentTarget: EventTarget & HTMLInputElement }): void => {
+ const checked = (e.target as HTMLInputElement).checked;
+
+ if (setting) {
+ settings.setKey(setting, invert ? !checked : checked);
+ onChange();
+ }
+};
+
+const flip = () => {
+ if (setting) $settings[setting] = !$settings[setting];
+};
</script>
<input type="checkbox" onchange={check} bind:checked {id} />
diff --git a/src/lib/Settings/SettingHint.svelte b/src/lib/Settings/SettingHint.svelte
index f82f061c..bac70366 100644
--- a/src/lib/Settings/SettingHint.svelte
+++ b/src/lib/Settings/SettingHint.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- export let lineBreak = false;
+export let lineBreak = false;
</script>
{#if lineBreak}
diff --git a/src/lib/Settings/SettingToggle.svelte b/src/lib/Settings/SettingToggle.svelte
index d7e31322..d389e72f 100644
--- a/src/lib/Settings/SettingToggle.svelte
+++ b/src/lib/Settings/SettingToggle.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import settings, { type Settings } from '$stores/settings';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import settings, { type Settings } from '$stores/settings';
- export let setting: keyof Settings;
- export let on = '';
- export let off = '';
- export let sectionBreak = false;
- export let disabled = false;
+export let setting: keyof Settings;
+export let on = '';
+export let off = '';
+export let sectionBreak = false;
+export let disabled = false;
</script>
<a
diff --git a/src/lib/Settings/Verbiage.svelte b/src/lib/Settings/Verbiage.svelte
index 76193c3b..0baf2768 100644
--- a/src/lib/Settings/Verbiage.svelte
+++ b/src/lib/Settings/Verbiage.svelte
@@ -1,5 +1,5 @@
<script>
- import root from '$lib/Utility/root';
+import root from '$lib/Utility/root';
</script>
<details open={false}>
diff --git a/src/lib/Tools/ActivityHistory/Grid.svelte b/src/lib/Tools/ActivityHistory/Grid.svelte
index b693f912..84c182fa 100644
--- a/src/lib/Tools/ActivityHistory/Grid.svelte
+++ b/src/lib/Tools/ActivityHistory/Grid.svelte
@@ -1,35 +1,35 @@
<script lang="ts">
- import {
- fillMissingDays,
- type ActivityHistoryEntry,
- activityHistory
- } from '$lib/Data/AniList/activity';
- import { onMount } from 'svelte';
- import userIdentity from '$stores/identity';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { clearAllParameters } from '../../Utility/parameters';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+import {
+ fillMissingDays,
+ type ActivityHistoryEntry,
+ activityHistory
+} from '$lib/Data/AniList/activity';
+import { onMount } from 'svelte';
+import userIdentity from '$stores/identity';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { clearAllParameters } from '../../Utility/parameters';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
- export let user: AniListAuthorisation;
- export let activityData: ActivityHistoryEntry[] | null = null;
- export let currentYear = new Date().getFullYear();
+export let user: AniListAuthorisation;
+export let activityData: ActivityHistoryEntry[] | null = null;
+export let currentYear = new Date().getFullYear();
- let activityHistoryData: ActivityHistoryEntry[];
- let baseHue = Math.floor(Math.random() * 360);
+let activityHistoryData: ActivityHistoryEntry[];
+let baseHue = Math.floor(Math.random() * 360);
- onMount(async () => {
- clearAllParameters();
+onMount(async () => {
+ clearAllParameters();
- activityHistoryData = activityData || (await activityHistory($userIdentity));
- });
+ activityHistoryData = activityData || (await activityHistory($userIdentity));
+});
- const gradientColour = (amount: number, maxAmount: number, baseHue: number) => {
- const lightness = 100 - Math.round((amount / maxAmount) * 50);
+const gradientColour = (amount: number, maxAmount: number, baseHue: number) => {
+ const lightness = 100 - Math.round((amount / maxAmount) * 50);
- return `hsl(${baseHue}, 100%, ${lightness}%)`;
- };
+ return `hsl(${baseHue}, 100%, ${lightness}%)`;
+};
</script>
{#if user === undefined}
diff --git a/src/lib/Tools/ActivityHistory/Tool.svelte b/src/lib/Tools/ActivityHistory/Tool.svelte
index 6f6282a6..06924b22 100644
--- a/src/lib/Tools/ActivityHistory/Tool.svelte
+++ b/src/lib/Tools/ActivityHistory/Tool.svelte
@@ -1,69 +1,69 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import {
- activityHistory,
- fillMissingDays,
- type ActivityHistoryEntry
- } from '$lib/Data/AniList/activity';
- import { onMount } from 'svelte';
- import userIdentity from '$stores/identity';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { clearAllParameters } from '../../Utility/parameters';
- import { domToBlob } from 'modern-screenshot';
- import ActivityHistoryGrid from './Grid.svelte';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
-
- export let user: AniListAuthorisation;
-
- let activityHistoryData: Promise<ActivityHistoryEntry[]>;
- let generated = false;
-
- onMount(async () => {
- clearAllParameters();
-
- if (user !== undefined) activityHistoryData = activityHistory($userIdentity);
- });
-
- // const incrementDate = (date: Date): Date => {
- // date.setDate(date.getDate() + 1);
-
- // return date;
- // };
-
- const screenshot = async () => {
- let element = document.querySelector('.grid') as HTMLElement;
-
- if (element !== null) {
- domToBlob(element, {
- quality: 1,
- scale: 2
- }).then((blob) => {
- const downloadWrapper = document.createElement('a');
- const image = document.createElement('img');
- const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
-
- downloadWrapper.href = object;
- downloadWrapper.target = '_blank';
- image.src = object;
-
- downloadWrapper.appendChild(image);
-
- const gridFinal = document.getElementById('grid-final');
-
- if (gridFinal !== null) {
- gridFinal.innerHTML = '';
-
- gridFinal.appendChild(downloadWrapper);
-
- generated = true;
- }
-
- downloadWrapper.click();
- });
- }
- };
+import Spacer from '$lib/Layout/Spacer.svelte';
+import {
+ activityHistory,
+ fillMissingDays,
+ type ActivityHistoryEntry
+} from '$lib/Data/AniList/activity';
+import { onMount } from 'svelte';
+import userIdentity from '$stores/identity';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { clearAllParameters } from '../../Utility/parameters';
+import { domToBlob } from 'modern-screenshot';
+import ActivityHistoryGrid from './Grid.svelte';
+import SettingHint from '$lib/Settings/SettingHint.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+
+export let user: AniListAuthorisation;
+
+let activityHistoryData: Promise<ActivityHistoryEntry[]>;
+let generated = false;
+
+onMount(async () => {
+ clearAllParameters();
+
+ if (user !== undefined) activityHistoryData = activityHistory($userIdentity);
+});
+
+// const incrementDate = (date: Date): Date => {
+// date.setDate(date.getDate() + 1);
+
+// return date;
+// };
+
+const screenshot = async () => {
+ let element = document.querySelector('.grid') as HTMLElement;
+
+ if (element !== null) {
+ domToBlob(element, {
+ quality: 1,
+ scale: 2
+ }).then((blob) => {
+ const downloadWrapper = document.createElement('a');
+ const image = document.createElement('img');
+ const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
+
+ downloadWrapper.href = object;
+ downloadWrapper.target = '_blank';
+ image.src = object;
+
+ downloadWrapper.appendChild(image);
+
+ const gridFinal = document.getElementById('grid-final');
+
+ if (gridFinal !== null) {
+ gridFinal.innerHTML = '';
+
+ gridFinal.appendChild(downloadWrapper);
+
+ generated = true;
+ }
+
+ downloadWrapper.click();
+ });
+ }
+};
</script>
{#if user === undefined}
diff --git a/src/lib/Tools/Birthdays.svelte b/src/lib/Tools/Birthdays.svelte
index e6654833..6c81a233 100644
--- a/src/lib/Tools/Birthdays.svelte
+++ b/src/lib/Tools/Birthdays.svelte
@@ -1,111 +1,111 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { page } from '$app/stores';
- import { ACDBBirthdays, type ACDBBirthday } from '$lib/Data/Birthday/secondary';
- import { aniSearchBirthdays, type aniSearchBirthday } from '$lib/Data/Birthday/primary';
- import Error from '$lib/Error/RateLimited.svelte';
- import { onMount } from 'svelte';
- import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
-
- interface Birthday {
- name: string;
- image: string;
- origin?: string;
+import { browser } from '$app/environment';
+import { page } from '$app/stores';
+import { ACDBBirthdays, type ACDBBirthday } from '$lib/Data/Birthday/secondary';
+import { aniSearchBirthdays, type aniSearchBirthday } from '$lib/Data/Birthday/primary';
+import Error from '$lib/Error/RateLimited.svelte';
+import { onMount } from 'svelte';
+import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import Message from '$lib/Loading/Message.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+
+interface Birthday {
+ name: string;
+ image: string;
+ origin?: string;
+}
+
+const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+let date = new Date();
+let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
+let day = parseOrDefault(urlParameters, 'day', date.getDate());
+let birthdays: Promise<{ birthdays: Birthday[]; allSourcesFailed: boolean }>;
+
+$: {
+ month = Math.min(month, 12);
+ month = Math.max(month, 1);
+ day = Math.min(day, new Date(2024, month, 0).getDate());
+ day = Math.max(day, 1);
+
+ birthdays = resolveBirthdays(month, day);
+
+ if (browser) {
+ $page.url.searchParams.set('month', month.toString());
+ $page.url.searchParams.set('day', day.toString());
+ clearAllParameters(['month', 'day']);
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
+}
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- let date = new Date();
- let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
- let day = parseOrDefault(urlParameters, 'day', date.getDate());
- let birthdays: Promise<{ birthdays: Birthday[]; allSourcesFailed: boolean }>;
-
- $: {
- month = Math.min(month, 12);
- month = Math.max(month, 1);
- day = Math.min(day, new Date(2024, month, 0).getDate());
- day = Math.max(day, 1);
-
- birthdays = resolveBirthdays(month, day);
-
- if (browser) {
- $page.url.searchParams.set('month', month.toString());
- $page.url.searchParams.set('day', day.toString());
- clearAllParameters(['month', 'day']);
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
- }
+onMount(() => clearAllParameters(['month', 'day']));
- onMount(() => clearAllParameters(['month', 'day']));
+const normaliseName = (name: string): string => name.toLowerCase().split(' ').sort().join(' ');
- const normaliseName = (name: string): string => name.toLowerCase().split(' ').sort().join(' ');
+const fixName = (name: string): string => {
+ const split = name.split(' ');
+ const last = split[split.length - 1];
- const fixName = (name: string): string => {
- const split = name.split(' ');
- const last = split[split.length - 1];
+ if (last === last.toUpperCase()) {
+ split[split.length - 1] = last[0] + last.slice(1).toLowerCase();
- if (last === last.toUpperCase()) {
- split[split.length - 1] = last[0] + last.slice(1).toLowerCase();
+ return split.join(' ');
+ }
- return split.join(' ');
- }
+ const bracketIndex = name.indexOf('[');
- const bracketIndex = name.indexOf('[');
+ if (bracketIndex !== -1) return name.slice(0, bracketIndex).trim();
- if (bracketIndex !== -1) return name.slice(0, bracketIndex).trim();
+ return name;
+};
- return name;
- };
+const combineBirthdaySources = (
+ acdb: ACDBBirthday[],
+ aniSearch: aniSearchBirthday[]
+): Birthday[] => {
+ const nameMap = new Map<string, Birthday>();
- const combineBirthdaySources = (
- acdb: ACDBBirthday[],
- aniSearch: aniSearchBirthday[]
- ): Birthday[] => {
- const nameMap = new Map<string, Birthday>();
-
- for (const entry of aniSearch.map((entry) => ({
- ...entry,
- normalisedName: normaliseName(fixName(entry.name))
- }))) {
- if (!nameMap.has(entry.normalisedName))
- nameMap.set(entry.normalisedName, {
- name: fixName(entry.name),
- image: entry.image
- });
- }
-
- for (const entry of acdb) {
- const normalisedName = normaliseName(fixName(entry.name));
+ for (const entry of aniSearch.map((entry) => ({
+ ...entry,
+ normalisedName: normaliseName(fixName(entry.name))
+ }))) {
+ if (!nameMap.has(entry.normalisedName))
+ nameMap.set(entry.normalisedName, {
+ name: fixName(entry.name),
+ image: entry.image
+ });
+ }
- if (!nameMap.has(normalisedName))
- nameMap.set(normalisedName, {
- name: entry.name,
- image: entry.character_image,
- origin: entry.origin
- });
- }
+ for (const entry of acdb) {
+ const normalisedName = normaliseName(fixName(entry.name));
- return Array.from(nameMap.values());
- };
+ if (!nameMap.has(normalisedName))
+ nameMap.set(normalisedName, {
+ name: entry.name,
+ image: entry.character_image,
+ origin: entry.origin
+ });
+ }
- const resolveBirthdays = async (
- month: number,
- day: number
- ): Promise<{ birthdays: Birthday[]; allSourcesFailed: boolean }> => {
- const [acdbResult, aniSearchResult] = await Promise.allSettled([
- ACDBBirthdays(month, day),
- browser ? aniSearchBirthdays(month, day) : Promise.resolve([])
- ]);
- const acdb = acdbResult.status === 'fulfilled' ? acdbResult.value : [];
- const aniSearch = aniSearchResult.status === 'fulfilled' ? aniSearchResult.value : [];
-
- return {
- birthdays: combineBirthdaySources(acdb, aniSearch),
- allSourcesFailed: acdbResult.status === 'rejected' && aniSearchResult.status === 'rejected'
- };
+ return Array.from(nameMap.values());
+};
+
+const resolveBirthdays = async (
+ month: number,
+ day: number
+): Promise<{ birthdays: Birthday[]; allSourcesFailed: boolean }> => {
+ const [acdbResult, aniSearchResult] = await Promise.allSettled([
+ ACDBBirthdays(month, day),
+ browser ? aniSearchBirthdays(month, day) : Promise.resolve([])
+ ]);
+ const acdb = acdbResult.status === 'fulfilled' ? acdbResult.value : [];
+ const aniSearch = aniSearchResult.status === 'fulfilled' ? aniSearchResult.value : [];
+
+ return {
+ birthdays: combineBirthdaySources(acdb, aniSearch),
+ allSourcesFailed: acdbResult.status === 'rejected' && aniSearchResult.status === 'rejected'
};
+};
</script>
{#await birthdays}
diff --git a/src/lib/Tools/BirthdaysTemplate.svelte b/src/lib/Tools/BirthdaysTemplate.svelte
index 5f476275..0ec937c4 100644
--- a/src/lib/Tools/BirthdaysTemplate.svelte
+++ b/src/lib/Tools/BirthdaysTemplate.svelte
@@ -1,36 +1,36 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { page } from '$app/stores';
- import { onMount } from 'svelte';
- import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
- import Message from '$lib/Loading/Message.svelte';
- import locale from '$stores/locale';
- import Error from '$lib/Error/RateLimited.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
+import { browser } from '$app/environment';
+import { page } from '$app/stores';
+import { onMount } from 'svelte';
+import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
+import Message from '$lib/Loading/Message.svelte';
+import locale from '$stores/locale';
+import Error from '$lib/Error/RateLimited.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
- export let remoteURL: string;
+export let remoteURL: string;
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- let date = new Date();
- let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
- let day = parseOrDefault(urlParameters, 'day', date.getDate());
- const remoteBirthdays = fetch(remoteURL);
+const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+let date = new Date();
+let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
+let day = parseOrDefault(urlParameters, 'day', date.getDate());
+const remoteBirthdays = fetch(remoteURL);
- $: {
- month = Math.min(month, 12);
- month = Math.max(month, 1);
- day = Math.min(day, new Date(2024, month, 0).getDate());
- day = Math.max(day, 1);
+$: {
+ month = Math.min(month, 12);
+ month = Math.max(month, 1);
+ day = Math.min(day, new Date(2024, month, 0).getDate());
+ day = Math.max(day, 1);
- if (browser) {
- $page.url.searchParams.set('month', month.toString());
- $page.url.searchParams.set('day', day.toString());
- clearAllParameters(['month', 'day']);
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
+ if (browser) {
+ $page.url.searchParams.set('month', month.toString());
+ $page.url.searchParams.set('day', day.toString());
+ clearAllParameters(['month', 'day']);
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
+}
- onMount(() => clearAllParameters(['month', 'day']));
+onMount(() => clearAllParameters(['month', 'day']));
</script>
{#await remoteBirthdays}
diff --git a/src/lib/Tools/DumpProfile.svelte b/src/lib/Tools/DumpProfile.svelte
index 717814d2..a111028c 100644
--- a/src/lib/Tools/DumpProfile.svelte
+++ b/src/lib/Tools/DumpProfile.svelte
@@ -1,30 +1,30 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { dumpUser } from '$lib/Data/AniList/user';
- import RateLimited from '$lib/Error/RateLimited.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import InputTemplate from './InputTemplate.svelte';
- import LZString from 'lz-string';
-
- let submission = '';
-
- // Credit: @hoh
- const decodeJSON = (about: string): JSON | null => {
- const match = (about || '').match(/^\[\]\(json([A-Za-z0-9+/=]+)\)/);
-
- if (match)
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { dumpUser } from '$lib/Data/AniList/user';
+import RateLimited from '$lib/Error/RateLimited.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import InputTemplate from './InputTemplate.svelte';
+import LZString from 'lz-string';
+
+let submission = '';
+
+// Credit: @hoh
+const decodeJSON = (about: string): JSON | null => {
+ const match = (about || '').match(/^\[\]\(json([A-Za-z0-9+/=]+)\)/);
+
+ if (match)
+ try {
+ return JSON.parse(atob(match[1]));
+ } catch {
try {
- return JSON.parse(atob(match[1]));
+ return JSON.parse(LZString.decompressFromBase64(match[1]));
} catch {
- try {
- return JSON.parse(LZString.decompressFromBase64(match[1]));
- } catch {
- return null;
- }
+ return null;
}
+ }
- return null;
- };
+ return null;
+};
</script>
<InputTemplate field="Username" bind:submission event="Dump User" submitText="Dump">
diff --git a/src/lib/Tools/EpisodeDiscussionCollector.svelte b/src/lib/Tools/EpisodeDiscussionCollector.svelte
index 71e13add..99c53f20 100644
--- a/src/lib/Tools/EpisodeDiscussionCollector.svelte
+++ b/src/lib/Tools/EpisodeDiscussionCollector.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { threads } from '$lib/Data/AniList/forum';
- import { onMount } from 'svelte';
- import { clearAllParameters } from '../Utility/parameters';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import InputTemplate from './InputTemplate.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { threads } from '$lib/Data/AniList/forum';
+import { onMount } from 'svelte';
+import { clearAllParameters } from '../Utility/parameters';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import InputTemplate from './InputTemplate.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
- let submission = '';
+let submission = '';
- onMount(clearAllParameters);
+onMount(clearAllParameters);
</script>
<InputTemplate
diff --git a/src/lib/Tools/FollowFix.svelte b/src/lib/Tools/FollowFix.svelte
index 62548379..a0e9902a 100644
--- a/src/lib/Tools/FollowFix.svelte
+++ b/src/lib/Tools/FollowFix.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import { toggleFollow } from '$lib/Data/AniList/follow';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+import { toggleFollow } from '$lib/Data/AniList/follow';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
- export let user: AniListAuthorisation;
+export let user: AniListAuthorisation;
- let input = '';
- let submit = '';
+let input = '';
+let submit = '';
</script>
{#if user === undefined}
diff --git a/src/lib/Tools/Hayai.svelte b/src/lib/Tools/Hayai.svelte
index 1ba99d86..aa1112da 100644
--- a/src/lib/Tools/Hayai.svelte
+++ b/src/lib/Tools/Hayai.svelte
@@ -1,83 +1,83 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { onMount } from 'svelte';
- import JSZip from 'jszip';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { onMount } from 'svelte';
+import JSZip from 'jszip';
- let fileInput: HTMLInputElement | null = null;
+let fileInput: HTMLInputElement | null = null;
- const handleFileUpload = async () => {
- if (!fileInput || !fileInput.files || fileInput.files.length === 0) return;
+const handleFileUpload = async () => {
+ if (!fileInput || !fileInput.files || fileInput.files.length === 0) return;
- const file = fileInput.files[0];
- const reader = new FileReader();
+ const file = fileInput.files[0];
+ const reader = new FileReader();
- reader.onload = async (event) => {
- const zip = await JSZip.loadAsync((event.target as FileReader).result as ArrayBuffer);
- const newZip = new JSZip();
+ reader.onload = async (event) => {
+ const zip = await JSZip.loadAsync((event.target as FileReader).result as ArrayBuffer);
+ const newZip = new JSZip();
- for (const relativePath in zip.files) {
- const zipEntry = zip.files[relativePath];
+ for (const relativePath in zip.files) {
+ const zipEntry = zip.files[relativePath];
- if (zipEntry.dir) {
- newZip.folder(relativePath);
- } else if (relativePath.endsWith('.xhtml') || relativePath.endsWith('.html')) {
- newZip.file(relativePath, applyBionicReadingToHTML(await zipEntry.async('text')));
- } else {
- newZip.file(relativePath, await zipEntry.async('arraybuffer'));
- }
+ if (zipEntry.dir) {
+ newZip.folder(relativePath);
+ } else if (relativePath.endsWith('.xhtml') || relativePath.endsWith('.html')) {
+ newZip.file(relativePath, applyBionicReadingToHTML(await zipEntry.async('text')));
+ } else {
+ newZip.file(relativePath, await zipEntry.async('arraybuffer'));
}
+ }
- downloadEPUB(
- await newZip.generateAsync({ type: 'blob' }),
- `${file.name.split('.epub')[0]}_hayai.epub`
- );
- };
-
- reader.readAsArrayBuffer(file);
+ downloadEPUB(
+ await newZip.generateAsync({ type: 'blob' }),
+ `${file.name.split('.epub')[0]}_hayai.epub`
+ );
};
- const applyBionicReadingToHTML = (content: string) => {
- const contentParser = new DOMParser().parseFromString(content, 'text/html');
+ reader.readAsArrayBuffer(file);
+};
- for (const paragraph of contentParser.getElementsByTagName('p'))
- paragraph.innerHTML = applyBionicReadingToString(paragraph.textContent ?? '');
+const applyBionicReadingToHTML = (content: string) => {
+ const contentParser = new DOMParser().parseFromString(content, 'text/html');
- return contentParser.documentElement.outerHTML;
- };
+ for (const paragraph of contentParser.getElementsByTagName('p'))
+ paragraph.innerHTML = applyBionicReadingToString(paragraph.textContent ?? '');
- const applyBionicReadingToString = (text: string) =>
- text
- .split(/\s+/)
- .map((word) => {
- if (/^\W+$/.test(word) || word.length <= 2) return word;
+ return contentParser.documentElement.outerHTML;
+};
- let boldLength;
+const applyBionicReadingToString = (text: string) =>
+ text
+ .split(/\s+/)
+ .map((word) => {
+ if (/^\W+$/.test(word) || word.length <= 2) return word;
- if (word.length <= 4) {
- boldLength = 2;
- } else if (word.length <= 7) {
- boldLength = 3;
- } else if (word.length <= 10) {
- boldLength = 4;
- } else {
- boldLength = Math.ceil(word.length * 0.5);
- }
+ let boldLength;
- return `<strong>${word.slice(0, boldLength)}</strong>${word.slice(boldLength)}`;
- })
- .join(' ');
+ if (word.length <= 4) {
+ boldLength = 2;
+ } else if (word.length <= 7) {
+ boldLength = 3;
+ } else if (word.length <= 10) {
+ boldLength = 4;
+ } else {
+ boldLength = Math.ceil(word.length * 0.5);
+ }
- const downloadEPUB = (blob: Blob | MediaSource, fileName: string) => {
- const link = document.createElement('a');
+ return `<strong>${word.slice(0, boldLength)}</strong>${word.slice(boldLength)}`;
+ })
+ .join(' ');
- link.href = URL.createObjectURL(blob);
- link.download = fileName;
+const downloadEPUB = (blob: Blob | MediaSource, fileName: string) => {
+ const link = document.createElement('a');
- link.click();
- URL.revokeObjectURL(link.href);
- };
+ link.href = URL.createObjectURL(blob);
+ link.download = fileName;
+
+ link.click();
+ URL.revokeObjectURL(link.href);
+};
- onMount(() => (fileInput = document.getElementById('epub-file') as HTMLInputElement));
+onMount(() => (fileInput = document.getElementById('epub-file') as HTMLInputElement));
</script>
<div class="card">
diff --git a/src/lib/Tools/InputTemplate.svelte b/src/lib/Tools/InputTemplate.svelte
index 48913646..af77b296 100644
--- a/src/lib/Tools/InputTemplate.svelte
+++ b/src/lib/Tools/InputTemplate.svelte
@@ -1,26 +1,26 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { clearAllParameters } from '$lib/Utility/parameters';
- import { onMount } from 'svelte';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { clearAllParameters } from '$lib/Utility/parameters';
+import { onMount } from 'svelte';
+import SettingHint from '$lib/Settings/SettingHint.svelte';
- export let field: string;
- export let submission: string;
- export let event: string | undefined = undefined;
- export let submitText: string;
- export let saveParameters: string[] = [];
- export let onSubmit = () => {
- return;
- };
- export let preserveCase = false;
- export let prompt = `Enter a ${
- preserveCase ? field : field.toLowerCase()
- } to search for to continue.`;
- export let hint: string | undefined = undefined;
+export let field: string;
+export let submission: string;
+export let event: string | undefined = undefined;
+export let submitText: string;
+export let saveParameters: string[] = [];
+export let onSubmit = () => {
+ return;
+};
+export let preserveCase = false;
+export let prompt = `Enter a ${
+ preserveCase ? field : field.toLowerCase()
+} to search for to continue.`;
+export let hint: string | undefined = undefined;
- let input = '';
+let input = '';
- onMount(() => clearAllParameters(saveParameters));
+onMount(() => clearAllParameters(saveParameters));
</script>
<div class="card">
diff --git a/src/lib/Tools/Likes.svelte b/src/lib/Tools/Likes.svelte
index 46d1edaf..d8545c39 100644
--- a/src/lib/Tools/Likes.svelte
+++ b/src/lib/Tools/Likes.svelte
@@ -1,22 +1,22 @@
<script lang="ts">
- import { activityLikes } from '$lib/Data/AniList/activity';
- import { threadLikes } from '$lib/Data/AniList/forum';
- import RateLimited from '$lib/Error/RateLimited.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import settings from '$stores/settings';
- import InputTemplate from './InputTemplate.svelte';
+import { activityLikes } from '$lib/Data/AniList/activity';
+import { threadLikes } from '$lib/Data/AniList/forum';
+import RateLimited from '$lib/Error/RateLimited.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+import settings from '$stores/settings';
+import InputTemplate from './InputTemplate.svelte';
- let submission = '';
+let submission = '';
- $: normalisedSubmission = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
- $: submissionType = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
- $: likesPromise =
- submissionType === 'activity'
- ? activityLikes(Number(normalisedSubmission))
- : submissionType === 'thread'
- ? threadLikes(Number(normalisedSubmission))
- : Promise.resolve(null);
+$: normalisedSubmission = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
+$: submissionType = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
+$: likesPromise =
+ submissionType === 'activity'
+ ? activityLikes(Number(normalisedSubmission))
+ : submissionType === 'thread'
+ ? threadLikes(Number(normalisedSubmission))
+ : Promise.resolve(null);
</script>
<InputTemplate
diff --git a/src/lib/Tools/Picker.svelte b/src/lib/Tools/Picker.svelte
index dab74599..df11eef5 100644
--- a/src/lib/Tools/Picker.svelte
+++ b/src/lib/Tools/Picker.svelte
@@ -1,10 +1,10 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { goto } from '$app/navigation';
- import root from '$lib/Utility/root';
- import { tools } from './tools';
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import root from '$lib/Utility/root';
+import { tools } from './tools';
- export let tool: string;
+export let tool: string;
</script>
<blockquote>
diff --git a/src/lib/Tools/RandomFollower.svelte b/src/lib/Tools/RandomFollower.svelte
index 34a9b48e..628f1b35 100644
--- a/src/lib/Tools/RandomFollower.svelte
+++ b/src/lib/Tools/RandomFollower.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { followers } from '$lib/Data/AniList/following';
- import RateLimited from '$lib/Error/RateLimited.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import TextSwap from '$lib/Layout/TextTransition.svelte';
- import InputTemplate from './InputTemplate.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { followers } from '$lib/Data/AniList/following';
+import RateLimited from '$lib/Error/RateLimited.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import TextSwap from '$lib/Layout/TextTransition.svelte';
+import InputTemplate from './InputTemplate.svelte';
- let submission = '';
- let randomSeed = 0;
+let submission = '';
+let randomSeed = 0;
</script>
<InputTemplate
diff --git a/src/lib/Tools/SequelCatcher/List.svelte b/src/lib/Tools/SequelCatcher/List.svelte
index a7e03ed0..79e28703 100644
--- a/src/lib/Tools/SequelCatcher/List.svelte
+++ b/src/lib/Tools/SequelCatcher/List.svelte
@@ -1,28 +1,28 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { filterRelations, type Media } from '$lib/Data/AniList/media';
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import { outboundLink } from '$lib/Media/links';
- import settings from '$stores/settings';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { filterRelations, type Media } from '$lib/Data/AniList/media';
+import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+import { outboundLink } from '$lib/Media/links';
+import settings from '$stores/settings';
- export let mediaListUnchecked: Media[];
+export let mediaListUnchecked: Media[];
- let includeCurrent = false;
- let includeSideStories = false;
+let includeCurrent = false;
+let includeSideStories = false;
- const matchCheck = (media: Media | undefined, swap = false) =>
- (media &&
- media.mediaListEntry &&
- media.mediaListEntry?.status !== 'CURRENT' &&
- media.mediaListEntry?.status !== 'REPEATING' &&
- media.mediaListEntry?.status !== 'PAUSED') ||
- !media
- ? swap
- ? undefined
- : media
- : swap
- ? media
- : undefined;
+const matchCheck = (media: Media | undefined, swap = false) =>
+ (media &&
+ media.mediaListEntry &&
+ media.mediaListEntry?.status !== 'CURRENT' &&
+ media.mediaListEntry?.status !== 'REPEATING' &&
+ media.mediaListEntry?.status !== 'PAUSED') ||
+ !media
+ ? swap
+ ? undefined
+ : media
+ : swap
+ ? media
+ : undefined;
</script>
<input type="checkbox" bind:checked={includeCurrent} /> Include current (watching, rewatching,
diff --git a/src/lib/Tools/SequelCatcher/Tool.svelte b/src/lib/Tools/SequelCatcher/Tool.svelte
index 05227ac4..01742d6f 100644
--- a/src/lib/Tools/SequelCatcher/Tool.svelte
+++ b/src/lib/Tools/SequelCatcher/Tool.svelte
@@ -1,39 +1,32 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import List from './List.svelte';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import userIdentity from '$stores/identity';
- import { type Media, mediaListCollection, Type } from '$lib/Data/AniList/media';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
- import anime from '$stores/anime';
- import identity from '$stores/identity';
- import { onMount } from 'svelte';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import Message from '$lib/Loading/Message.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Username from '$lib/Layout/Username.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import List from './List.svelte';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import userIdentity from '$stores/identity';
+import { type Media, mediaListCollection, Type } from '$lib/Data/AniList/media';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+import anime from '$stores/anime';
+import identity from '$stores/identity';
+import { onMount } from 'svelte';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import Message from '$lib/Loading/Message.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import Username from '$lib/Layout/Username.svelte';
- export let user: AniListAuthorisation;
+export let user: AniListAuthorisation;
- let mediaList: Promise<Media[]>;
+let mediaList: Promise<Media[]>;
- onMount(async () => {
- if (user === undefined || $identity.id === -2) return;
+onMount(async () => {
+ if (user === undefined || $identity.id === -2) return;
- mediaList = mediaListCollection(
- user,
- $userIdentity,
- Type.Anime,
- $anime,
- $lastPruneTimes.anime,
- {
- forcePrune: true,
- includeCompleted: true,
- all: true,
- includeRelations: true
- }
- );
+ mediaList = mediaListCollection(user, $userIdentity, Type.Anime, $anime, $lastPruneTimes.anime, {
+ forcePrune: true,
+ includeCompleted: true,
+ all: true,
+ includeRelations: true
});
+});
</script>
{#if user === undefined || $identity.id === -2}
diff --git a/src/lib/Tools/SequelSpy/Prequels.svelte b/src/lib/Tools/SequelSpy/Prequels.svelte
index b22db3af..5929821e 100644
--- a/src/lib/Tools/SequelSpy/Prequels.svelte
+++ b/src/lib/Tools/SequelSpy/Prequels.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import type { MediaPrequel } from '$lib/Data/AniList/prequels';
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import { airingTime } from '$lib/Media/Anime/Airing/time';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import settings from '$stores/settings';
- import type { Media } from '$lib/Data/AniList/media';
+import type { MediaPrequel } from '$lib/Data/AniList/prequels';
+import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+import { airingTime } from '$lib/Media/Anime/Airing/time';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import settings from '$stores/settings';
+import type { Media } from '$lib/Data/AniList/media';
- export let currentPrequels: MediaPrequel[];
+export let currentPrequels: MediaPrequel[];
- const prequelAiringTime = (prequel: MediaPrequel) =>
- airingTime(prequel as unknown as Media, null, false, true);
+const prequelAiringTime = (prequel: MediaPrequel) =>
+ airingTime(prequel as unknown as Media, null, false, true);
</script>
<ul>
diff --git a/src/lib/Tools/SequelSpy/Tool.svelte b/src/lib/Tools/SequelSpy/Tool.svelte
index 998cab13..0a862984 100644
--- a/src/lib/Tools/SequelSpy/Tool.svelte
+++ b/src/lib/Tools/SequelSpy/Tool.svelte
@@ -1,38 +1,38 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { prequels, type MediaPrequel } from '$lib/Data/AniList/prequels';
- import { onMount } from 'svelte';
- import { clearAllParameters, parseOrDefault } from '../../Utility/parameters';
- import { page } from '$app/stores';
- import { browser } from '$app/environment';
- import { season as getSeason } from '$lib/Media/Anime/season';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import identity from '$stores/identity';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
- import Prequels from './Prequels.svelte';
-
- export let user: AniListAuthorisation;
-
- let currentPrequels: Promise<MediaPrequel[]> = Promise.resolve([]) as Promise<MediaPrequel[]>;
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- let year = parseOrDefault(urlParameters, 'year', new Date().getFullYear());
- let season = parseOrDefault(urlParameters, 'season', getSeason());
-
- $: {
- if (year.toString().length === 4 && $identity.id !== -2 && user)
- currentPrequels = prequels(user, year, season);
- }
- $: {
- if (browser) {
- $page.url.searchParams.set('year', year.toString());
- $page.url.searchParams.set('season', season.toString());
- clearAllParameters(['year', 'season']);
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
+import Spacer from '$lib/Layout/Spacer.svelte';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { prequels, type MediaPrequel } from '$lib/Data/AniList/prequels';
+import { onMount } from 'svelte';
+import { clearAllParameters, parseOrDefault } from '../../Utility/parameters';
+import { page } from '$app/stores';
+import { browser } from '$app/environment';
+import { season as getSeason } from '$lib/Media/Anime/season';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import identity from '$stores/identity';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+import Prequels from './Prequels.svelte';
+
+export let user: AniListAuthorisation;
+
+let currentPrequels: Promise<MediaPrequel[]> = Promise.resolve([]) as Promise<MediaPrequel[]>;
+const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+let year = parseOrDefault(urlParameters, 'year', new Date().getFullYear());
+let season = parseOrDefault(urlParameters, 'season', getSeason());
+
+$: {
+ if (year.toString().length === 4 && $identity.id !== -2 && user)
+ currentPrequels = prequels(user, year, season);
+}
+$: {
+ if (browser) {
+ $page.url.searchParams.set('year', year.toString());
+ $page.url.searchParams.set('season', season.toString());
+ clearAllParameters(['year', 'season']);
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
+}
- onMount(() => clearAllParameters(['year', 'season']));
+onMount(() => clearAllParameters(['year', 'season']));
</script>
{#if user === undefined || $identity.id === -2}
diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte
index a3705fbe..1849cbb1 100644
--- a/src/lib/Tools/Tracker/Tool.svelte
+++ b/src/lib/Tools/Tracker/Tool.svelte
@@ -1,70 +1,70 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { v6 as uuidv6 } from 'uuid';
- import { database, type TrackerEntry } from '$lib/Database/IDB/tracker';
- import { onMount } from 'svelte';
- import Message from '$lib/Loading/Message.svelte';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { v6 as uuidv6 } from 'uuid';
+import { database, type TrackerEntry } from '$lib/Database/IDB/tracker';
+import { onMount } from 'svelte';
+import Message from '$lib/Loading/Message.svelte';
- let url = '';
- let title = '';
- let progress = 0;
- let error = '';
- let masterList: TrackerEntry[] | null = null;
- let confirmDelete = 0;
+let url = '';
+let title = '';
+let progress = 0;
+let error = '';
+let masterList: TrackerEntry[] | null = null;
+let confirmDelete = 0;
- $: listAccess = masterList || [];
+$: listAccess = masterList || [];
- onMount(async () => {
- masterList = await database.entries.toArray();
- });
+onMount(async () => {
+ masterList = await database.entries.toArray();
+});
- const adjustEntry = (id: string, to: number) => {
- const entry = listAccess.find((entry) => entry.id === id);
+const adjustEntry = (id: string, to: number) => {
+ const entry = listAccess.find((entry) => entry.id === id);
- if (!entry) return;
+ if (!entry) return;
- entry.progress = Math.max(0, to);
+ entry.progress = Math.max(0, to);
- database.entries.update(id, { progress: entry.progress });
+ database.entries.update(id, { progress: entry.progress });
- masterList = listAccess.map((entry) =>
- entry.id === id ? { ...entry, progress: entry.progress } : entry
- );
- };
+ masterList = listAccess.map((entry) =>
+ entry.id === id ? { ...entry, progress: entry.progress } : entry
+ );
+};
- const addEntry = async (url: string, title: string, progress: number) => {
- if (!url || !title) {
- error = 'URL and title are required fields';
+const addEntry = async (url: string, title: string, progress: number) => {
+ if (!url || !title) {
+ error = 'URL and title are required fields';
- return;
- }
+ return;
+ }
- if (listAccess.some((entry) => entry.url === url)) {
- error =
- 'Entry with URL already exists: ' + listAccess.find((entry) => entry.url === url)?.title;
+ if (listAccess.some((entry) => entry.url === url)) {
+ error =
+ 'Entry with URL already exists: ' + listAccess.find((entry) => entry.url === url)?.title;
- return;
- }
+ return;
+ }
- await database.entries.add({ url, title, progress, id: uuidv6() });
+ await database.entries.add({ url, title, progress, id: uuidv6() });
- masterList = await database.entries.toArray();
- };
+ masterList = await database.entries.toArray();
+};
- const deleteEntry = async (id: string) => {
- if (confirmDelete !== 1) {
- confirmDelete = 1;
- error = 'Click again to confirm deletion';
+const deleteEntry = async (id: string) => {
+ if (confirmDelete !== 1) {
+ confirmDelete = 1;
+ error = 'Click again to confirm deletion';
- return;
- }
+ return;
+ }
- await database.entries.delete(id);
+ await database.entries.delete(id);
- masterList = await database.entries.toArray();
- confirmDelete = 0;
- error = '';
- };
+ masterList = await database.entries.toArray();
+ confirmDelete = 0;
+ error = '';
+};
</script>
<div class="card">
diff --git a/src/lib/Tools/UmaMusumeBirthdays.svelte b/src/lib/Tools/UmaMusumeBirthdays.svelte
index 8ee6697b..cc8c1513 100644
--- a/src/lib/Tools/UmaMusumeBirthdays.svelte
+++ b/src/lib/Tools/UmaMusumeBirthdays.svelte
@@ -1,51 +1,51 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { page } from '$app/stores';
- import Error from '$lib/Error/RateLimited.svelte';
- import { onMount } from 'svelte';
- import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import settings from '$stores/settings';
- import locale from '$stores/locale';
+import { browser } from '$app/environment';
+import { page } from '$app/stores';
+import Error from '$lib/Error/RateLimited.svelte';
+import { onMount } from 'svelte';
+import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import Message from '$lib/Loading/Message.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+import settings from '$stores/settings';
+import locale from '$stores/locale';
- interface Birthday {
- birth_day: number;
- birth_month: number;
- game_id: number;
- id: number;
- name_en: string;
- name_jp: string;
- preferred_url: string;
- sns_icon: string;
- }
+interface Birthday {
+ birth_day: number;
+ birth_month: number;
+ game_id: number;
+ id: number;
+ name_en: string;
+ name_jp: string;
+ preferred_url: string;
+ sns_icon: string;
+}
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- let date = new Date();
- let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
- let day = parseOrDefault(urlParameters, 'day', date.getDate());
- let umapyoi: Promise<Birthday[]>;
+const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+let date = new Date();
+let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
+let day = parseOrDefault(urlParameters, 'day', date.getDate());
+let umapyoi: Promise<Birthday[]>;
- $: {
- month = Math.min(month, 12);
- month = Math.max(month, 1);
- day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate());
- day = Math.max(day, 1);
+$: {
+ month = Math.min(month, 12);
+ month = Math.max(month, 1);
+ day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate());
+ day = Math.max(day, 1);
- if (browser) {
- $page.url.searchParams.set('month', month.toString());
- $page.url.searchParams.set('day', day.toString());
- clearAllParameters(['month', 'day']);
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
+ if (browser) {
+ $page.url.searchParams.set('month', month.toString());
+ $page.url.searchParams.set('day', day.toString());
+ clearAllParameters(['month', 'day']);
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
+}
- onMount(() => {
- clearAllParameters(['month', 'day']);
+onMount(() => {
+ clearAllParameters(['month', 'day']);
- umapyoi = fetch('https://umapyoi.net/api/v1/character/birthday').then((r) => r.json());
- });
+ umapyoi = fetch('https://umapyoi.net/api/v1/character/birthday').then((r) => r.json());
+});
</script>
{#await umapyoi}
diff --git a/src/lib/Tools/Wrapped/ActivityHistory.svelte b/src/lib/Tools/Wrapped/ActivityHistory.svelte
index 3da401d4..f1f1a28a 100644
--- a/src/lib/Tools/Wrapped/ActivityHistory.svelte
+++ b/src/lib/Tools/Wrapped/ActivityHistory.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
- import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import ActivityHistoryGrid from '../ActivityHistory/Grid.svelte';
+import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import ActivityHistoryGrid from '../ActivityHistory/Grid.svelte';
- export let user: AniListAuthorisation;
- export let activities: ActivityHistoryEntry[];
- export let year: number;
- export let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL';
+export let user: AniListAuthorisation;
+export let activities: ActivityHistoryEntry[];
+export let year: number;
+export let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL';
</script>
<div
diff --git a/src/lib/Tools/Wrapped/DataLoader.svelte b/src/lib/Tools/Wrapped/DataLoader.svelte
index 9c99b4a4..6063f349 100644
--- a/src/lib/Tools/Wrapped/DataLoader.svelte
+++ b/src/lib/Tools/Wrapped/DataLoader.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- import { onMount } from 'svelte';
+import { onMount } from 'svelte';
- export let onLoad: () => void;
+export let onLoad: () => void;
- onMount(() => onLoad());
+onMount(() => onLoad());
</script>
diff --git a/src/lib/Tools/Wrapped/Media.svelte b/src/lib/Tools/Wrapped/Media.svelte
index 90e0cde3..bbd7ee5b 100644
--- a/src/lib/Tools/Wrapped/Media.svelte
+++ b/src/lib/Tools/Wrapped/Media.svelte
@@ -1,17 +1,17 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import type { Wrapped } from '$lib/Data/AniList/wrapped';
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import proxy from '$lib/Utility/proxy';
+import type { Media } from '$lib/Data/AniList/media';
+import type { Wrapped } from '$lib/Data/AniList/wrapped';
+import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+import proxy from '$lib/Utility/proxy';
- export let animeList: Media[] | undefined;
- export let mangaList: Media[] | undefined;
- export let wrapped: Wrapped;
- export let updateWidth: () => void;
- export let highestRatedMediaPercentage: boolean;
- export let highestRatedCount: number;
- export let animeMostTitle: string;
- export let mangaMostTitle: string;
+export let animeList: Media[] | undefined;
+export let mangaList: Media[] | undefined;
+export let wrapped: Wrapped;
+export let updateWidth: () => void;
+export let highestRatedMediaPercentage: boolean;
+export let highestRatedCount: number;
+export let animeMostTitle: string;
+export let mangaMostTitle: string;
</script>
{#if animeList !== undefined || mangaList !== undefined}
diff --git a/src/lib/Tools/Wrapped/MediaExtras.svelte b/src/lib/Tools/Wrapped/MediaExtras.svelte
index 78b89e36..d9dc8efb 100644
--- a/src/lib/Tools/Wrapped/MediaExtras.svelte
+++ b/src/lib/Tools/Wrapped/MediaExtras.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
- import type { TopMedia } from '$lib/Data/AniList/wrapped';
- import proxy from '$lib/Utility/proxy';
+import type { TopMedia } from '$lib/Data/AniList/wrapped';
+import proxy from '$lib/Utility/proxy';
- export let topMedia: TopMedia;
- export let updateWidth: () => void;
- export let highestRatedGenreTagPercentage: boolean;
- export let genreTagTitle: string;
+export let topMedia: TopMedia;
+export let updateWidth: () => void;
+export let highestRatedGenreTagPercentage: boolean;
+export let genreTagTitle: string;
</script>
<div class="categories-grid" style="padding-top: 0;">
diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte
index 3985b4c0..3f13b483 100644
--- a/src/lib/Tools/Wrapped/Tool.svelte
+++ b/src/lib/Tools/Wrapped/Tool.svelte
@@ -1,748 +1,730 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import './wrapped.css';
- import userIdentity from '$stores/identity';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import { onMount } from 'svelte';
- import {
- tops,
- wrapped,
- type TopMedia,
- SortOptions,
- type Wrapped
- } from '$lib/Data/AniList/wrapped';
- import {
- fullActivityHistory,
- activityHistory as getActivityHistory,
- type ActivityHistoryEntry
- } from '$lib/Data/AniList/activity';
- import { Type, mediaListCollection, type Media } from '$lib/Data/AniList/media';
- import anime from '$stores/anime';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import manga from '$stores/manga';
- import Error from '$lib/Error/RateLimited.svelte';
- import { domToBlob } from 'modern-screenshot';
- import { browser } from '$app/environment';
- import { page } from '$app/stores';
- import { clearAllParameters } from '../../Utility/parameters';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
- import { database } from '$lib/Database/IDB/activities';
- import Activity from './Top/Activity.svelte';
- import Anime from './Top/Anime.svelte';
- import Manga from './Top/Manga.svelte';
- import ActivityHistory from './ActivityHistory.svelte';
- import MediaExtras from './MediaExtras.svelte';
- import MediaPanel from './Media.svelte';
- import Watermark from './Watermark.svelte';
- import DataLoader from './DataLoader.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
-
- export let user: AniListAuthorisation;
-
- const currentYear = new Date(Date.now()).getFullYear();
- let selectedYear = new Date(Date.now()).getFullYear();
- let episodes = 0;
- let chapters = 0;
- let minutesWatched = 0;
- let animeList: Media[] | undefined = undefined;
- let mangaList: Media[] | undefined = undefined;
- let calculatedAnimeList: Media[] | undefined = undefined;
- let calculatedMangaList: Media[] | undefined = undefined;
- let originalAnimeList: Media[] | undefined = undefined;
- let originalMangaList: Media[] | undefined = undefined;
- let transparency = false;
- let lightTheme = true;
- let watermark = false;
- let includeMusic = false;
- let includeSpecials = true;
- let includeRepeats = false;
- let width = 1920;
- let lightMode = false;
- let highestRatedCount = 5;
- let genreTagCount = 5;
- let mounted = false;
- let generated = false;
- let disableActivityHistory = true;
- let excludedKeywordsInput = '';
- let excludedKeywords: string[] = [];
- let useFullActivityHistory = false;
- let disableLoopingActivityCounter = false;
- let topGenresTags = true;
- let topMedia: TopMedia;
- let highestRatedMediaPercentage = true;
- let highestRatedGenreTagPercentage = true;
- let genreTagsSort = SortOptions.SCORE;
- let mediaSort = SortOptions.SCORE;
- let includeMovies = true;
- let includeOVAs = true;
- let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = 'ORIGINAL';
- let includeOngoingMediaFromPreviousYears = false;
- let excludeUnratedUnwatched = true;
- let startDateFilter: Date | null = null;
- let endDateFilter: Date | null = null;
- let dateTicked = false;
- let shouldFetchData = false;
- let needsRefetch = false;
- let dataFetched = false;
- let fetchKey = 0;
- let lastSelectedYear = selectedYear;
- let lastUseFullActivityHistory = useFullActivityHistory;
- let lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
- let lastStartDateFilter: Date | null = startDateFilter;
- let lastEndDateFilter: Date | null = endDateFilter;
-
- $: {
- if (browser && mounted) {
- $page.url.searchParams.set('transparency', transparency.toString());
- $page.url.searchParams.set('lightTheme', lightTheme.toString());
- $page.url.searchParams.set('watermark', watermark.toString());
- $page.url.searchParams.set('includeMusic', includeMusic.toString());
- $page.url.searchParams.set('includeSpecials', includeSpecials.toString());
- $page.url.searchParams.set('includeRepeats', includeRepeats.toString());
- $page.url.searchParams.set('lightMode', lightMode.toString());
- $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString());
- $page.url.searchParams.set('genreTagCount', genreTagCount.toString());
- $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString());
- $page.url.searchParams.set(
- 'highestRatedMediaPercentage',
- highestRatedMediaPercentage.toString()
- );
- $page.url.searchParams.set(
- 'highestRatedGenreTagPercentage',
- highestRatedGenreTagPercentage.toString()
- );
- $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString());
- $page.url.searchParams.set('mediaSort', mediaSort.toString());
- $page.url.searchParams.set('includeMovies', includeMovies.toString());
- $page.url.searchParams.set('includeOVAs', includeOVAs.toString());
- $page.url.searchParams.set('excludeUnratedUnwatched', excludeUnratedUnwatched.toString());
- $page.url.searchParams.set(
- 'disableLoopingActivityCounter',
- disableLoopingActivityCounter.toString()
- );
-
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
- }
+import Spacer from '$lib/Layout/Spacer.svelte';
+import './wrapped.css';
+import userIdentity from '$stores/identity';
+import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+import { onMount } from 'svelte';
+import { tops, wrapped, type TopMedia, SortOptions, type Wrapped } from '$lib/Data/AniList/wrapped';
+import {
+ fullActivityHistory,
+ activityHistory as getActivityHistory,
+ type ActivityHistoryEntry
+} from '$lib/Data/AniList/activity';
+import { Type, mediaListCollection, type Media } from '$lib/Data/AniList/media';
+import anime from '$stores/anime';
+import lastPruneTimes from '$stores/lastPruneTimes';
+import manga from '$stores/manga';
+import Error from '$lib/Error/RateLimited.svelte';
+import { domToBlob } from 'modern-screenshot';
+import { browser } from '$app/environment';
+import { page } from '$app/stores';
+import { clearAllParameters } from '../../Utility/parameters';
+import SettingHint from '$lib/Settings/SettingHint.svelte';
+import { database } from '$lib/Database/IDB/activities';
+import Activity from './Top/Activity.svelte';
+import Anime from './Top/Anime.svelte';
+import Manga from './Top/Manga.svelte';
+import ActivityHistory from './ActivityHistory.svelte';
+import MediaExtras from './MediaExtras.svelte';
+import MediaPanel from './Media.svelte';
+import Watermark from './Watermark.svelte';
+import DataLoader from './DataLoader.svelte';
+import Skeleton from '$lib/Loading/Skeleton.svelte';
+import Message from '$lib/Loading/Message.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+
+export let user: AniListAuthorisation;
+
+const currentYear = new Date(Date.now()).getFullYear();
+let selectedYear = new Date(Date.now()).getFullYear();
+let episodes = 0;
+let chapters = 0;
+let minutesWatched = 0;
+let animeList: Media[] | undefined = undefined;
+let mangaList: Media[] | undefined = undefined;
+let calculatedAnimeList: Media[] | undefined = undefined;
+let calculatedMangaList: Media[] | undefined = undefined;
+let originalAnimeList: Media[] | undefined = undefined;
+let originalMangaList: Media[] | undefined = undefined;
+let transparency = false;
+let lightTheme = true;
+let watermark = false;
+let includeMusic = false;
+let includeSpecials = true;
+let includeRepeats = false;
+let width = 1920;
+let lightMode = false;
+let highestRatedCount = 5;
+let genreTagCount = 5;
+let mounted = false;
+let generated = false;
+let disableActivityHistory = true;
+let excludedKeywordsInput = '';
+let excludedKeywords: string[] = [];
+let useFullActivityHistory = false;
+let disableLoopingActivityCounter = false;
+let topGenresTags = true;
+let topMedia: TopMedia;
+let highestRatedMediaPercentage = true;
+let highestRatedGenreTagPercentage = true;
+let genreTagsSort = SortOptions.SCORE;
+let mediaSort = SortOptions.SCORE;
+let includeMovies = true;
+let includeOVAs = true;
+let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = 'ORIGINAL';
+let includeOngoingMediaFromPreviousYears = false;
+let excludeUnratedUnwatched = true;
+let startDateFilter: Date | null = null;
+let endDateFilter: Date | null = null;
+let dateTicked = false;
+let shouldFetchData = false;
+let needsRefetch = false;
+let dataFetched = false;
+let fetchKey = 0;
+let lastSelectedYear = selectedYear;
+let lastUseFullActivityHistory = useFullActivityHistory;
+let lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
+let lastStartDateFilter: Date | null = startDateFilter;
+let lastEndDateFilter: Date | null = endDateFilter;
+
+$: {
+ if (browser && mounted) {
+ $page.url.searchParams.set('transparency', transparency.toString());
+ $page.url.searchParams.set('lightTheme', lightTheme.toString());
+ $page.url.searchParams.set('watermark', watermark.toString());
+ $page.url.searchParams.set('includeMusic', includeMusic.toString());
+ $page.url.searchParams.set('includeSpecials', includeSpecials.toString());
+ $page.url.searchParams.set('includeRepeats', includeRepeats.toString());
+ $page.url.searchParams.set('lightMode', lightMode.toString());
+ $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString());
+ $page.url.searchParams.set('genreTagCount', genreTagCount.toString());
+ $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString());
+ $page.url.searchParams.set(
+ 'highestRatedMediaPercentage',
+ highestRatedMediaPercentage.toString()
+ );
+ $page.url.searchParams.set(
+ 'highestRatedGenreTagPercentage',
+ highestRatedGenreTagPercentage.toString()
+ );
+ $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString());
+ $page.url.searchParams.set('mediaSort', mediaSort.toString());
+ $page.url.searchParams.set('includeMovies', includeMovies.toString());
+ $page.url.searchParams.set('includeOVAs', includeOVAs.toString());
+ $page.url.searchParams.set('excludeUnratedUnwatched', excludeUnratedUnwatched.toString());
+ $page.url.searchParams.set(
+ 'disableLoopingActivityCounter',
+ disableLoopingActivityCounter.toString()
+ );
- $: {
- if (dataFetched) {
- const yearChanged = selectedYear !== lastSelectedYear;
- const fullActivityChanged = useFullActivityHistory !== lastUseFullActivityHistory;
- const loopingChanged = disableLoopingActivityCounter !== lastDisableLoopingActivityCounter;
- const startDateChanged = startDateFilter !== lastStartDateFilter;
- const endDateChanged = endDateFilter !== lastEndDateFilter;
-
- if (
- yearChanged ||
- fullActivityChanged ||
- loopingChanged ||
- startDateChanged ||
- endDateChanged
- )
- needsRefetch = true;
- }
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
+}
+
+$: {
+ if (dataFetched) {
+ const yearChanged = selectedYear !== lastSelectedYear;
+ const fullActivityChanged = useFullActivityHistory !== lastUseFullActivityHistory;
+ const loopingChanged = disableLoopingActivityCounter !== lastDisableLoopingActivityCounter;
+ const startDateChanged = startDateFilter !== lastStartDateFilter;
+ const endDateChanged = endDateFilter !== lastEndDateFilter;
+
+ if (yearChanged || fullActivityChanged || loopingChanged || startDateChanged || endDateChanged)
+ needsRefetch = true;
+ }
+}
+
+$: {
+ includeMusic = includeMusic;
+ includeSpecials = includeSpecials;
+ includeRepeats = includeRepeats;
+ disableActivityHistory = disableActivityHistory;
+ highestRatedMediaPercentage = highestRatedMediaPercentage;
+ highestRatedGenreTagPercentage = highestRatedGenreTagPercentage;
+ topGenresTags = topGenresTags;
+ genreTagsSort = genreTagsSort;
+ mediaSort = mediaSort;
+ includeMovies = includeMovies;
+ includeOVAs = includeOVAs;
+ selectedYear = selectedYear;
+ includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears;
+ excludeUnratedUnwatched = excludeUnratedUnwatched;
+
+ if (shouldFetchData) update().then(updateWidth).catch(updateWidth);
+}
+$: {
+ animeList = animeList;
+ mangaList = mangaList;
+ highestRatedCount = highestRatedCount;
+
+ new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+}
+$: {
+ genreTagCount = genreTagCount;
+
+ if (animeList && mangaList)
+ topMedia = tops(
+ [...(animeList || []), ...(mangaList || [])],
+ genreTagCount,
+ genreTagsSort,
+ excludedKeywords
+ );
- $: {
- includeMusic = includeMusic;
- includeSpecials = includeSpecials;
- includeRepeats = includeRepeats;
- disableActivityHistory = disableActivityHistory;
- highestRatedMediaPercentage = highestRatedMediaPercentage;
- highestRatedGenreTagPercentage = highestRatedGenreTagPercentage;
- topGenresTags = topGenresTags;
- genreTagsSort = genreTagsSort;
- mediaSort = mediaSort;
- includeMovies = includeMovies;
- includeOVAs = includeOVAs;
- selectedYear = selectedYear;
- includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears;
- excludeUnratedUnwatched = excludeUnratedUnwatched;
-
- if (shouldFetchData) update().then(updateWidth).catch(updateWidth);
+ new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+}
+$: {
+ excludedKeywords = excludedKeywords;
+
+ if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) {
+ animeList = originalAnimeList;
+ mangaList = originalMangaList;
+ animeList = excludeKeywords(animeList as Media[]);
+ mangaList = excludeKeywords(mangaList as Media[]);
}
- $: {
- animeList = animeList;
- mangaList = mangaList;
- highestRatedCount = highestRatedCount;
- new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+ updateWidth();
+}
+$: genreTagTitle = (() => {
+ switch (genreTagsSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Watched';
+ case SortOptions.COUNT:
+ return 'Most Common';
}
- $: {
- genreTagCount = genreTagCount;
-
- if (animeList && mangaList)
- topMedia = tops(
- [...(animeList || []), ...(mangaList || [])],
- genreTagCount,
- genreTagsSort,
- excludedKeywords
- );
-
- new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+})();
+$: animeMostTitle = (() => {
+ switch (mediaSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Watched';
+ case SortOptions.COUNT:
+ return 'Most Common';
}
- $: {
- excludedKeywords = excludedKeywords;
-
- if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) {
- animeList = originalAnimeList;
- mangaList = originalMangaList;
- animeList = excludeKeywords(animeList as Media[]);
- mangaList = excludeKeywords(mangaList as Media[]);
- }
-
- updateWidth();
+})();
+$: mangaMostTitle = (() => {
+ switch (mediaSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Read';
+ case SortOptions.COUNT:
+ return 'Most Common';
}
- $: genreTagTitle = (() => {
- switch (genreTagsSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Watched';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
- $: animeMostTitle = (() => {
- switch (mediaSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Watched';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
- $: mangaMostTitle = (() => {
- switch (mediaSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Read';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
-
- const updateWidth = () => {
- if (!browser) return;
-
- const wrappedContainer = document.querySelector('#wrapped') as HTMLElement;
-
- if (!wrappedContainer) return;
-
- wrappedContainer.style.width = `1920px`;
-
- const reset = () => {
- let topWidths = 0;
- let middleWidths = 0;
- let bottomWidths = 0;
-
- wrappedContainer.querySelectorAll('.category').forEach((item) => {
- const category = item as HTMLElement;
- const style = window.getComputedStyle(category);
- const width =
- category.offsetWidth +
- parseFloat(style.marginLeft) +
- parseFloat(style.marginRight) +
- parseFloat(style.paddingLeft) +
- parseFloat(style.paddingRight) +
- parseFloat(style.borderLeftWidth) +
- parseFloat(style.borderRightWidth);
-
- if (category.classList.contains('top-category')) {
- topWidths += width;
- } else if (category.classList.contains('middle-category')) {
- middleWidths += width;
- } else if (category.classList.contains('bottom-category')) {
- bottomWidths += width;
- }
- });
-
- let requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
+})();
+
+const updateWidth = () => {
+ if (!browser) return;
+
+ const wrappedContainer = document.querySelector('#wrapped') as HTMLElement;
+
+ if (!wrappedContainer) return;
+
+ wrappedContainer.style.width = `1920px`;
+
+ const reset = () => {
+ let topWidths = 0;
+ let middleWidths = 0;
+ let bottomWidths = 0;
+
+ wrappedContainer.querySelectorAll('.category').forEach((item) => {
+ const category = item as HTMLElement;
+ const style = window.getComputedStyle(category);
+ const width =
+ category.offsetWidth +
+ parseFloat(style.marginLeft) +
+ parseFloat(style.marginRight) +
+ parseFloat(style.paddingLeft) +
+ parseFloat(style.paddingRight) +
+ parseFloat(style.borderLeftWidth) +
+ parseFloat(style.borderRightWidth);
+
+ if (category.classList.contains('top-category')) {
+ topWidths += width;
+ } else if (category.classList.contains('middle-category')) {
+ middleWidths += width;
+ } else if (category.classList.contains('bottom-category')) {
+ bottomWidths += width;
+ }
+ });
- if (!disableActivityHistory && bottomWidths > requiredWidth) requiredWidth = bottomWidths;
+ let requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
- requiredWidth += wrappedContainer.offsetWidth - wrappedContainer.clientWidth;
+ if (!disableActivityHistory && bottomWidths > requiredWidth) requiredWidth = bottomWidths;
- wrappedContainer.style.width = `${requiredWidth}px`;
- width = requiredWidth;
- };
+ requiredWidth += wrappedContainer.offsetWidth - wrappedContainer.clientWidth;
- reset();
- reset();
+ wrappedContainer.style.width = `${requiredWidth}px`;
+ width = requiredWidth;
};
- onMount(async () => {
- clearAllParameters([
- 'transparency',
- 'lightTheme',
- 'watermark',
- 'includeMusic',
- 'includeSpecials',
- 'includeRepeats',
- 'forceDark',
- 'highestRatedCount',
- 'genreTagCount',
- 'disableActivityHistory',
- 'highestRatedMediaPercentage',
- 'highestRatedGenreTagPercentage',
- 'genreTagsSort',
- 'mediaSort',
- 'includeMovies',
- 'includeOVAs',
- 'disableLoopingActivityCounter'
- ]);
-
- if (browser) {
- transparency = $page.url.searchParams.get('transparency') === 'true';
- lightTheme = $page.url.searchParams.get('lightTheme') === 'true';
- watermark = $page.url.searchParams.get('watermark') === 'true';
- includeMusic = $page.url.searchParams.get('includeMusic') === 'true';
- includeSpecials = $page.url.searchParams.get('includeSpecials') === 'true';
- includeRepeats = $page.url.searchParams.get('includeRepeats') === 'true';
- lightMode = $page.url.searchParams.get('lightMode') === 'true';
- highestRatedCount = parseInt($page.url.searchParams.get('highestRatedCount') || '5', 10);
- genreTagCount = parseInt($page.url.searchParams.get('genreTagCount') || '5', 10);
- disableActivityHistory = $page.url.searchParams.get('disableActivityHistory') === 'true';
- disableLoopingActivityCounter =
- $page.url.searchParams.get('disableLoopingActivityCounter') === 'true';
- highestRatedMediaPercentage =
- $page.url.searchParams.get('highestRatedMediaPercentage') === 'true';
- highestRatedGenreTagPercentage =
- $page.url.searchParams.get('highestRatedGenreTagPercentage') === 'true';
- // genreTagsSort = parseInt($page.url.searchParams.get('genreTagsSort') || '0', 10);
- // mediaSort = parseInt($page.url.searchParams.get('mediaSort') || '0', 10);
- includeMovies = $page.url.searchParams.get('includeMovies') === 'true';
- includeOVAs = $page.url.searchParams.get('includeOVAs') === 'true';
- }
-
- await update().then(() => (mounted = true));
- });
-
- const triggerFetch = () => {
- shouldFetchData = true;
- needsRefetch = false;
- dataFetched = true;
- fetchKey += 1;
- lastSelectedYear = selectedYear;
- lastUseFullActivityHistory = useFullActivityHistory;
- lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
- lastStartDateFilter = startDateFilter;
- lastEndDateFilter = endDateFilter;
- };
+ reset();
+ reset();
+};
+
+onMount(async () => {
+ clearAllParameters([
+ 'transparency',
+ 'lightTheme',
+ 'watermark',
+ 'includeMusic',
+ 'includeSpecials',
+ 'includeRepeats',
+ 'forceDark',
+ 'highestRatedCount',
+ 'genreTagCount',
+ 'disableActivityHistory',
+ 'highestRatedMediaPercentage',
+ 'highestRatedGenreTagPercentage',
+ 'genreTagsSort',
+ 'mediaSort',
+ 'includeMovies',
+ 'includeOVAs',
+ 'disableLoopingActivityCounter'
+ ]);
+
+ if (browser) {
+ transparency = $page.url.searchParams.get('transparency') === 'true';
+ lightTheme = $page.url.searchParams.get('lightTheme') === 'true';
+ watermark = $page.url.searchParams.get('watermark') === 'true';
+ includeMusic = $page.url.searchParams.get('includeMusic') === 'true';
+ includeSpecials = $page.url.searchParams.get('includeSpecials') === 'true';
+ includeRepeats = $page.url.searchParams.get('includeRepeats') === 'true';
+ lightMode = $page.url.searchParams.get('lightMode') === 'true';
+ highestRatedCount = parseInt($page.url.searchParams.get('highestRatedCount') || '5', 10);
+ genreTagCount = parseInt($page.url.searchParams.get('genreTagCount') || '5', 10);
+ disableActivityHistory = $page.url.searchParams.get('disableActivityHistory') === 'true';
+ disableLoopingActivityCounter =
+ $page.url.searchParams.get('disableLoopingActivityCounter') === 'true';
+ highestRatedMediaPercentage =
+ $page.url.searchParams.get('highestRatedMediaPercentage') === 'true';
+ highestRatedGenreTagPercentage =
+ $page.url.searchParams.get('highestRatedGenreTagPercentage') === 'true';
+ // genreTagsSort = parseInt($page.url.searchParams.get('genreTagsSort') || '0', 10);
+ // mediaSort = parseInt($page.url.searchParams.get('mediaSort') || '0', 10);
+ includeMovies = $page.url.searchParams.get('includeMovies') === 'true';
+ includeOVAs = $page.url.searchParams.get('includeOVAs') === 'true';
+ }
- const createDummyMedia = (type: 'ANIME' | 'MANGA'): Media => ({
- id: 0,
- idMal: 0,
- status: 'FINISHED',
- type,
- episodes: type === 'ANIME' ? 0 : 0,
- chapters: type === 'MANGA' ? 0 : 0,
- volumes: 0,
- duration: 0,
- format: type === 'ANIME' ? 'TV' : 'MANGA',
- title: {
- romaji: '...',
- english: '...',
- native: '...'
- },
- synonyms: [],
- mediaListEntry: {
- progress: 0,
- progressVolumes: 0,
- status: 'COMPLETED',
- score: 0,
- repeat: 0,
- startedAt: {
- year: 0,
- month: 0,
- day: 0
- },
- completedAt: {
- year: 0,
- month: 0,
- day: 0
- },
- createdAt: 0,
- updatedAt: 0,
- customLists: {}
- },
- startDate: {
+ await update().then(() => (mounted = true));
+});
+
+const triggerFetch = () => {
+ shouldFetchData = true;
+ needsRefetch = false;
+ dataFetched = true;
+ fetchKey += 1;
+ lastSelectedYear = selectedYear;
+ lastUseFullActivityHistory = useFullActivityHistory;
+ lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
+ lastStartDateFilter = startDateFilter;
+ lastEndDateFilter = endDateFilter;
+};
+
+const createDummyMedia = (type: 'ANIME' | 'MANGA'): Media => ({
+ id: 0,
+ idMal: 0,
+ status: 'FINISHED',
+ type,
+ episodes: type === 'ANIME' ? 0 : 0,
+ chapters: type === 'MANGA' ? 0 : 0,
+ volumes: 0,
+ duration: 0,
+ format: type === 'ANIME' ? 'TV' : 'MANGA',
+ title: {
+ romaji: '...',
+ english: '...',
+ native: '...'
+ },
+ synonyms: [],
+ mediaListEntry: {
+ progress: 0,
+ progressVolumes: 0,
+ status: 'COMPLETED',
+ score: 0,
+ repeat: 0,
+ startedAt: {
year: 0,
- month: 0
+ month: 0,
+ day: 0
},
- endDate: {
+ completedAt: {
year: 0,
- month: 0
+ month: 0,
+ day: 0
},
- coverImage: {
- extraLarge: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg',
- medium: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg'
+ createdAt: 0,
+ updatedAt: 0,
+ customLists: {}
+ },
+ startDate: {
+ year: 0,
+ month: 0
+ },
+ endDate: {
+ year: 0,
+ month: 0
+ },
+ coverImage: {
+ extraLarge: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg',
+ medium: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg'
+ },
+ tags: [],
+ genres: [],
+ season: 'WINTER',
+ isAdult: false,
+ relations: {
+ edges: []
+ }
+});
+
+const dummyWrapped: Wrapped = {
+ statistics: {
+ anime: {
+ startYears: [],
+ genres: [],
+ tags: []
},
- tags: [],
- genres: [],
- season: 'WINTER',
- isAdult: false,
- relations: {
- edges: []
+ manga: {
+ startYears: [],
+ genres: [],
+ tags: []
}
- });
-
- const dummyWrapped: Wrapped = {
- statistics: {
- anime: {
- startYears: [],
- genres: [],
- tags: []
- },
- manga: {
- startYears: [],
- genres: [],
- tags: []
- }
- },
- activities: {
- statusCount: 0,
- messageCount: 0
- },
- avatar: {
- large: 'https://s4.anilist.co/file/anilistcdn/user/avatar/large/3.jpg'
+ },
+ activities: {
+ statusCount: 0,
+ messageCount: 0
+ },
+ avatar: {
+ large: 'https://s4.anilist.co/file/anilistcdn/user/avatar/large/3.jpg'
+ }
+};
+
+const dummyActivities: ActivityHistoryEntry[] = [];
+const dummyAnimeList: Media[] = [createDummyMedia('ANIME')];
+const dummyMangaList: Media[] = [createDummyMedia('MANGA')];
+
+const update = async () => {
+ if ($userIdentity.id === -1) return;
+
+ let rawAnimeList = await mediaListCollection(
+ user,
+ $userIdentity,
+ Type.Anime,
+ $anime,
+ $lastPruneTimes.anime,
+ {
+ forcePrune: dateTicked ? false : true,
+ includeCompleted: true,
+ all: true
}
- };
-
- const dummyActivities: ActivityHistoryEntry[] = [];
- const dummyAnimeList: Media[] = [createDummyMedia('ANIME')];
- const dummyMangaList: Media[] = [createDummyMedia('MANGA')];
-
- const update = async () => {
- if ($userIdentity.id === -1) return;
-
- let rawAnimeList = await mediaListCollection(
- user,
- $userIdentity,
- Type.Anime,
- $anime,
- $lastPruneTimes.anime,
- {
- forcePrune: dateTicked ? false : true,
- includeCompleted: true,
- all: true
- }
- );
- calculatedAnimeList = rawAnimeList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeMusic ? true : item.format !== 'MUSIC') &&
- (includeRepeats
+ );
+ calculatedAnimeList = rawAnimeList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeMusic ? true : item.format !== 'MUSIC') &&
+ (includeRepeats
+ ? true
+ : item.startDate.year === selectedYear || item.endDate.year === selectedYear
? true
- : item.startDate.year === selectedYear || item.endDate.year === selectedYear
- ? true
- : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false)) &&
- (includeMovies ? true : item.format !== 'MOVIE') &&
- (includeSpecials ? true : item.format !== 'SPECIAL') &&
- (includeOVAs ? true : item.format !== 'OVA') &&
- (excludeUnratedUnwatched ? item.mediaListEntry?.score !== 0 : true) &&
- (excludeUnratedUnwatched ? item.mediaListEntry?.progress !== 0 : true) &&
- (startDateFilter && item.mediaListEntry?.startedAt
- ? new Date(
- item.mediaListEntry.startedAt.year,
- item.mediaListEntry?.startedAt.month - 1,
- item.mediaListEntry.startedAt.day
- ) >= new Date(startDateFilter)
- : true) &&
- (endDateFilter && item.mediaListEntry?.startedAt
- ? new Date(
- item.mediaListEntry.completedAt.year,
- item.mediaListEntry?.completedAt.month - 1,
- item.mediaListEntry.completedAt.day
- ) <= new Date(endDateFilter)
- : true)
- )
- .sort((a, b) => {
- switch (mediaSort) {
- case SortOptions.MINUTES_WATCHED:
- if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
- else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
- return -1;
- else
- return (
- b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
- );
- case SortOptions.SCORE:
- default:
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- }
- });
-
- animeList = rawAnimeList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeMusic ? true : item.format !== 'MUSIC') &&
- (includeRepeats
+ : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false)) &&
+ (includeMovies ? true : item.format !== 'MOVIE') &&
+ (includeSpecials ? true : item.format !== 'SPECIAL') &&
+ (includeOVAs ? true : item.format !== 'OVA') &&
+ (excludeUnratedUnwatched ? item.mediaListEntry?.score !== 0 : true) &&
+ (excludeUnratedUnwatched ? item.mediaListEntry?.progress !== 0 : true) &&
+ (startDateFilter && item.mediaListEntry?.startedAt
+ ? new Date(
+ item.mediaListEntry.startedAt.year,
+ item.mediaListEntry?.startedAt.month - 1,
+ item.mediaListEntry.startedAt.day
+ ) >= new Date(startDateFilter)
+ : true) &&
+ (endDateFilter && item.mediaListEntry?.startedAt
+ ? new Date(
+ item.mediaListEntry.completedAt.year,
+ item.mediaListEntry?.completedAt.month - 1,
+ item.mediaListEntry.completedAt.day
+ ) <= new Date(endDateFilter)
+ : true)
+ )
+ .sort((a, b) => {
+ switch (mediaSort) {
+ case SortOptions.MINUTES_WATCHED:
+ if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
+ else if (b.duration === undefined || b.mediaListEntry?.progress === undefined) return -1;
+ else
+ return b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress;
+ case SortOptions.SCORE:
+ default:
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ }
+ });
+
+ animeList = rawAnimeList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeMusic ? true : item.format !== 'MUSIC') &&
+ (includeRepeats
+ ? true
+ : item.startDate.year === selectedYear || item.endDate.year === selectedYear
? true
- : item.startDate.year === selectedYear || item.endDate.year === selectedYear
- ? true
- : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false) ||
- (includeOngoingMediaFromPreviousYears
- ? (item.mediaListEntry?.updatedAt
- ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.status === 'CURRENT'
- : false
- : false)) &&
- (includeMovies ? true : item.format !== 'MOVIE') &&
- (includeSpecials ? true : item.format !== 'SPECIAL') &&
- (includeOVAs ? true : item.format !== 'OVA')
- )
- .sort((a, b) => {
- switch (mediaSort) {
- case SortOptions.MINUTES_WATCHED:
- if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
- else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
- return -1;
- else
- return (
- b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
- );
- case SortOptions.SCORE:
- default:
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- }
- });
-
- let rawMangaList = await mediaListCollection(
- user,
- $userIdentity,
- Type.Manga,
- $manga,
- $lastPruneTimes.manga,
- {
- forcePrune: dateTicked ? false : true,
- includeCompleted: true,
- all: true
+ : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false) ||
+ (includeOngoingMediaFromPreviousYears
+ ? (item.mediaListEntry?.updatedAt
+ ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.status === 'CURRENT'
+ : false
+ : false)) &&
+ (includeMovies ? true : item.format !== 'MOVIE') &&
+ (includeSpecials ? true : item.format !== 'SPECIAL') &&
+ (includeOVAs ? true : item.format !== 'OVA')
+ )
+ .sort((a, b) => {
+ switch (mediaSort) {
+ case SortOptions.MINUTES_WATCHED:
+ if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
+ else if (b.duration === undefined || b.mediaListEntry?.progress === undefined) return -1;
+ else
+ return b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress;
+ case SortOptions.SCORE:
+ default:
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
}
- );
- calculatedMangaList = rawMangaList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false)) &&
- (excludeUnratedUnwatched ? item.mediaListEntry?.score !== 0 : true) &&
- (excludeUnratedUnwatched ? item.mediaListEntry?.progress !== 0 : true) &&
- (startDateFilter && item.mediaListEntry?.startedAt
- ? new Date(
- item.mediaListEntry.startedAt.year,
- item.mediaListEntry?.startedAt.month - 1,
- item.mediaListEntry.startedAt.day
- ) >= new Date(startDateFilter)
- : true) &&
- (endDateFilter && item.mediaListEntry?.startedAt
- ? new Date(
- item.mediaListEntry.completedAt.year,
- item.mediaListEntry?.completedAt.month - 1,
- item.mediaListEntry.completedAt.day
- ) <= new Date(endDateFilter)
- : true)
- )
- .sort((a, b) => {
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- });
-
- mangaList = rawMangaList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false) ||
- (includeOngoingMediaFromPreviousYears
- ? (item.mediaListEntry?.updatedAt
- ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.status === 'CURRENT'
- : false
- : false))
- )
- .sort((a, b) => {
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- });
-
- episodes = 0;
- minutesWatched = 0;
- chapters = 0;
- dateTicked = false;
-
- for (const media of calculatedAnimeList) {
- episodes += media.mediaListEntry?.progress || 0;
- minutesWatched += (media.mediaListEntry?.progress || 0) * media.duration || 0;
+ });
+
+ let rawMangaList = await mediaListCollection(
+ user,
+ $userIdentity,
+ Type.Manga,
+ $manga,
+ $lastPruneTimes.manga,
+ {
+ forcePrune: dateTicked ? false : true,
+ includeCompleted: true,
+ all: true
}
+ );
+ calculatedMangaList = rawMangaList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false)) &&
+ (excludeUnratedUnwatched ? item.mediaListEntry?.score !== 0 : true) &&
+ (excludeUnratedUnwatched ? item.mediaListEntry?.progress !== 0 : true) &&
+ (startDateFilter && item.mediaListEntry?.startedAt
+ ? new Date(
+ item.mediaListEntry.startedAt.year,
+ item.mediaListEntry?.startedAt.month - 1,
+ item.mediaListEntry.startedAt.day
+ ) >= new Date(startDateFilter)
+ : true) &&
+ (endDateFilter && item.mediaListEntry?.startedAt
+ ? new Date(
+ item.mediaListEntry.completedAt.year,
+ item.mediaListEntry?.completedAt.month - 1,
+ item.mediaListEntry.completedAt.day
+ ) <= new Date(endDateFilter)
+ : true)
+ )
+ .sort((a, b) => {
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ });
- for (const media of calculatedMangaList) chapters += media.mediaListEntry?.progress || 0;
- };
+ mangaList = rawMangaList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false) ||
+ (includeOngoingMediaFromPreviousYears
+ ? (item.mediaListEntry?.updatedAt
+ ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.status === 'CURRENT'
+ : false
+ : false))
+ )
+ .sort((a, b) => {
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ });
- /* eslint-disable @typescript-eslint/no-explicit-any */
- // const year = (statistic: { startYears: any }) =>
- // statistic.startYears.find((y: { startYear: number }) => y.startYear === 2023);
-
- const screenshot = async () => {
- let element = document.querySelector('#wrapped') as HTMLElement;
-
- if (element !== null) {
- domToBlob(element, {
- backgroundColor: transparency ? 'transparent' : lightTheme ? '#edf1f5' : '#0b1622',
- quality: 1,
- scale: 2,
- fetch: {
- requestInit: {
- mode: 'cors'
- },
- bypassingCache: true
- }
- }).then((blob) => {
- const downloadWrapper = document.createElement('a');
- // const wrappedImageButton = document.getElementById(
- // 'wrapped-image-download'
- // ) as HTMLAnchorElement;
- const image = document.createElement('img');
- const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
-
- // downloadWrapper.download = `due_dot_moe_wrapped_${dark ? 'dark' : 'light'}.png`;
- downloadWrapper.href = object;
- downloadWrapper.target = '_blank';
- image.src = object;
-
- downloadWrapper.appendChild(image);
-
- // if (wrappedImageButton !== null) {
- // wrappedImageButton.href = object;
- // }
-
- const wrappedFinal = document.getElementById('wrapped-final');
-
- if (wrappedFinal !== null) {
- wrappedFinal.innerHTML = '';
-
- wrappedFinal.appendChild(downloadWrapper);
-
- generated = true;
- }
-
- downloadWrapper.click();
- });
- }
- };
+ episodes = 0;
+ minutesWatched = 0;
+ chapters = 0;
+ dateTicked = false;
- // const abbreviate = (string: string, maxLength = 40, enabled = true) => {
- // if (!enabled) {
- // return string;
- // }
-
- // if (string.length <= maxLength) {
- // return string;
- // }
-
- // return string.slice(0, maxLength - 3) + ' …';
- // };
-
- const submitExcludedKeywords = () => {
- if (excludedKeywordsInput.length <= 0 && excludedKeywords.length > 0) {
- animeList = originalAnimeList;
- mangaList = originalMangaList;
- excludedKeywords = [];
- } else if (excludedKeywordsInput.length >= 0 && excludedKeywords.length <= 0) {
- originalAnimeList = animeList;
- originalMangaList = mangaList;
- }
+ for (const media of calculatedAnimeList) {
+ episodes += media.mediaListEntry?.progress || 0;
+ minutesWatched += (media.mediaListEntry?.progress || 0) * media.duration || 0;
+ }
- if (excludedKeywordsInput.length > 0)
- excludedKeywords = excludedKeywordsInput
- .split(',')
- .map((k) => k.trim())
- .filter((k) => k.length > 0);
- };
+ for (const media of calculatedMangaList) chapters += media.mediaListEntry?.progress || 0;
+};
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// const year = (statistic: { startYears: any }) =>
+// statistic.startYears.find((y: { startYear: number }) => y.startYear === 2023);
+
+const screenshot = async () => {
+ let element = document.querySelector('#wrapped') as HTMLElement;
+
+ if (element !== null) {
+ domToBlob(element, {
+ backgroundColor: transparency ? 'transparent' : lightTheme ? '#edf1f5' : '#0b1622',
+ quality: 1,
+ scale: 2,
+ fetch: {
+ requestInit: {
+ mode: 'cors'
+ },
+ bypassingCache: true
+ }
+ }).then((blob) => {
+ const downloadWrapper = document.createElement('a');
+ // const wrappedImageButton = document.getElementById(
+ // 'wrapped-image-download'
+ // ) as HTMLAnchorElement;
+ const image = document.createElement('img');
+ const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
+
+ // downloadWrapper.download = `due_dot_moe_wrapped_${dark ? 'dark' : 'light'}.png`;
+ downloadWrapper.href = object;
+ downloadWrapper.target = '_blank';
+ image.src = object;
+
+ downloadWrapper.appendChild(image);
- const excludeKeywords = (media: Media[]) => {
- if (excludedKeywords.length <= 0) return media;
+ // if (wrappedImageButton !== null) {
+ // wrappedImageButton.href = object;
+ // }
- return media.filter((m) => {
- for (const keyword of excludedKeywords) {
- if (m.title.english?.toLowerCase().includes(keyword.toLowerCase())) return false;
- if (m.title.romaji?.toLowerCase().includes(keyword.toLowerCase())) return false;
- if (m.title.native?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ const wrappedFinal = document.getElementById('wrapped-final');
+
+ if (wrappedFinal !== null) {
+ wrappedFinal.innerHTML = '';
+
+ wrappedFinal.appendChild(downloadWrapper);
+
+ generated = true;
}
- return true;
+ downloadWrapper.click();
});
- };
+ }
+};
+
+// const abbreviate = (string: string, maxLength = 40, enabled = true) => {
+// if (!enabled) {
+// return string;
+// }
+
+// if (string.length <= maxLength) {
+// return string;
+// }
+
+// return string.slice(0, maxLength - 3) + ' …';
+// };
+
+const submitExcludedKeywords = () => {
+ if (excludedKeywordsInput.length <= 0 && excludedKeywords.length > 0) {
+ animeList = originalAnimeList;
+ mangaList = originalMangaList;
+ excludedKeywords = [];
+ } else if (excludedKeywordsInput.length >= 0 && excludedKeywords.length <= 0) {
+ originalAnimeList = animeList;
+ originalMangaList = mangaList;
+ }
- const pruneFullYear = async () => {
- await database.activities.bulkDelete((await database.activities.toArray()).map((m) => m.page));
- };
+ if (excludedKeywordsInput.length > 0)
+ excludedKeywords = excludedKeywordsInput
+ .split(',')
+ .map((k) => k.trim())
+ .filter((k) => k.length > 0);
+};
+
+const excludeKeywords = (media: Media[]) => {
+ if (excludedKeywords.length <= 0) return media;
+
+ return media.filter((m) => {
+ for (const keyword of excludedKeywords) {
+ if (m.title.english?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ if (m.title.romaji?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ if (m.title.native?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ }
- // const mergeArraySort = (a: any, b: any, mode: 'tags' | 'genres') => {
- // let merged = [...a, ...b].sort((a, b) => b.meanScore - a.meanScore);
-
- // merged = merged.filter(
- // (item, index, self) =>
- // self.findIndex((itemToCompare) =>
- // mode === 'genres'
- // ? itemToCompare.genre === item.genre
- // : itemToCompare.tag.name === item.tag.name
- // ) === index
- // );
-
- // return merged;
- // };
-
- // const randomCoverFromTop10 = (
- // statistics: { anime: any; manga: any },
- // mode: 'tags' | 'genres'
- // ) => {
- // const top = mergeArraySort(statistics.anime[mode], statistics.manga[mode], mode);
-
- // return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]);
- // };
+ return true;
+ });
+};
+
+const pruneFullYear = async () => {
+ await database.activities.bulkDelete((await database.activities.toArray()).map((m) => m.page));
+};
+
+// const mergeArraySort = (a: any, b: any, mode: 'tags' | 'genres') => {
+// let merged = [...a, ...b].sort((a, b) => b.meanScore - a.meanScore);
+
+// merged = merged.filter(
+// (item, index, self) =>
+// self.findIndex((itemToCompare) =>
+// mode === 'genres'
+// ? itemToCompare.genre === item.genre
+// : itemToCompare.tag.name === item.tag.name
+// ) === index
+// );
+
+// return merged;
+// };
+
+// const randomCoverFromTop10 = (
+// statistics: { anime: any; manga: any },
+// mode: 'tags' | 'genres'
+// ) => {
+// const top = mergeArraySort(statistics.anime[mode], statistics.manga[mode], mode);
+
+// return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]);
+// };
</script>
{#if $userIdentity.id === -2 || user === undefined}
diff --git a/src/lib/Tools/Wrapped/Top/Activity.svelte b/src/lib/Tools/Wrapped/Top/Activity.svelte
index ea6ba592..fdfd90e0 100644
--- a/src/lib/Tools/Wrapped/Top/Activity.svelte
+++ b/src/lib/Tools/Wrapped/Top/Activity.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
- import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
- import identity from '$stores/identity';
- import type { Wrapped } from '$lib/Data/AniList/wrapped';
- import proxy from '$lib/Utility/proxy';
+import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
+import identity from '$stores/identity';
+import type { Wrapped } from '$lib/Data/AniList/wrapped';
+import proxy from '$lib/Utility/proxy';
- export let wrapped: Wrapped;
- export let year: number;
- export let activities: ActivityHistoryEntry[];
- export let useFullActivityHistory: boolean;
- export let updateWidth: () => void;
+export let wrapped: Wrapped;
+export let year: number;
+export let activities: ActivityHistoryEntry[];
+export let useFullActivityHistory: boolean;
+export let updateWidth: () => void;
- const currentYear = new Date(Date.now()).getFullYear();
+const currentYear = new Date(Date.now()).getFullYear();
</script>
<div class="grid-item image-grid avatar-grid category top-category">
diff --git a/src/lib/Tools/Wrapped/Top/Anime.svelte b/src/lib/Tools/Wrapped/Top/Anime.svelte
index 08df7fd3..6caf4b8a 100644
--- a/src/lib/Tools/Wrapped/Top/Anime.svelte
+++ b/src/lib/Tools/Wrapped/Top/Anime.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
+import type { Media } from '$lib/Data/AniList/media';
- export let minutesWatched: number;
- export let animeList: Media[] | undefined;
- export let episodes: number;
+export let minutesWatched: number;
+export let animeList: Media[] | undefined;
+export let episodes: number;
</script>
<div class="category-grid pure-category category top-category">
diff --git a/src/lib/Tools/Wrapped/Top/Manga.svelte b/src/lib/Tools/Wrapped/Top/Manga.svelte
index a36f7724..9fa4ab00 100644
--- a/src/lib/Tools/Wrapped/Top/Manga.svelte
+++ b/src/lib/Tools/Wrapped/Top/Manga.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import { estimatedDayReading } from '$lib/Media/Manga/time';
+import type { Media } from '$lib/Data/AniList/media';
+import { estimatedDayReading } from '$lib/Media/Manga/time';
- export let mangaList: Media[] | undefined;
- export let chapters: number;
+export let mangaList: Media[] | undefined;
+export let chapters: number;
</script>
<div class="category-grid pure-category category top-category">
diff --git a/src/lib/Tooltip/LinkedTooltip.svelte b/src/lib/Tooltip/LinkedTooltip.svelte
index 290dbab3..4c90d9af 100644
--- a/src/lib/Tooltip/LinkedTooltip.svelte
+++ b/src/lib/Tooltip/LinkedTooltip.svelte
@@ -1,203 +1,199 @@
<script lang="ts">
- import tooltipPosition from '$stores/tooltipPosition';
- import { fade } from 'svelte/transition';
-
- export let id: string | undefined = undefined;
- export let pin: string | undefined = undefined;
- export let content: string;
- export let disable = false;
- export let pinPosition: 'top' | 'bottom' | 'left' | 'right' = 'top';
- export let offset = 10;
- export let tooltipTransitionTime = 200;
- export let tooltipHideDelay = 10;
- export let debounceDelay = 100;
- export let tooltipOpacityTransitionTime = 200;
- export let relative = false;
- export let ignoreAnchorStyling = false;
-
- let tooltipDiv: HTMLDivElement | null = null;
- let hideTimeout: number | null = null;
- let debounceTimer: number | null = null;
- let opacity = 0;
-
- const createTooltip = () => {
- if (!tooltipDiv) {
- tooltipDiv = document.createElement('div');
- tooltipDiv.style.position = 'absolute';
- tooltipDiv.style.zIndex = '1000';
- opacity = 0;
- tooltipDiv.style.pointerEvents = 'none';
- tooltipDiv.style.whiteSpace = 'nowrap';
-
- tooltipDiv.classList.add('card');
- tooltipDiv.classList.add('card-small');
- document.body.appendChild(tooltipDiv);
- }
- };
-
- const updateTooltipPosition = (x: number, y: number) => {
- if (tooltipDiv) {
- if (pin) {
- const pinnedElement = document.getElementById(pin);
-
- if (pinnedElement) {
- const rectangle = pinnedElement.getBoundingClientRect();
- const parentRectangle = pinnedElement.offsetParent?.getBoundingClientRect();
- const tooltipWidth = tooltipDiv.offsetWidth;
- const tooltipHeight = tooltipDiv.offsetHeight;
- let top = 0;
- let left = 0;
-
- switch (pinPosition) {
- case 'top':
- if (relative && parentRectangle) {
- top = rectangle.top - tooltipHeight - offset - parentRectangle.top;
- left =
- rectangle.left + rectangle.width / 2 - tooltipWidth / 2 - parentRectangle.left;
- } else {
- top = rectangle.top - tooltipHeight - offset;
- left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
- }
-
- break;
- case 'bottom':
- if (relative && parentRectangle) {
- top = rectangle.top + rectangle.height + offset - parentRectangle.top;
- left =
- rectangle.left + rectangle.width / 2 - tooltipWidth / 2 - parentRectangle.left;
- } else {
- top = rectangle.top + rectangle.height + offset;
- left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
- }
-
- break;
- case 'left':
- if (relative && parentRectangle) {
- top =
- rectangle.top + rectangle.height / 2 - tooltipHeight / 2 - parentRectangle.top;
- left = rectangle.left - tooltipWidth - offset - parentRectangle.left;
- } else {
- top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
- left = rectangle.left - tooltipWidth - offset;
- }
-
- break;
- case 'right':
- if (relative && parentRectangle) {
- top =
- rectangle.top + rectangle.height / 2 - tooltipHeight / 2 - parentRectangle.top;
- left = rectangle.left + rectangle.width + offset - parentRectangle.left;
- } else {
- top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
- left = rectangle.left + rectangle.width + offset;
- }
-
- break;
- }
-
- if (relative && parentRectangle) {
- if (left + parentRectangle.left < 0) left = offset - parentRectangle.left;
- if (left + tooltipWidth + parentRectangle.left > window.innerWidth)
- left = window.innerWidth - tooltipWidth - offset - parentRectangle.left;
- if (top + parentRectangle.top < 0)
+import tooltipPosition from '$stores/tooltipPosition';
+import { fade } from 'svelte/transition';
+
+export let id: string | undefined = undefined;
+export let pin: string | undefined = undefined;
+export let content: string;
+export let disable = false;
+export let pinPosition: 'top' | 'bottom' | 'left' | 'right' = 'top';
+export let offset = 10;
+export let tooltipTransitionTime = 200;
+export let tooltipHideDelay = 10;
+export let debounceDelay = 100;
+export let tooltipOpacityTransitionTime = 200;
+export let relative = false;
+export let ignoreAnchorStyling = false;
+
+let tooltipDiv: HTMLDivElement | null = null;
+let hideTimeout: number | null = null;
+let debounceTimer: number | null = null;
+let opacity = 0;
+
+const createTooltip = () => {
+ if (!tooltipDiv) {
+ tooltipDiv = document.createElement('div');
+ tooltipDiv.style.position = 'absolute';
+ tooltipDiv.style.zIndex = '1000';
+ opacity = 0;
+ tooltipDiv.style.pointerEvents = 'none';
+ tooltipDiv.style.whiteSpace = 'nowrap';
+
+ tooltipDiv.classList.add('card');
+ tooltipDiv.classList.add('card-small');
+ document.body.appendChild(tooltipDiv);
+ }
+};
+
+const updateTooltipPosition = (x: number, y: number) => {
+ if (tooltipDiv) {
+ if (pin) {
+ const pinnedElement = document.getElementById(pin);
+
+ if (pinnedElement) {
+ const rectangle = pinnedElement.getBoundingClientRect();
+ const parentRectangle = pinnedElement.offsetParent?.getBoundingClientRect();
+ const tooltipWidth = tooltipDiv.offsetWidth;
+ const tooltipHeight = tooltipDiv.offsetHeight;
+ let top = 0;
+ let left = 0;
+
+ switch (pinPosition) {
+ case 'top':
+ if (relative && parentRectangle) {
+ top = rectangle.top - tooltipHeight - offset - parentRectangle.top;
+ left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2 - parentRectangle.left;
+ } else {
+ top = rectangle.top - tooltipHeight - offset;
+ left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
+ }
+
+ break;
+ case 'bottom':
+ if (relative && parentRectangle) {
top = rectangle.top + rectangle.height + offset - parentRectangle.top;
- if (top + tooltipHeight + parentRectangle.top > window.innerHeight)
- top = window.innerHeight - tooltipHeight - offset - parentRectangle.top;
- } else {
- if (left < 0) left = offset;
- if (left + tooltipWidth > window.innerWidth)
- left = window.innerWidth - tooltipWidth - offset;
- if (top < 0) top = rectangle.top + rectangle.height + offset;
- if (top + tooltipHeight > window.innerHeight)
- top = window.innerHeight - tooltipHeight - offset;
- }
-
- tooltipPosition.set({
- x: left,
- y: top + (relative ? 0 : window.scrollY)
- });
-
- return;
+ left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2 - parentRectangle.left;
+ } else {
+ top = rectangle.top + rectangle.height + offset;
+ left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
+ }
+
+ break;
+ case 'left':
+ if (relative && parentRectangle) {
+ top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2 - parentRectangle.top;
+ left = rectangle.left - tooltipWidth - offset - parentRectangle.left;
+ } else {
+ top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
+ left = rectangle.left - tooltipWidth - offset;
+ }
+
+ break;
+ case 'right':
+ if (relative && parentRectangle) {
+ top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2 - parentRectangle.top;
+ left = rectangle.left + rectangle.width + offset - parentRectangle.left;
+ } else {
+ top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
+ left = rectangle.left + rectangle.width + offset;
+ }
+
+ break;
}
- }
- const tooltipWidth = tooltipDiv.offsetWidth;
- const tooltipHeight = tooltipDiv.offsetHeight;
- let top = y - tooltipHeight - offset;
- let left = x - tooltipWidth / 2;
+ if (relative && parentRectangle) {
+ if (left + parentRectangle.left < 0) left = offset - parentRectangle.left;
+ if (left + tooltipWidth + parentRectangle.left > window.innerWidth)
+ left = window.innerWidth - tooltipWidth - offset - parentRectangle.left;
+ if (top + parentRectangle.top < 0)
+ top = rectangle.top + rectangle.height + offset - parentRectangle.top;
+ if (top + tooltipHeight + parentRectangle.top > window.innerHeight)
+ top = window.innerHeight - tooltipHeight - offset - parentRectangle.top;
+ } else {
+ if (left < 0) left = offset;
+ if (left + tooltipWidth > window.innerWidth)
+ left = window.innerWidth - tooltipWidth - offset;
+ if (top < 0) top = rectangle.top + rectangle.height + offset;
+ if (top + tooltipHeight > window.innerHeight)
+ top = window.innerHeight - tooltipHeight - offset;
+ }
- if (left < 0) left = offset;
- if (left + tooltipWidth > window.innerWidth) left = window.innerWidth - tooltipWidth - offset;
- if (top < 0) top = y + offset;
+ tooltipPosition.set({
+ x: left,
+ y: top + (relative ? 0 : window.scrollY)
+ });
- tooltipPosition.set({
- x: left,
- y: top
- });
+ return;
+ }
}
- };
- const showTooltip = (content: string, x: number, y: number) => {
- if (hideTimeout !== null) {
- clearTimeout(hideTimeout);
+ const tooltipWidth = tooltipDiv.offsetWidth;
+ const tooltipHeight = tooltipDiv.offsetHeight;
+ let top = y - tooltipHeight - offset;
+ let left = x - tooltipWidth / 2;
- hideTimeout = null;
- }
+ if (left < 0) left = offset;
+ if (left + tooltipWidth > window.innerWidth) left = window.innerWidth - tooltipWidth - offset;
+ if (top < 0) top = y + offset;
- createTooltip();
+ tooltipPosition.set({
+ x: left,
+ y: top
+ });
+ }
+};
- if (tooltipDiv) {
- tooltipDiv.innerHTML = content.replace(/\n/g, '<br>');
- tooltipDiv.style.opacity = '0';
+const showTooltip = (content: string, x: number, y: number) => {
+ if (hideTimeout !== null) {
+ clearTimeout(hideTimeout);
- updateTooltipPosition(x, y);
- setTimeout(() => {
- if (tooltipDiv) {
- opacity = 1;
- }
- }, 10);
- }
- };
+ hideTimeout = null;
+ }
- const hideTooltip = () => {
- setTimeout(() => {
- if (tooltipDiv) {
- opacity = 0;
+ createTooltip();
- hideTimeout = window.setTimeout(() => {
- if (tooltipDiv) {
- document.body.removeChild(tooltipDiv);
+ if (tooltipDiv) {
+ tooltipDiv.innerHTML = content.replace(/\n/g, '<br>');
+ tooltipDiv.style.opacity = '0';
- tooltipDiv = null;
- }
- }, tooltipTransitionTime);
+ updateTooltipPosition(x, y);
+ setTimeout(() => {
+ if (tooltipDiv) {
+ opacity = 1;
}
- }, tooltipHideDelay);
- };
+ }, 10);
+ }
+};
- const handleMouseEnter = (event: MouseEvent) => {
- if (disable) return;
+const hideTooltip = () => {
+ setTimeout(() => {
+ if (tooltipDiv) {
+ opacity = 0;
- if (hideTimeout !== null) {
- clearTimeout(hideTimeout);
+ hideTimeout = window.setTimeout(() => {
+ if (tooltipDiv) {
+ document.body.removeChild(tooltipDiv);
- hideTimeout = null;
+ tooltipDiv = null;
+ }
+ }, tooltipTransitionTime);
}
+ }, tooltipHideDelay);
+};
+
+const handleMouseEnter = (event: MouseEvent) => {
+ if (disable) return;
+
+ if (hideTimeout !== null) {
+ clearTimeout(hideTimeout);
+
+ hideTimeout = null;
+ }
- if (!tooltipDiv) showTooltip(content, event.pageX, event.pageY);
- };
+ if (!tooltipDiv) showTooltip(content, event.pageX, event.pageY);
+};
- const handleMouseMove = (event: MouseEvent) => {
- if (debounceTimer !== null) clearTimeout(debounceTimer);
+const handleMouseMove = (event: MouseEvent) => {
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
- debounceTimer = window.setTimeout(() => {
- if (tooltipDiv && opacity === 1) updateTooltipPosition(event.pageX, event.pageY);
- }, debounceDelay);
- };
+ debounceTimer = window.setTimeout(() => {
+ if (tooltipDiv && opacity === 1) updateTooltipPosition(event.pageX, event.pageY);
+ }, debounceDelay);
+};
- const handleMouseLeave = () => {
- hideTooltip();
- };
+const handleMouseLeave = () => {
+ hideTooltip();
+};
</script>
<span
diff --git a/src/lib/User/BadgeWall/AWC.svelte b/src/lib/User/BadgeWall/AWC.svelte
index 8d340282..52eb670a 100644
--- a/src/lib/User/BadgeWall/AWC.svelte
+++ b/src/lib/User/BadgeWall/AWC.svelte
@@ -1,63 +1,63 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import type { AWCBadgesGroup } from '$lib/Data/awc';
- import { cdn, thumbnail } from '$lib/Utility/image';
- import type { Preferences } from '../../../graphql/$types';
- import FallbackBadge from './FallbackBadge.svelte';
- import './badges.css';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import type { AWCBadgesGroup } from '$lib/Data/awc';
+import { cdn, thumbnail } from '$lib/Utility/image';
+import type { Preferences } from '../../../graphql/$types';
+import FallbackBadge from './FallbackBadge.svelte';
+import './badges.css';
- export let awcPromise: Promise<Response>;
- export let categoryFilter: string | null;
- export let isOwner: boolean;
- export let preferences: Preferences;
+export let awcPromise: Promise<Response>;
+export let categoryFilter: string | null;
+export let isOwner: boolean;
+export let preferences: Preferences;
- const awcBadgesGrouped = (awcResponse: string): AWCBadgesGroup[] => {
- return Array.from(
- new DOMParser().parseFromString(awcResponse, 'text/html').querySelectorAll('.container')
- )
- .map((c) => {
- const container = c as HTMLDivElement;
- const header = container.querySelector('.container-header') as HTMLDivElement;
+const awcBadgesGrouped = (awcResponse: string): AWCBadgesGroup[] => {
+ return Array.from(
+ new DOMParser().parseFromString(awcResponse, 'text/html').querySelectorAll('.container')
+ )
+ .map((c) => {
+ const container = c as HTMLDivElement;
+ const header = container.querySelector('.container-header') as HTMLDivElement;
- if (!header) return;
+ if (!header) return;
- if (!['Anime', 'Manga', 'Special'].includes(header.innerText)) return;
-
- if (header.innerText === 'Special') {
- return {
- group: header.innerText,
- badges: Array.from(container.querySelectorAll('.badge-display img')).map((b) => {
- const badge = b as HTMLImageElement;
-
- return {
- link: '#',
- description: badge.alt,
- image: badge.src.includes('placeholder')
- ? cdn('https://awc.moe/static/images/badge-placeholder.png')
- : badge.src
- };
- })
- };
- }
+ if (!['Anime', 'Manga', 'Special'].includes(header.innerText)) return;
+ if (header.innerText === 'Special') {
return {
group: header.innerText,
- badges: Array.from(container.querySelectorAll('.badge-display a')).map((b) => {
- const badge = b as HTMLAnchorElement;
- const image = badge.querySelector('img') as HTMLImageElement;
+ badges: Array.from(container.querySelectorAll('.badge-display img')).map((b) => {
+ const badge = b as HTMLImageElement;
return {
- link: badge.href,
- description: image.alt,
- image: image.src.includes('placeholder')
+ link: '#',
+ description: badge.alt,
+ image: badge.src.includes('placeholder')
? cdn('https://awc.moe/static/images/badge-placeholder.png')
- : image.src
+ : badge.src
};
})
};
- })
- .filter((b) => b !== undefined) as AWCBadgesGroup[];
- };
+ }
+
+ return {
+ group: header.innerText,
+ badges: Array.from(container.querySelectorAll('.badge-display a')).map((b) => {
+ const badge = b as HTMLAnchorElement;
+ const image = badge.querySelector('img') as HTMLImageElement;
+
+ return {
+ link: badge.href,
+ description: image.alt,
+ image: image.src.includes('placeholder')
+ ? cdn('https://awc.moe/static/images/badge-placeholder.png')
+ : image.src
+ };
+ })
+ };
+ })
+ .filter((b) => b !== undefined) as AWCBadgesGroup[];
+};
</script>
{#await awcPromise then badges}
diff --git a/src/lib/User/BadgeWall/BadgePreview.svelte b/src/lib/User/BadgeWall/BadgePreview.svelte
index 062fdac7..cf0cb284 100644
--- a/src/lib/User/BadgeWall/BadgePreview.svelte
+++ b/src/lib/User/BadgeWall/BadgePreview.svelte
@@ -1,104 +1,104 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import { thumbnail } from '$lib/Utility/image';
- import type { Badge } from '$lib/Database/SB/User/badges';
- import { cdn } from '$lib/Utility/image';
- import { databaseTimeToDate } from '$lib/Utility/time';
- import locale from '$stores/locale';
- import { onMount } from 'svelte';
- import root from '$lib/Utility/root';
- import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
-
- export let selectedBadge: Badge | undefined;
- export let onNext: () => void = () => undefined;
- export let onPrevious: () => void = () => undefined;
- export let hasNext: boolean;
- export let hasPrevious: boolean;
-
- let source = cdn(thumbnail(selectedBadge?.image || '')) || '';
- let badgeReference: HTMLImageElement;
-
- $: {
- if (selectedBadge && selectedBadge.image) {
- const image = new Image();
-
- image.src = cdn(selectedBadge.image) || '';
- image.onload = () => {
- source = image.src;
- };
- }
- }
-
- $: {
- if (selectedBadge)
- fetch(root(`/api/badges?incrementClickCount=${selectedBadge.id}`), {
- method: 'PUT'
- });
+import Spacer from '$lib/Layout/Spacer.svelte';
+import { thumbnail } from '$lib/Utility/image';
+import type { Badge } from '$lib/Database/SB/User/badges';
+import { cdn } from '$lib/Utility/image';
+import { databaseTimeToDate } from '$lib/Utility/time';
+import locale from '$stores/locale';
+import { onMount } from 'svelte';
+import root from '$lib/Utility/root';
+import ParallaxImage from '$lib/Image/ParallaxImage.svelte';
+
+export let selectedBadge: Badge | undefined;
+export let onNext: () => void = () => undefined;
+export let onPrevious: () => void = () => undefined;
+export let hasNext: boolean;
+export let hasPrevious: boolean;
+
+let source = cdn(thumbnail(selectedBadge?.image || '')) || '';
+let badgeReference: HTMLImageElement;
+
+$: {
+ if (selectedBadge && selectedBadge.image) {
+ const image = new Image();
+
+ image.src = cdn(selectedBadge.image) || '';
+ image.onload = () => {
+ source = image.src;
+ };
}
+}
- onMount(() => {
- badgeReference = document.querySelector('.badge-container-image') as HTMLImageElement;
+$: {
+ if (selectedBadge)
+ fetch(root(`/api/badges?incrementClickCount=${selectedBadge.id}`), {
+ method: 'PUT'
+ });
+}
- const handleClickOutside = (event: MouseEvent) => {
- if ((event.target as HTMLElement).classList.contains('popup')) selectedBadge = undefined;
- };
+onMount(() => {
+ badgeReference = document.querySelector('.badge-container-image') as HTMLImageElement;
- document.addEventListener('click', handleClickOutside);
+ const handleClickOutside = (event: MouseEvent) => {
+ if ((event.target as HTMLElement).classList.contains('popup')) selectedBadge = undefined;
+ };
- return () => {
- document.removeEventListener('click', handleClickOutside);
- };
- });
-
- const classifySource = (source: string) => {
- let name = source;
- const sourceLower = source.toLowerCase();
-
- if (sourceLower.includes('pixiv.net')) {
- name = 'Pixiv';
- } else if (sourceLower.includes('twitter.com') || sourceLower.includes('x.com')) {
- name = 'X (Twitter)';
- } else if (sourceLower.includes('zerochan.net')) {
- name = 'Zerochan';
- } else if (sourceLower.includes('imgur.com')) {
- name = 'Imgur';
- } else if (sourceLower.includes('lofter.com')) {
- name = 'Lofter';
- }
+ document.addEventListener('click', handleClickOutside);
- return `<a href="${source}" target="_blank">${name}</a>`;
+ return () => {
+ document.removeEventListener('click', handleClickOutside);
};
+});
+
+const classifySource = (source: string) => {
+ let name = source;
+ const sourceLower = source.toLowerCase();
+
+ if (sourceLower.includes('pixiv.net')) {
+ name = 'Pixiv';
+ } else if (sourceLower.includes('twitter.com') || sourceLower.includes('x.com')) {
+ name = 'X (Twitter)';
+ } else if (sourceLower.includes('zerochan.net')) {
+ name = 'Zerochan';
+ } else if (sourceLower.includes('imgur.com')) {
+ name = 'Imgur';
+ } else if (sourceLower.includes('lofter.com')) {
+ name = 'Lofter';
+ }
- const classifyDesigner = (designer: string) => {
- let name = designer;
- let userLink = designer;
- const designerLower = designer.toLowerCase();
- const anilistUser = designer.match(/https?:\/\/anilist\.co\/user\/([^/]+)\/?/);
-
- if (anilistUser) {
- name = anilistUser[1];
- } else if (designerLower.startsWith('@')) {
- name = designer.replace('@', '');
- userLink = `https://anilist.co/user/${name}/`;
- } else if (!designerLower.startsWith('http')) {
- userLink = `https://anilist.co/user/${name}/`;
- }
+ return `<a href="${source}" target="_blank">${name}</a>`;
+};
+
+const classifyDesigner = (designer: string) => {
+ let name = designer;
+ let userLink = designer;
+ const designerLower = designer.toLowerCase();
+ const anilistUser = designer.match(/https?:\/\/anilist\.co\/user\/([^/]+)\/?/);
+
+ if (anilistUser) {
+ name = anilistUser[1];
+ } else if (designerLower.startsWith('@')) {
+ name = designer.replace('@', '');
+ userLink = `https://anilist.co/user/${name}/`;
+ } else if (!designerLower.startsWith('http')) {
+ userLink = `https://anilist.co/user/${name}/`;
+ }
- return `<a href="${userLink}" target="_blank">@${name}</a>`;
- };
+ return `<a href="${userLink}" target="_blank">@${name}</a>`;
+};
- const onClick = (event: MouseEvent) => {
- event.preventDefault();
+const onClick = (event: MouseEvent) => {
+ event.preventDefault();
- if (
- event.clientX <
- badgeReference.getBoundingClientRect().left + badgeReference.getBoundingClientRect().width / 2
- ) {
- onPrevious();
- } else {
- onNext();
- }
- };
+ if (
+ event.clientX <
+ badgeReference.getBoundingClientRect().left + badgeReference.getBoundingClientRect().width / 2
+ ) {
+ onPrevious();
+ } else {
+ onNext();
+ }
+};
</script>
{#if selectedBadge}
diff --git a/src/lib/User/BadgeWall/Badges.svelte b/src/lib/User/BadgeWall/Badges.svelte
index 99d500da..04943c64 100644
--- a/src/lib/User/BadgeWall/Badges.svelte
+++ b/src/lib/User/BadgeWall/Badges.svelte
@@ -1,21 +1,21 @@
<script lang="ts">
- import Spacer from '$lib/Layout/Spacer.svelte';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import locale from '$stores/locale';
- import { databaseTimeToDate } from '$lib/Utility/time';
- import FallbackImage from '$lib/Image/FallbackImage.svelte';
- import { cdn, thumbnail } from '$lib/Utility/image';
- import FallbackBadge from './FallbackBadge.svelte';
- import type { Preferences } from '../../../graphql/$types';
- import type { IndexedBadge } from './badge';
+import Spacer from '$lib/Layout/Spacer.svelte';
+import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+import tooltip from '$lib/Tooltip/tooltip';
+import locale from '$stores/locale';
+import { databaseTimeToDate } from '$lib/Utility/time';
+import FallbackImage from '$lib/Image/FallbackImage.svelte';
+import { cdn, thumbnail } from '$lib/Utility/image';
+import FallbackBadge from './FallbackBadge.svelte';
+import type { Preferences } from '../../../graphql/$types';
+import type { IndexedBadge } from './badge';
- export let ungroupedBadges: IndexedBadge[];
- export let groupedBadges: [string, IndexedBadge[]][];
- export let categoryFilter: string | null;
- export let editMode: boolean;
- export let preferences: Preferences | undefined;
- export let selectedBadge: IndexedBadge | undefined = undefined;
+export let ungroupedBadges: IndexedBadge[];
+export let groupedBadges: [string, IndexedBadge[]][];
+export let categoryFilter: string | null;
+export let editMode: boolean;
+export let preferences: Preferences | undefined;
+export let selectedBadge: IndexedBadge | undefined = undefined;
</script>
{#if ungroupedBadges.length === 0}
diff --git a/src/lib/User/BadgeWall/FallbackBadge.svelte b/src/lib/User/BadgeWall/FallbackBadge.svelte
index bda93e4e..86e90939 100644
--- a/src/lib/User/BadgeWall/FallbackBadge.svelte
+++ b/src/lib/User/BadgeWall/FallbackBadge.svelte
@@ -1,70 +1,70 @@
<script lang="ts">
- import { classifyDesignerName } from './badge';
- import locale from '$stores/locale';
- import { tweened } from 'svelte/motion';
- import type { Badge } from '../../Database/SB/User/badges';
- import type { AWCBadge } from '../../Data/awc';
- import Tooltip from '../../Tooltip/LinkedTooltip.svelte';
- import { databaseTimeToDate } from '../../Utility/time';
- import { cubicOut } from 'svelte/easing';
- import { dev } from '$app/environment';
- import type { Preferences } from '../../../graphql/$types';
+import { classifyDesignerName } from './badge';
+import locale from '$stores/locale';
+import { tweened } from 'svelte/motion';
+import type { Badge } from '../../Database/SB/User/badges';
+import type { AWCBadge } from '../../Data/awc';
+import Tooltip from '../../Tooltip/LinkedTooltip.svelte';
+import { databaseTimeToDate } from '../../Utility/time';
+import { cubicOut } from 'svelte/easing';
+import { dev } from '$app/environment';
+import type { Preferences } from '../../../graphql/$types';
- export let source: string | null | undefined;
- export let alternative: string | null | undefined;
- export let fallback: string | null | undefined;
- export let maxReplaceCount = 1;
- export let replaceDelay = 1000;
- export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg';
- export let hideOnError = false;
- export let badge: Badge | AWCBadge;
- export let style = '';
- export let selectedBadge: Badge | null = null;
- export let awc = false;
- export let index: number | null = null;
- export let preferences: Preferences | undefined;
+export let source: string | null | undefined;
+export let alternative: string | null | undefined;
+export let fallback: string | null | undefined;
+export let maxReplaceCount = 1;
+export let replaceDelay = 1000;
+export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg';
+export let hideOnError = false;
+export let badge: Badge | AWCBadge;
+export let style = '';
+export let selectedBadge: Badge | null = null;
+export let awc = false;
+export let index: number | null = null;
+export let preferences: Preferences | undefined;
- let replaceCount = 0;
- let badgeReference: HTMLImageElement;
- const mouse = tweened(
- { x: 0, y: 0 },
- {
- duration: 75,
- easing: cubicOut
- }
- );
+let replaceCount = 0;
+let badgeReference: HTMLImageElement;
+const mouse = tweened(
+ { x: 0, y: 0 },
+ {
+ duration: 75,
+ easing: cubicOut
+ }
+);
- const delayedReplace = (event: Event, image: string | undefined | null) => {
- if (replaceCount >= maxReplaceCount) return;
+const delayedReplace = (event: Event, image: string | undefined | null) => {
+ if (replaceCount >= maxReplaceCount) return;
- setTimeout(() => {
- (event.target as HTMLImageElement).src = image || '';
+ setTimeout(() => {
+ (event.target as HTMLImageElement).src = image || '';
- replaceCount += 1;
- }, replaceDelay);
- };
+ replaceCount += 1;
+ }, replaceDelay);
+};
- const handleMouseMove = (event: MouseEvent) => {
- const boundingRectangle = badgeReference.getBoundingClientRect();
- const factor = 1.25;
- const limit = 50;
+const handleMouseMove = (event: MouseEvent) => {
+ const boundingRectangle = badgeReference.getBoundingClientRect();
+ const factor = 1.25;
+ const limit = 50;
- if ($mouse.x === 0 && $mouse.y === 0) $mouse = { x: event.clientX, y: event.clientY };
+ if ($mouse.x === 0 && $mouse.y === 0) $mouse = { x: event.clientX, y: event.clientY };
- $mouse.x +=
- (-(event.clientX - boundingRectangle.left - boundingRectangle.width / 2) - $mouse.x) * factor;
- $mouse.y +=
- (-(event.clientY - boundingRectangle.top - boundingRectangle.height / 2) - $mouse.y) * factor;
- $mouse.x = Math.max(Math.min($mouse.x, limit), -limit);
- $mouse.y = Math.max(Math.min($mouse.y, limit), -limit);
- };
+ $mouse.x +=
+ (-(event.clientX - boundingRectangle.left - boundingRectangle.width / 2) - $mouse.x) * factor;
+ $mouse.y +=
+ (-(event.clientY - boundingRectangle.top - boundingRectangle.height / 2) - $mouse.y) * factor;
+ $mouse.x = Math.max(Math.min($mouse.x, limit), -limit);
+ $mouse.y = Math.max(Math.min($mouse.y, limit), -limit);
+};
- const handleMouseLeave = () => {
- $mouse = { x: 0, y: 0 };
- };
+const handleMouseLeave = () => {
+ $mouse = { x: 0, y: 0 };
+};
- const isDBBadge = (b: Badge | AWCBadge): b is Badge => 'id' in b;
- const asAWCBadge = (b: Badge | AWCBadge) => b as AWCBadge;
+const isDBBadge = (b: Badge | AWCBadge): b is Badge => 'id' in b;
+const asAWCBadge = (b: Badge | AWCBadge) => b as AWCBadge;
</script>
{#if replaceCount < maxReplaceCount}
diff --git a/src/lib/Utility/Loading.svelte b/src/lib/Utility/Loading.svelte
index 92cbc1ac..67691cbb 100644
--- a/src/lib/Utility/Loading.svelte
+++ b/src/lib/Utility/Loading.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- export let type: string | undefined = undefined;
- export let percent: number | undefined = undefined;
- export let card = true;
+export let type: string | undefined = undefined;
+export let percent: number | undefined = undefined;
+export let card = true;
</script>
<div class:card>