diff options
| author | Fuwn <[email protected]> | 2024-10-09 00:41:20 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-10-09 00:41:43 -0700 |
| commit | 998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch) | |
| tree | 50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/lib/List | |
| parent | feat(graphql): add badgeCount field (diff) | |
| download | due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip | |
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/lib/List')
| -rw-r--r-- | src/lib/List/Anime/AnimeListTemplate.svelte | 170 | ||||
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 312 | ||||
| -rw-r--r-- | src/lib/List/Anime/CompletedAnimeList.svelte | 180 | ||||
| -rw-r--r-- | src/lib/List/Anime/DueAnimeList.svelte | 154 | ||||
| -rw-r--r-- | src/lib/List/Anime/DueIndexColumn.svelte | 28 | ||||
| -rw-r--r-- | src/lib/List/Anime/PlaceholderList.svelte | 26 | ||||
| -rw-r--r-- | src/lib/List/Anime/UpcomingAnimeList.svelte | 180 | ||||
| -rw-r--r-- | src/lib/List/CleanGrid.svelte | 116 | ||||
| -rw-r--r-- | src/lib/List/CleanList.svelte | 106 | ||||
| -rw-r--r-- | src/lib/List/ListTitle.svelte | 46 | ||||
| -rw-r--r-- | src/lib/List/Manga/CleanMangaList.svelte | 284 | ||||
| -rw-r--r-- | src/lib/List/Manga/MangaListTemplate.svelte | 762 | ||||
| -rw-r--r-- | src/lib/List/MediaTitleDisplay.svelte | 138 | ||||
| -rw-r--r-- | src/lib/List/covers.css | 66 | ||||
| -rw-r--r-- | src/lib/List/mediaTitle.ts | 10 |
15 files changed, 1289 insertions, 1289 deletions
diff --git a/src/lib/List/Anime/AnimeListTemplate.svelte b/src/lib/List/Anime/AnimeListTemplate.svelte index 0bb2dfe8..08583d7c 100644 --- a/src/lib/List/Anime/AnimeListTemplate.svelte +++ b/src/lib/List/Anime/AnimeListTemplate.svelte @@ -1,96 +1,96 @@ <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 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'; - 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: any; - export let completed = false; - export let plannedOnly = false; - export let upcoming = false; - export let notYetReleased = false; - export let dummy = false; + 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: any; + export let completed = false; + export let plannedOnly = false; + export let upcoming = false; + export let notYetReleased = false; + export let dummy = false; - 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(() => { - if (browser) { - const lastStoredList = localStorage.getItem( - `last${ - notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : '' - }AnimeListLength` - ); - if (lastStoredList) lastListSize = parseInt(lastStoredList); - } - }); + onMount(() => { + if (browser) { + const lastStoredList = localStorage.getItem( + `last${ + notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : '' + }AnimeListLength` + ); + if (lastStoredList) lastListSize = parseInt(lastStoredList); + } + }); </script> {#if !$subsPlease && !dummy} - <PlaceholderList count={lastListSize} {title} /> + <PlaceholderList count={lastListSize} {title} /> {:else} - {#await animeLists} - {#if previousAnimeList} - <CleanAnimeList - media={previousAnimeList} - {title} - bind:animeLists - {user} - {endTime} - bind:lastUpdatedMedia - {completed} - {notYetReleased} - {upcoming} - bind:previousAnimeList - bind:pendingUpdate - {dummy} - /> - {:else} - <PlaceholderList count={lastListSize} {title} /> - {/if} - {:then media} - {#if $identity.id === -2 && !dummy} - <PlaceholderList count={lastListSize} {title} /> - {:else} - <CleanAnimeList - media={cleanMedia(media, $settings.displayUnresolved, $subsPlease, plannedOnly)} - {title} - bind:animeLists - {user} - {endTime} - bind:lastUpdatedMedia - {completed} - {notYetReleased} - {upcoming} - bind:previousAnimeList - bind:pendingUpdate - {dummy} - /> - {/if} - {:catch} - <ListTitle time={0} count={-1337} {title} /> + {#await animeLists} + {#if previousAnimeList} + <CleanAnimeList + media={previousAnimeList} + {title} + bind:animeLists + {user} + {endTime} + bind:lastUpdatedMedia + {completed} + {notYetReleased} + {upcoming} + bind:previousAnimeList + bind:pendingUpdate + {dummy} + /> + {:else} + <PlaceholderList count={lastListSize} {title} /> + {/if} + {:then media} + {#if $identity.id === -2 && !dummy} + <PlaceholderList count={lastListSize} {title} /> + {:else} + <CleanAnimeList + media={cleanMedia(media, $settings.displayUnresolved, $subsPlease, plannedOnly)} + {title} + bind:animeLists + {user} + {endTime} + bind:lastUpdatedMedia + {completed} + {notYetReleased} + {upcoming} + bind:previousAnimeList + bind:pendingUpdate + {dummy} + /> + {/if} + {:catch} + <ListTitle time={0} count={-1337} {title} /> - <Error /> - {/await} + <Error /> + {/await} {/if} diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index f9584659..0220a366 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -1,168 +1,168 @@ <script lang="ts"> - /* 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'; - - export let media: Media[]; - export let title: any; - 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; - - let keyCacher: NodeJS.Timeout; - let totalEpisodeDueCount = media - .map((anime) => { - if (anime.mediaListEntry?.status === 'COMPLETED') return 0; - - return (anime.nextAiringEpisode?.episode || 1) - (anime.mediaListEntry?.progress || 0) - 1; - }) - .reduce((a, b) => a + b, 0); - - onMount(() => { - if (dummy) return; - - keyCacher = setInterval( - () => { - media = media; - - if ( - media.some( - (m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < Date.now() / 1000 - ) - ) - animeLists = cleanCache(user, $identity); - }, - (() => { - const airingAt = media - .filter( - (m) => - (m.status === 'RELEASING' || m.status === 'NOT_YET_RELEASED') && - m.nextAiringEpisode?.airingAt - ) - .find((m) => m.nextAiringEpisode?.airingAt)?.nextAiringEpisode?.airingAt; - const untilAiring = airingAt - ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 - : undefined; - - return untilAiring ? (untilAiring < 0 ? 1000 : untilAiring) : 1000; - })() - ); - - if (browser) - localStorage.setItem( - `last${ - notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : '' - }AnimeListLength`, - media.length.toString() - ); - }); - - onDestroy(() => clearInterval(keyCacher)); - - const increment = (anime: Media, progress: number) => { - if (!dummy && pendingUpdate !== anime.id) { - $revalidateAnime = true; - lastUpdatedMedia = anime.id; - pendingUpdate = anime.id; - - 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; - }); - } - }; + /* 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'; + + export let media: Media[]; + export let title: any; + 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; + + let keyCacher: NodeJS.Timeout; + let totalEpisodeDueCount = media + .map((anime) => { + if (anime.mediaListEntry?.status === 'COMPLETED') return 0; + + return (anime.nextAiringEpisode?.episode || 1) - (anime.mediaListEntry?.progress || 0) - 1; + }) + .reduce((a, b) => a + b, 0); + + onMount(() => { + if (dummy) return; + + keyCacher = setInterval( + () => { + media = media; + + if ( + media.some( + (m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < Date.now() / 1000 + ) + ) + animeLists = cleanCache(user, $identity); + }, + (() => { + const airingAt = media + .filter( + (m) => + (m.status === 'RELEASING' || m.status === 'NOT_YET_RELEASED') && + m.nextAiringEpisode?.airingAt + ) + .find((m) => m.nextAiringEpisode?.airingAt)?.nextAiringEpisode?.airingAt; + const untilAiring = airingAt + ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 + : undefined; + + return untilAiring ? (untilAiring < 0 ? 1000 : untilAiring) : 1000; + })() + ); + + if (browser) + localStorage.setItem( + `last${ + notYetReleased ? 'NotYetReleased' : upcoming ? 'Upcoming' : completed ? 'Completed' : '' + }AnimeListLength`, + media.length.toString() + ); + }); + + onDestroy(() => clearInterval(keyCacher)); + + const increment = (anime: Media, progress: number) => { + if (!dummy && pendingUpdate !== anime.id) { + $revalidateAnime = true; + lastUpdatedMedia = anime.id; + pendingUpdate = anime.id; + + 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; + }); + } + }; </script> <ListTitle - time={endTime / 1000} - count={$settings.displayTotalDueEpisodes && !notYetReleased && !completed && !upcoming - ? totalEpisodeDueCount - : media.length} - {title} - hideTime={dummy} - hideCount={dummy} + time={endTime / 1000} + count={$settings.displayTotalDueEpisodes && !notYetReleased && !completed && !upcoming + ? totalEpisodeDueCount + : media.length} + {title} + hideTime={dummy} + hideCount={dummy} /> {#if media.length === 0} - No anime to display. <button on:click={() => (animeLists = cleanCache(user, $identity))}> - Force refresh - </button> + No anime to display. <button on:click={() => (animeLists = cleanCache(user, $identity))}> + Force refresh + </button> {/if} {#if $settings.displayCoverModeAnime} - <CleanGrid {media} {dummy} type="anime" {upcoming} {notYetReleased}> - <div slot="title" let:title={anime} let:progress> - {#if !upcoming && !notYetReleased} - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed || dummy} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] - <br /> - <AiringTime originalAnime={anime} /> - {/if} - {:else} - <AiringTime originalAnime={anime} upcoming={true} /> - {/if} - </div> - </CleanGrid> + <CleanGrid {media} {dummy} type="anime" {upcoming} {notYetReleased}> + <div slot="title" let:title={anime} let:progress> + {#if !upcoming && !notYetReleased} + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + on:click={() => increment(anime, progress)}>+</button + > + {#if !completed || dummy} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <br /> + <AiringTime originalAnime={anime} /> + {/if} + {:else} + <AiringTime originalAnime={anime} upcoming={true} /> + {/if} + </div> + </CleanGrid> {:else} - <CleanList {media} type="anime" {upcoming} {notYetReleased} {lastUpdatedMedia}> - <span slot="information" let:title={anime} let:progress> - {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} - <span class="opaque">|</span> - {/if} - {#if !upcoming || notYetReleased} - <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] - <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} /> - </span> - {/if} - {:else} - <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} upcoming={true} /> - </span> - {/if} - </span> - </CleanList> + <CleanList {media} type="anime" {upcoming} {notYetReleased} {lastUpdatedMedia}> + <span slot="information" let:title={anime} let:progress> + {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} + <span class="opaque">|</span> + {/if} + {#if !upcoming || notYetReleased} + <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + on:click={() => increment(anime, progress)}>+</button + > + {#if !completed} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <span class:countdown={$settings.displayCountdownRightAligned}> + <AiringTime originalAnime={anime} /> + </span> + {/if} + {:else} + <span class:countdown={$settings.displayCountdownRightAligned}> + <AiringTime originalAnime={anime} upcoming={true} /> + </span> + {/if} + </span> + </CleanList> {/if} diff --git a/src/lib/List/Anime/CompletedAnimeList.svelte b/src/lib/List/Anime/CompletedAnimeList.svelte index 29d62724..76ec7207 100644 --- a/src/lib/List/Anime/CompletedAnimeList.svelte +++ b/src/lib/List/Anime/CompletedAnimeList.svelte @@ -1,108 +1,108 @@ <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 { getNotificationsContext } from 'svelte-notifications'; - 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 { getNotificationsContext } from 'svelte-notifications'; + 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 user: AniListAuthorisation = { + accessToken: '', + refreshToken: '', + expiresIn: 0, + tokenType: '' + }; + export let dummy = false; - const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; - let startTime: number; - let endTime: number; + const { addNotification } = getNotificationsContext(); + let animeLists: Promise<Media[]>; + let startTime: number; + let endTime: number; - onMount(async () => { - startTime = performance.now(); + onMount(async () => { + startTime = performance.now(); - if (dummy) { - animeLists = Promise.resolve( - 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 - ) - .sort(() => 0.5 - Math.random()) - .map((anime) => { - const nextEpisode = Math.floor(Math.random() * (anime.episodes || 0)) + 1 || 1; + if (dummy) { + animeLists = Promise.resolve( + 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 + ) + .sort(() => 0.5 - Math.random()) + .map((anime) => { + const nextEpisode = Math.floor(Math.random() * (anime.episodes || 0)) + 1 || 1; - anime.status = 'FINISHED'; - anime.nextAiringEpisode = { - airingAt: - Math.floor(Date.now() / 1000) + - Math.floor(Math.random() * 7 * 24 * 60 * 60) + - 60 * 60, - episode: Math.floor(Math.random() * (anime.episodes || 0)) + 1 || 1 - }; - anime.mediaListEntry.progress = Math.floor(Math.random() * nextEpisode) || 1; + anime.status = 'FINISHED'; + anime.nextAiringEpisode = { + airingAt: + Math.floor(Date.now() / 1000) + + Math.floor(Math.random() * 7 * 24 * 60 * 60) + + 60 * 60, + episode: Math.floor(Math.random() * (anime.episodes || 0)) + 1 || 1 + }; + anime.mediaListEntry.progress = Math.floor(Math.random() * nextEpisode) || 1; - return anime; - }) - .sort( - (a, b) => (a.nextAiringEpisode?.airingAt || 0) - (b.nextAiringEpisode?.airingAt || 0) - ) - .slice(0, 7) as unknown as Media[] - ); - } else { - animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { - addNotification - }); - } - }); + return anime; + }) + .sort( + (a, b) => (a.nextAiringEpisode?.airingAt || 0) - (b.nextAiringEpisode?.airingAt || 0) + ) + .slice(0, 7) 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) => { - const difference = (anime: Media) => - anime.episodes - (anime.mediaListEntry || { progress: 0 }).progress; + outdatedCompletedAnime.sort((a: Media, b: Media) => { + const difference = (anime: Media) => + anime.episodes - (anime.mediaListEntry || { progress: 0 }).progress; - return difference(a) - difference(b); - }); + return difference(a) - difference(b); + }); - if (!endTime) endTime = performance.now() - startTime; + if (!endTime) endTime = performance.now() - startTime; - return outdatedCompletedAnime; - }; + return outdatedCompletedAnime; + }; </script> <AnimeList - {endTime} - {cleanMedia} - bind:animeLists - {user} - title={$locale().lists.completed.anime} - completed - {dummy} + {endTime} + {cleanMedia} + bind:animeLists + {user} + title={$locale().lists.completed.anime} + completed + {dummy} /> diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte index 28744b01..0109d5fa 100644 --- a/src/lib/List/Anime/DueAnimeList.svelte +++ b/src/lib/List/Anime/DueAnimeList.svelte @@ -1,94 +1,94 @@ <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 { getNotificationsContext } from 'svelte-notifications'; - import locale from '$stores/locale'; - import identity from '$stores/identity'; + 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 { getNotificationsContext } from 'svelte-notifications'; + import locale from '$stores/locale'; + import identity from '$stores/identity'; - export let user: AniListAuthorisation; + export let user: AniListAuthorisation; - const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; - let startTime: number; - let endTime: number; + const { addNotification } = getNotificationsContext(); + let animeLists: Promise<Media[]>; + let startTime: number; + let endTime: number; - const keyCacher = setInterval(() => { - startTime = performance.now(); - endTime = -1; - animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { - forcePrune: true, - addNotification - }); - }, $settings.cacheMinutes * 1000 * 60); + const keyCacher = setInterval(() => { + startTime = performance.now(); + endTime = -1; + animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { + forcePrune: true, + addNotification + }); + }, $settings.cacheMinutes * 1000 * 60); - onMount(async () => { - startTime = performance.now(); - animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { - addNotification - }); - }); + onMount(async () => { + startTime = performance.now(); + animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { + addNotification + }); + }); - onDestroy(() => clearInterval(keyCacher)); + onDestroy(() => clearInterval(keyCacher)); - const cleanMedia = ( - anime: Media[], - displayUnresolved: boolean, - subsPlease: SubsPlease | null - ) => { - if (anime === undefined) return []; + 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) - ) - .filter( - (media: Media) => - // Outdated media - (media.nextAiringEpisode || { episode: 0 }).episode - 1 > - (media.mediaListEntry || { progress: 0 }).progress - ) - .map((media: Media) => { - if ((media.nextAiringEpisode || { episode: 0 }).episode - 1 <= 0) - media.nextAiringEpisode = { episode: -1 }; + 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) + ) + .filter( + (media: Media) => + // Outdated media + (media.nextAiringEpisode || { episode: 0 }).episode - 1 > + (media.mediaListEntry || { progress: 0 }).progress + ) + .map((media: Media) => { + if ((media.nextAiringEpisode || { episode: 0 }).episode - 1 <= 0) + media.nextAiringEpisode = { episode: -1 }; - return media; - }); + return media; + }); - if (!displayUnresolved) - dueAnime = dueAnime.filter((media: Media) => media.nextAiringEpisode?.episode !== -1); + if (!displayUnresolved) + dueAnime = dueAnime.filter((media: Media) => media.nextAiringEpisode?.episode !== -1); - dueAnime.sort((a: Media, b: Media) => { - if ($settings.displaySortedByDifference === true) { - const difference = (anime: Media) => - (anime.nextAiringEpisode?.episode === -1 - ? 99999 - : anime.nextAiringEpisode?.episode || -1) - - (anime.mediaListEntry || { progress: 0 }).progress; + dueAnime.sort((a: Media, b: Media) => { + if ($settings.displaySortedByDifference === true) { + const difference = (anime: Media) => + (anime.nextAiringEpisode?.episode === -1 + ? 99999 + : anime.nextAiringEpisode?.episode || -1) - + (anime.mediaListEntry || { progress: 0 }).progress; - return difference(a) - difference(b); - } else { - return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999); - } - }); + return difference(a) - difference(b); + } else { + return (a.nextAiringEpisode?.airingAt || 9999) - (b.nextAiringEpisode?.airingAt || 9999); + } + }); - if (!endTime || endTime === -1) endTime = performance.now() - startTime; + 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 6d81a661..61f2a178 100644 --- a/src/lib/List/Anime/DueIndexColumn.svelte +++ b/src/lib/List/Anime/DueIndexColumn.svelte @@ -1,21 +1,21 @@ <script lang="ts"> - import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; - import Skeleton from '$lib/Loading/Skeleton.svelte'; - import locale from '$stores/locale'; - import settings from '$stores/settings'; - import ListTitle from '../ListTitle.svelte'; - import AnimeList from '$lib/List/Anime/DueAnimeList.svelte'; + import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + import locale from '$stores/locale'; + import settings from '$stores/settings'; + import ListTitle from '../ListTitle.svelte'; + import AnimeList from '$lib/List/Anime/DueAnimeList.svelte'; - export let userIdentity: { id: number }; - export let user: AniListAuthorisation; + export let userIdentity: { id: number }; + export let user: AniListAuthorisation; </script> <details open={!$settings.displayAnimeCollapsed} class="list list-due"> - {#if userIdentity.id !== -2} - <AnimeList {user} /> - {:else} - <ListTitle title={$locale().lists.due.episodes} /> + {#if userIdentity.id !== -2} + <AnimeList {user} /> + {:else} + <ListTitle title={$locale().lists.due.episodes} /> - <Skeleton card={false} count={5} height="0.9rem" list /> - {/if} + <Skeleton card={false} count={5} height="0.9rem" list /> + {/if} </details> diff --git a/src/lib/List/Anime/PlaceholderList.svelte b/src/lib/List/Anime/PlaceholderList.svelte index 5d7eb908..1f701d79 100644 --- a/src/lib/List/Anime/PlaceholderList.svelte +++ b/src/lib/List/Anime/PlaceholderList.svelte @@ -1,21 +1,21 @@ <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} /> <Skeleton - card={false} - {count} - pad={$settings.displayCoverModeAnime} - height={$settings.displayCoverModeAnime ? '8rem' : '0.9rem'} - width={$settings.displayCoverModeAnime ? `${$settings.displayCoverWidth / 1.05}px` : '100%'} - list={!$settings.displayCoverModeAnime} - grid={$settings.displayCoverModeAnime} + card={false} + {count} + pad={$settings.displayCoverModeAnime} + height={$settings.displayCoverModeAnime ? '8rem' : '0.9rem'} + width={$settings.displayCoverModeAnime ? `${$settings.displayCoverWidth / 1.05}px` : '100%'} + list={!$settings.displayCoverModeAnime} + grid={$settings.displayCoverModeAnime} /> diff --git a/src/lib/List/Anime/UpcomingAnimeList.svelte b/src/lib/List/Anime/UpcomingAnimeList.svelte index 7e1e2462..7b01af86 100644 --- a/src/lib/List/Anime/UpcomingAnimeList.svelte +++ b/src/lib/List/Anime/UpcomingAnimeList.svelte @@ -1,110 +1,110 @@ <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 AnimeList from './AnimeListTemplate.svelte'; - import settings from '$stores/settings'; - import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease'; - import { getNotificationsContext } from 'svelte-notifications'; - import locale from '$stores/locale'; - import identity from '$stores/identity'; - import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match'; - import revalidateAnime from '$stores/revalidateAnime'; + 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 { getNotificationsContext } from 'svelte-notifications'; + import locale from '$stores/locale'; + import identity from '$stores/identity'; + import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match'; + import revalidateAnime from '$stores/revalidateAnime'; - export let user: AniListAuthorisation; + export let user: AniListAuthorisation; - const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; - let startTime: number; - let endTime: number; + const { addNotification } = getNotificationsContext(); + 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) || - (media.nextAiringEpisode || { episode: 0 }).episode - 1 <= - (media.mediaListEntry || { progress: 0 }).progress - ) - .map((media: Media) => { - // Adjust for planned anime - if ( - ($settings.displayPlannedAnime ? media.episodes !== 1 : true) && - (media.nextAiringEpisode || { episode: 0 }).episode - 1 <= 0 - ) - media.nextAiringEpisode = { episode: -1 }; + 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) || + (media.nextAiringEpisode || { episode: 0 }).episode - 1 <= + (media.mediaListEntry || { progress: 0 }).progress + ) + .map((media: Media) => { + // Adjust for planned anime + if ( + ($settings.displayPlannedAnime ? media.episodes !== 1 : true) && + (media.nextAiringEpisode || { episode: 0 }).episode - 1 <= 0 + ) + 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 - {endTime} - {cleanMedia} - bind:animeLists - {user} - title={$locale().lists.upcoming.episodes} - upcoming + {endTime} + {cleanMedia} + bind:animeLists + {user} + title={$locale().lists.upcoming.episodes} + upcoming /> {#if $settings.displayPlannedAnime} - <p /> + <p /> - <AnimeList - {endTime} - {cleanMedia} - bind:animeLists - {user} - title={$locale().lists.upcoming.notYetReleased} - notYetReleased - plannedOnly - /> + <AnimeList + {endTime} + {cleanMedia} + bind:animeLists + {user} + title={$locale().lists.upcoming.notYetReleased} + notYetReleased + plannedOnly + /> {/if} diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte index 18ee51f6..ec93a685 100644 --- a/src/lib/List/CleanGrid.svelte +++ b/src/lib/List/CleanGrid.svelte @@ -1,69 +1,69 @@ <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 media: Media[]; + export let dummy = false; + export let type: 'anime' | 'manga'; + export let upcoming = false; + export let notYetReleased = false; - let uniqueID = new Date().getTime(); + let uniqueID = new Date().getTime(); </script> <div - class="covers" - style={`grid-template-columns: repeat(auto-fill, minmax(${$settings.displayCoverWidth}px, 1fr))`} + class="covers" + style={`grid-template-columns: repeat(auto-fill, minmax(${$settings.displayCoverWidth}px, 1fr))`} > - {#each media as title} - {@const progress = (title.mediaListEntry || { progress: 0 }).progress} + {#each media as title} + {@const progress = (title.mediaListEntry || { progress: 0 }).progress} - {#if type === 'anime' ? upcoming || notYetReleased || progress !== (title.nextAiringEpisode?.episode || 9999) - 1 : progress != title.episodes} - <div class="cover-card" id={`${type}-${title.id}-${uniqueID}`}> - <LinkedTooltip - pin={`${type}-${title.id}-${uniqueID}`} - content={title ? mediaTitle(title) : ''} - relative={dummy} - > - <div class="cover-card-image"> - <a - href={$settings.displayCopyMediaTitleNotLink - ? '#' - : outboundLink(title, type, $settings.displayOutboundLinksTo)} - on:click={(e) => { - if ($settings.displayCopyMediaTitleNotLink) { - e.preventDefault(); + {#if type === 'anime' ? upcoming || notYetReleased || progress !== (title.nextAiringEpisode?.episode || 9999) - 1 : progress != title.episodes} + <div class="cover-card" id={`${type}-${title.id}-${uniqueID}`}> + <LinkedTooltip + pin={`${type}-${title.id}-${uniqueID}`} + content={title ? mediaTitle(title) : ''} + relative={dummy} + > + <div class="cover-card-image"> + <a + href={$settings.displayCopyMediaTitleNotLink + ? '#' + : outboundLink(title, type, $settings.displayOutboundLinksTo)} + on:click={(e) => { + if ($settings.displayCopyMediaTitleNotLink) { + e.preventDefault(); - navigator.clipboard.writeText(title.title.romaji); - } - }} - target="_blank" - > - <span class="cover-container"> - <ParallaxImage - source={$settings.displayDataSaver - ? title.coverImage.medium - : title.coverImage.extraLarge} - alternativeText="Cover" - limit={12.5} - classList={`cover${ - title.isAdult && $settings.displayBlurAdultContent ? ' adult' : '' - }`} - /> - </span> - </a> - </div> - </LinkedTooltip> + navigator.clipboard.writeText(title.title.romaji); + } + }} + target="_blank" + > + <span class="cover-container"> + <ParallaxImage + source={$settings.displayDataSaver + ? title.coverImage.medium + : title.coverImage.extraLarge} + alternativeText="Cover" + limit={12.5} + classList={`cover${ + title.isAdult && $settings.displayBlurAdultContent ? ' adult' : '' + }`} + /> + </span> + </a> + </div> + </LinkedTooltip> - <div class="cover-title"> - <slot name="title" {progress} {title} /> - </div> - </div> - {/if} - {/each} + <div class="cover-title"> + <slot name="title" {progress} {title} /> + </div> + </div> + {/if} + {/each} </div> diff --git a/src/lib/List/CleanList.svelte b/src/lib/List/CleanList.svelte index d07f45f1..47811932 100644 --- a/src/lib/List/CleanList.svelte +++ b/src/lib/List/CleanList.svelte @@ -1,62 +1,62 @@ <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 media: Media[]; + export let type: 'anime' | 'manga'; + export let upcoming = false; + export let notYetReleased = false; + export let lastUpdatedMedia: number; </script> <ul> - {#each media as title} - {@const progress = (title.mediaListEntry || { progress: 0 }).progress} + {#each media as title} + {@const progress = (title.mediaListEntry || { progress: 0 }).progress} - {#if type === 'anime' ? upcoming || notYetReleased || progress !== (title.nextAiringEpisode?.episode || 9999) - 1 : progress !== title.episodes} - <li class="entry"> - <span class="content"> - <LinkedTooltip - id={`${type}-${title.id}`} - content={`<img src="${ - $settings.displayDataSaver ? title.coverImage.medium : title.coverImage.extraLarge - }" style="width: 250px; object-fit: cover; border-radius: 8px;" />`} - pin={`${type}-${title.id}`} - pinPosition={type === 'anime' ? 'right' : 'left'} - disable={!$settings.displayHoverCover} - > - <a - href={$settings.displayCopyMediaTitleNotLink - ? '#' - : outboundLink(title, type, $settings.displayOutboundLinksTo)} - on:click={(e) => { - if ($settings.displayCopyMediaTitleNotLink) { - e.preventDefault(); + {#if type === 'anime' ? upcoming || notYetReleased || progress !== (title.nextAiringEpisode?.episode || 9999) - 1 : progress !== title.episodes} + <li class="entry"> + <span class="content"> + <LinkedTooltip + id={`${type}-${title.id}`} + content={`<img src="${ + $settings.displayDataSaver ? title.coverImage.medium : title.coverImage.extraLarge + }" style="width: 250px; object-fit: cover; border-radius: 8px;" />`} + pin={`${type}-${title.id}`} + pinPosition={type === 'anime' ? 'right' : 'left'} + disable={!$settings.displayHoverCover} + > + <a + href={$settings.displayCopyMediaTitleNotLink + ? '#' + : outboundLink(title, type, $settings.displayOutboundLinksTo)} + on:click={(e) => { + if ($settings.displayCopyMediaTitleNotLink) { + e.preventDefault(); - navigator.clipboard.writeText(title.title.romaji); - } - }} - target="_blank" - > - <span - style={lastUpdatedMedia === title.id && title.chapters !== progress - ? 'color: lightcoral' - : ''} - > - <MediaTitleDisplay title={title.title} /> - </span> - </a> - </LinkedTooltip> - {#if $settings.displaySocialButton} - [<a href={`https://anilist.co/${type}/${title.id}/social`} target="_blank">S</a>] - {/if} + navigator.clipboard.writeText(title.title.romaji); + } + }} + target="_blank" + > + <span + style={lastUpdatedMedia === title.id && title.chapters !== progress + ? 'color: lightcoral' + : ''} + > + <MediaTitleDisplay title={title.title} /> + </span> + </a> + </LinkedTooltip> + {#if $settings.displaySocialButton} + [<a href={`https://anilist.co/${type}/${title.id}/social`} target="_blank">S</a>] + {/if} - <slot name="information" {progress} {title} /> - </span> - </li> - {/if} - {/each} + <slot name="information" {progress} {title} /> + </span> + </li> + {/if} + {/each} </ul> diff --git a/src/lib/List/ListTitle.svelte b/src/lib/List/ListTitle.svelte index 2c597c09..21013b52 100644 --- a/src/lib/List/ListTitle.svelte +++ b/src/lib/List/ListTitle.svelte @@ -1,32 +1,32 @@ <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> - <span title={title.hint || undefined} use:tooltip>{title.title}</span> - {#if !hideCount}[{count === -1337 ? '...' : count}]{/if} - <!-- {#if !hideCount}[{#if count === -1337}...{:else}<NumberTicker + <span title={title.hint || undefined} use:tooltip>{title.title}</span> + {#if !hideCount}[{count === -1337 ? '...' : count}]{/if} + <!-- {#if !hideCount}[{#if count === -1337}...{:else}<NumberTicker end={count} duration={Math.min(2500, Math.max(500, Math.abs(count - 0) * 10))} />{/if}]{/if} --> - {#if !hideTime} - <small class="opaque">{time ? time.toFixed(3) : '...'}s</small> - {/if} - <slot /> - {#if progress !== undefined} - <button class="badge unclickable-button button-badge badge-info"> - {progress.toFixed(0)}% - </button> - {/if} + {#if !hideTime} + <small class="opaque">{time ? time.toFixed(3) : '...'}s</small> + {/if} + <slot /> + {#if progress !== undefined} + <button class="badge unclickable-button button-badge badge-info"> + {progress.toFixed(0)}% + </button> + {/if} </summary> diff --git a/src/lib/List/Manga/CleanMangaList.svelte b/src/lib/List/Manga/CleanMangaList.svelte index be02bace..dfaa187c 100644 --- a/src/lib/List/Manga/CleanMangaList.svelte +++ b/src/lib/List/Manga/CleanMangaList.svelte @@ -1,166 +1,166 @@ <script lang="ts"> - 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 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'; - 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 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; - let serviceStatusResponse: Promise<Response>; + let serviceStatusResponse: Promise<Response>; - onMount(() => { - serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping')); + onMount(() => { + serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping')); - if (browser) - localStorage.setItem(`last${due ? '' : 'Completed'}MangaListLength`, media.length.toString()); - }); + if (browser) + localStorage.setItem(`last${due ? '' : 'Completed'}MangaListLength`, media.length.toString()); + }); - const increment = (manga: Media) => { - if (!(pendingUpdate === manga.id || dummy)) - updateMedia(manga.id, manga.mediaListEntry?.progress, media); - }; + const increment = (manga: Media) => { + if (!(pendingUpdate === manga.id || dummy)) + updateMedia(manga.id, manga.mediaListEntry?.progress, media); + }; </script> {#if authorised} - <ListTitle - count={media.length} - time={endTime / 1000} - title={due - ? $locale().lists.due.mangaAndLightNovels - : $locale().lists.completed.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - > - {#if !dummy} - <button - class="small-button" - title="Force a full refresh" - on:click={cleanCache} - data-umami-event="Force Refresh Manga">Refresh</button - > - {/if} - </ListTitle> + <ListTitle + count={media.length} + time={endTime / 1000} + title={due + ? $locale().lists.due.mangaAndLightNovels + : $locale().lists.completed.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + > + {#if !dummy} + <button + class="small-button" + title="Force a full refresh" + on:click={cleanCache} + data-umami-event="Force Refresh Manga">Refresh</button + > + {/if} + </ListTitle> {/if} {#if rateLimited} - {#await serviceStatusResponse} - <Skeleton card={false} count={1} height="0.9rem" list /> - {:then status} - {#if status} - {#if status.status === 503} - <a href="https://due.moe">due.moe</a>'s manga data source is currently down for maintenance. - Please check back later. - {:else if status.status !== 200} - <a href="https://due.moe">due.moe</a>'s manga data source is currently unavailable. Please - check back later. - {:else} - <Error /> - {/if} - {:else} - <Skeleton card={false} count={1} height="0.9rem" list /> - {/if} - {:catch} - <a href="https://due.moe">due.moe</a>'s manga data source is currently unreachable. Please check - back later. - {/await} + {#await serviceStatusResponse} + <Skeleton card={false} count={1} height="0.9rem" list /> + {:then status} + {#if status} + {#if status.status === 503} + <a href="https://due.moe">due.moe</a>'s manga data source is currently down for maintenance. + Please check back later. + {:else if status.status !== 200} + <a href="https://due.moe">due.moe</a>'s manga data source is currently unavailable. Please + check back later. + {:else} + <Error /> + {/if} + {:else} + <Skeleton card={false} count={1} height="0.9rem" list /> + {/if} + {:catch} + <a href="https://due.moe">due.moe</a>'s manga data source is currently unreachable. Please check + back later. + {/await} {/if} {#if media.length === 0 && !rateLimited} - {#if rateLimited} - <p /> - {/if} + {#if rateLimited} + <p /> + {/if} - <p> - No manga to display. <button on:click={cleanCache} data-umami-event="Force Refresh No Manga" - >Force refresh</button - > - </p> + <p> + No manga to display. <button on:click={cleanCache} data-umami-event="Force Refresh No Manga" + >Force refresh</button + > + </p> - <span> - Don't read manga? <button - on:click={() => ($settings.disableManga = true)} - data-umami-event="Disable No Manga">Hide the manga panel</button - > - You can re-enable it later in the <a href={root('/settings')}>Settings</a>. - </span> + <span> + Don't read manga? <button + on:click={() => ($settings.disableManga = true)} + data-umami-event="Disable No Manga">Hide the manga panel</button + > + You can re-enable it later in the <a href={root('/settings')}>Settings</a>. + </span> {/if} {#if $settings.displayCoverModeManga || dummy} - <CleanGrid {media} {dummy} type="manga"> - <div slot="title" let:title={manga} let:progress> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + <CleanGrid {media} {dummy} type="manga"> + <div slot="title" let:title={manga} let:progress> + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + on:click={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </div> - </CleanGrid> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </div> + </CleanGrid> {:else} - <CleanList {media} type="manga" {lastUpdatedMedia}> - <span slot="information" let:title={manga} let:progress> - <span class="opaque">|</span> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + <CleanList {media} type="manga" {lastUpdatedMedia}> + <span slot="information" let:title={manga} let:progress> + <span class="opaque">|</span> + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + on:click={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </span> - </CleanList> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </span> + </CleanList> {/if} diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte index fe01465f..1303419f 100644 --- a/src/lib/List/Manga/MangaListTemplate.svelte +++ b/src/lib/List/Manga/MangaListTemplate.svelte @@ -1,387 +1,387 @@ <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 authorisedJson from '$lib/Data/Static/authorised.json'; - import { incrementMediaProgress } from '$lib/Media/Anime/cache'; - import { getNotificationsContext } from 'svelte-notifications'; - 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'; - - export let user: AniListAuthorisation = { - accessToken: '', - refreshToken: '', - expiresIn: 0, - tokenType: '' - }; - export let displayUnresolved: boolean; - export let due: boolean; - export let dummy = $settings.debugDummyLists || false; - - const { addNotification } = getNotificationsContext(); - const authorised = authorisedJson.includes($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; - - const keyCacher = setInterval(() => { - startTime = performance.now(); - endTime = -1; - mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { - addNotification - }); - }, $settings.cacheMinutes * 1000 * 60); - - onMount(async () => { - if (browser) { - const lastStoredList = localStorage.getItem(`last${due ? '' : 'Completed'}MangaListLength`); - - if (lastStoredList) lastListSize = parseInt(lastStoredList); - } - - startTime = performance.now(); - - if (dummy) { - mangaLists = Promise.resolve( - 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' - ) - .sort(() => 0.5 - Math.random()) - .map((manga) => { - manga.status = 'FINISHED'; - manga.episodes = Math.floor(Math.random() * (manga.chapters || 0)) as unknown as null; - manga.mediaListEntry.progress = Math.floor(Math.random() * (manga.episodes || 0)) + 1; - - return manga; - }) - .slice(0, 7) as unknown as Media[] - ); - } else { - mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { - addNotification - }); - } - }); - - onDestroy(() => clearInterval(keyCacher)); - - const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => { - progress = 0; - - if (manga && dummy) return manga; - - if (manga === undefined) return []; - - if (!authorised && (await database.chapters.toArray()).length <= 0 && !force) return []; - - if (authorised) { - let refreshing = false; - - 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) - ); - })(); - } - } - - 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 (!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 (count === -22) { - rateLimited = true; - - break; - } - - chapterCounts.push(count); - - if (progress < 100) progress += progressStep; - } - - finalMedia.forEach((m: Media, i) => (m.episodes = chapterCounts[i] || -1337)); - - if (!displayUnresolved) finalMedia = finalMedia.filter((m: Media) => m.episodes !== -1337); - - finalMedia.sort( - (a: Media, b: Media) => - (a.episodes || 9999) - - (a.mediaListEntry || { progress: 0 }).progress - - ((b.episodes || 9999) - (b.mediaListEntry || { progress: 0 }).progress) - ); - - 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 (!endTime || endTime === -1) endTime = performance.now() - startTime; - - return finalMedia; - }; - - const updateMedia = async (id: number, progress: number | undefined, media: Media[]) => { - pendingUpdate = id; - lastUpdatedMedia = id; - - await database.chapters.delete(id); - - incrementMediaProgress(id, progress, user, () => { - previousMangaList = media; - - 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; - - pruneAllManga().then(() => { - mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { - forcePrune: true - }); - }); - }; + 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 authorisedJson from '$lib/Data/Static/authorised.json'; + import { incrementMediaProgress } from '$lib/Media/Anime/cache'; + import { getNotificationsContext } from 'svelte-notifications'; + 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'; + + export let user: AniListAuthorisation = { + accessToken: '', + refreshToken: '', + expiresIn: 0, + tokenType: '' + }; + export let displayUnresolved: boolean; + export let due: boolean; + export let dummy = $settings.debugDummyLists || false; + + const { addNotification } = getNotificationsContext(); + const authorised = authorisedJson.includes($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; + + const keyCacher = setInterval(() => { + startTime = performance.now(); + endTime = -1; + mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { + addNotification + }); + }, $settings.cacheMinutes * 1000 * 60); + + onMount(async () => { + if (browser) { + const lastStoredList = localStorage.getItem(`last${due ? '' : 'Completed'}MangaListLength`); + + if (lastStoredList) lastListSize = parseInt(lastStoredList); + } + + startTime = performance.now(); + + if (dummy) { + mangaLists = Promise.resolve( + 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' + ) + .sort(() => 0.5 - Math.random()) + .map((manga) => { + manga.status = 'FINISHED'; + manga.episodes = Math.floor(Math.random() * (manga.chapters || 0)) as unknown as null; + manga.mediaListEntry.progress = Math.floor(Math.random() * (manga.episodes || 0)) + 1; + + return manga; + }) + .slice(0, 7) as unknown as Media[] + ); + } else { + mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { + addNotification + }); + } + }); + + onDestroy(() => clearInterval(keyCacher)); + + const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => { + progress = 0; + + if (manga && dummy) return manga; + + if (manga === undefined) return []; + + if (!authorised && (await database.chapters.toArray()).length <= 0 && !force) return []; + + if (authorised) { + let refreshing = false; + + 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) + ); + })(); + } + } + + 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 (!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 (count === -22) { + rateLimited = true; + + break; + } + + chapterCounts.push(count); + + if (progress < 100) progress += progressStep; + } + + finalMedia.forEach((m: Media, i) => (m.episodes = chapterCounts[i] || -1337)); + + if (!displayUnresolved) finalMedia = finalMedia.filter((m: Media) => m.episodes !== -1337); + + finalMedia.sort( + (a: Media, b: Media) => + (a.episodes || 9999) - + (a.mediaListEntry || { progress: 0 }).progress - + ((b.episodes || 9999) - (b.mediaListEntry || { progress: 0 }).progress) + ); + + 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 (!endTime || endTime === -1) endTime = performance.now() - startTime; + + return finalMedia; + }; + + const updateMedia = async (id: number, progress: number | undefined, media: Media[]) => { + pendingUpdate = id; + lastUpdatedMedia = id; + + await database.chapters.delete(id); + + incrementMediaProgress(id, progress, user, () => { + previousMangaList = media; + + 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; + + pruneAllManga().then(() => { + mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, { + forcePrune: true + }); + }); + }; </script> {#await mangaLists} - {#if previousMangaList} - <CleanMangaList - media={previousMangaList} - {cleanCache} - {lastUpdatedMedia} - {updateMedia} - {endTime} - {pendingUpdate} - {due} - {rateLimited} - {authorised} - {dummy} - /> - {:else} - {#if !authorised} - <ListTitle - count={0} - time={endTime / 1000} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - > - {#if !dummy} - <button - data-umami-event="Force Refresh Manga" - title="Force a full refresh" - on:click={() => { - cleanCache(); - - forceFlag = true; - }}>Refresh</button - > - {/if} - </ListTitle> - {:else} - <ListTitle - {progress} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - /> - {/if} - - <Skeleton card={false} count={lastListSize} height="0.9rem" list /> - {/if} + {#if previousMangaList} + <CleanMangaList + media={previousMangaList} + {cleanCache} + {lastUpdatedMedia} + {updateMedia} + {endTime} + {pendingUpdate} + {due} + {rateLimited} + {authorised} + {dummy} + /> + {:else} + {#if !authorised} + <ListTitle + count={0} + time={endTime / 1000} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + > + {#if !dummy} + <button + data-umami-event="Force Refresh Manga" + title="Force a full refresh" + on:click={() => { + cleanCache(); + + forceFlag = true; + }}>Refresh</button + > + {/if} + </ListTitle> + {:else} + <ListTitle + {progress} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + /> + {/if} + + <Skeleton card={false} count={lastListSize} height="0.9rem" list /> + {/if} {:then media} - {#await cleanMedia(media, displayUnresolved, forceFlag)} - {#if previousMangaList} - <CleanMangaList - media={previousMangaList} - {cleanCache} - {lastUpdatedMedia} - {updateMedia} - {endTime} - {pendingUpdate} - {due} - {rateLimited} - {authorised} - {dummy} - /> - {:else} - {#if !authorised} - <ListTitle - count={0} - time={endTime / 1000} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - > - {#if !dummy} - <button - data-umami-event="Force Refresh Manga" - title="Force a full refresh" - on:click={() => { - cleanCache(); - - forceFlag = true; - }}>Refresh</button - > - {/if} - </ListTitle> - {:else} - <ListTitle - {progress} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - /> - {/if} - - <Skeleton card={false} count={lastListSize} height="0.9rem" list /> - {/if} - {:then cleanedMedia} - {#if !authorised} - <ListTitle - count={cleanedMedia.length} - time={endTime / 1000} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - > - {#if !dummy} - <button - data-umami-event="Force Refresh Manga" - title="Force a full refresh" - on:click={() => { - cleanCache(); - - forceFlag = true; - }}>Refresh</button - > - {/if} - </ListTitle> - {/if} - - <CleanMangaList - media={cleanedMedia} - {cleanCache} - {lastUpdatedMedia} - {updateMedia} - {endTime} - {pendingUpdate} - {due} - {rateLimited} - {authorised} - {dummy} - /> - {:catch} - {#if authorised} - <ListTitle - count={-1337} - time={0} - title={$locale().lists.due.mangaAndLightNovels} - hideTime={dummy} - hideCount={dummy} - /> - {/if} - - <Error list={false} /> - {/await} + {#await cleanMedia(media, displayUnresolved, forceFlag)} + {#if previousMangaList} + <CleanMangaList + media={previousMangaList} + {cleanCache} + {lastUpdatedMedia} + {updateMedia} + {endTime} + {pendingUpdate} + {due} + {rateLimited} + {authorised} + {dummy} + /> + {:else} + {#if !authorised} + <ListTitle + count={0} + time={endTime / 1000} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + > + {#if !dummy} + <button + data-umami-event="Force Refresh Manga" + title="Force a full refresh" + on:click={() => { + cleanCache(); + + forceFlag = true; + }}>Refresh</button + > + {/if} + </ListTitle> + {:else} + <ListTitle + {progress} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + /> + {/if} + + <Skeleton card={false} count={lastListSize} height="0.9rem" list /> + {/if} + {:then cleanedMedia} + {#if !authorised} + <ListTitle + count={cleanedMedia.length} + time={endTime / 1000} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + > + {#if !dummy} + <button + data-umami-event="Force Refresh Manga" + title="Force a full refresh" + on:click={() => { + cleanCache(); + + forceFlag = true; + }}>Refresh</button + > + {/if} + </ListTitle> + {/if} + + <CleanMangaList + media={cleanedMedia} + {cleanCache} + {lastUpdatedMedia} + {updateMedia} + {endTime} + {pendingUpdate} + {due} + {rateLimited} + {authorised} + {dummy} + /> + {:catch} + {#if authorised} + <ListTitle + count={-1337} + time={0} + title={$locale().lists.due.mangaAndLightNovels} + hideTime={dummy} + hideCount={dummy} + /> + {/if} + + <Error list={false} /> + {/await} {/await} diff --git a/src/lib/List/MediaTitleDisplay.svelte b/src/lib/List/MediaTitleDisplay.svelte index 51c6cd13..6a886704 100644 --- a/src/lib/List/MediaTitleDisplay.svelte +++ b/src/lib/List/MediaTitleDisplay.svelte @@ -1,77 +1,77 @@ <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)}`}> - {#if $settings.displayTitleFormat === 'native'} - {#if $settings.displayFurigana} - {@const kana = abbreviate - ? abbreviated(wanakana.toKana(title.native), abbreviateTo) - : wanakana.toKana(title.native)} - {@const native = abbreviate ? abbreviated(title.native, abbreviateTo) : title.native} + {#if $settings.displayTitleFormat === 'native'} + {#if $settings.displayFurigana} + {@const kana = abbreviate + ? abbreviated(wanakana.toKana(title.native), abbreviateTo) + : wanakana.toKana(title.native)} + {@const native = abbreviate ? abbreviated(title.native, abbreviateTo) : title.native} - <LinkedTooltip - content={title.english || title.romaji || title.native} - disable={tooltip} - pin={`title-display-${compressToBase64(title.native)}`} - relative - ignoreAnchorStyling - > - {#if kana === native} - {native} - {:else} - <ruby> - {native} - <rt> - {kana} - </rt> - </ruby> - {/if} - </LinkedTooltip> - {:else} - <LinkedTooltip - content={title.english || title.romaji || title.native} - disable={tooltip} - pin={`title-display-${compressToBase64(title.native)}`} - relative - ignoreAnchorStyling - > - {abbreviate ? abbreviated(title.native, abbreviateTo) : title.native} - </LinkedTooltip> - {/if} - {:else if $settings.displayTitleFormat === 'romaji'} - <LinkedTooltip - content={title.english || title.romaji || title.native} - disable={tooltip} - pin={`title-display-${compressToBase64(title.native)}`} - relative - ignoreAnchorStyling - > - {abbreviate ? abbreviated(title.romaji, abbreviateTo) : title.romaji} - </LinkedTooltip> - {:else} - <LinkedTooltip - content={title.romaji || title.native} - disable={tooltip} - pin={`title-display-${compressToBase64(title.native)}`} - relative - ignoreAnchorStyling - > - {abbreviate - ? abbreviated(title.english || title.romaji || title.native, abbreviateTo) - : title.english || title.romaji || title.native} - </LinkedTooltip> - {/if} + <LinkedTooltip + content={title.english || title.romaji || title.native} + disable={tooltip} + pin={`title-display-${compressToBase64(title.native)}`} + relative + ignoreAnchorStyling + > + {#if kana === native} + {native} + {:else} + <ruby> + {native} + <rt> + {kana} + </rt> + </ruby> + {/if} + </LinkedTooltip> + {:else} + <LinkedTooltip + content={title.english || title.romaji || title.native} + disable={tooltip} + pin={`title-display-${compressToBase64(title.native)}`} + relative + ignoreAnchorStyling + > + {abbreviate ? abbreviated(title.native, abbreviateTo) : title.native} + </LinkedTooltip> + {/if} + {:else if $settings.displayTitleFormat === 'romaji'} + <LinkedTooltip + content={title.english || title.romaji || title.native} + disable={tooltip} + pin={`title-display-${compressToBase64(title.native)}`} + relative + ignoreAnchorStyling + > + {abbreviate ? abbreviated(title.romaji, abbreviateTo) : title.romaji} + </LinkedTooltip> + {:else} + <LinkedTooltip + content={title.romaji || title.native} + disable={tooltip} + pin={`title-display-${compressToBase64(title.native)}`} + relative + ignoreAnchorStyling + > + {abbreviate + ? abbreviated(title.english || title.romaji || title.native, abbreviateTo) + : title.english || title.romaji || title.native} + </LinkedTooltip> + {/if} </span> diff --git a/src/lib/List/covers.css b/src/lib/List/covers.css index 7038ffbf..e0b69c78 100644 --- a/src/lib/List/covers.css +++ b/src/lib/List/covers.css @@ -1,68 +1,68 @@ .covers { - display: grid; - justify-content: center; - gap: 1em 0.5em; - margin-top: 0.5rem; + display: grid; + justify-content: center; + gap: 1em 0.5em; + margin-top: 0.5rem; } .cover { - background-size: cover; - background-position: center; - border-radius: 8px; + background-size: cover; + background-position: center; + border-radius: 8px; } .cover-title { - text-align: center; - margin: 0.25rem; + text-align: center; + margin: 0.25rem; } .cover-card { - display: inline-block; + display: inline-block; } .cover-card-image { - border-radius: 8px; - transition: transform 0.45s ease, box-shadow 0.45s ease; - margin-bottom: 0.5em; + border-radius: 8px; + transition: transform 0.45s ease, box-shadow 0.45s ease; + margin-bottom: 0.5em; } .cover-card-image:hover { - transform: scale(1.1); - position: relative; - transition: transform 0.45s ease, box-shadow 0.45s ease; + transform: scale(1.1); + position: relative; + transition: transform 0.45s ease, box-shadow 0.45s ease; } .entry::after { - content: ''; - display: table; - clear: both; + content: ''; + display: table; + clear: both; } .countdown { - white-space: nowrap; - float: right; + white-space: nowrap; + float: right; } .adult { - filter: blur(10px) grayscale(50%) brightness(0.5); - transition: filter 0.3s ease; + filter: blur(10px) grayscale(50%) brightness(0.5); + transition: filter 0.3s ease; } .adult:hover { - filter: blur(0) !important; - transition: filter 0.3s ease; + filter: blur(0) !important; + transition: filter 0.3s ease; } .cover-container { - /* overflow: hidden; */ - display: flex; - justify-content: center; - align-items: center; - border-radius: 8px; + /* overflow: hidden; */ + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; } button { - margin-top: unset; - vertical-align: unset; - height: unset; + margin-top: unset; + vertical-align: unset; + height: unset; } diff --git a/src/lib/List/mediaTitle.ts b/src/lib/List/mediaTitle.ts index 9186b5d1..e59a3091 100644 --- a/src/lib/List/mediaTitle.ts +++ b/src/lib/List/mediaTitle.ts @@ -3,14 +3,14 @@ import settings from '$stores/settings'; import { get } from 'svelte/store'; export interface Title { - title: string; - hint: string; + title: string; + hint: string; } export const mediaTitle = (media: Media) => { - if (!media) return 'Loading ...'; + if (!media) return 'Loading ...'; - const title = media.title; + const title = media.title; - return title[get(settings).displayTitleFormat] || title.english || title.romaji || title.native; + return title[get(settings).displayTitleFormat] || title.english || title.romaji || title.native; }; |