diff options
| author | Fuwn <[email protected]> | 2026-03-01 16:04:11 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-03-01 16:04:11 -0800 |
| commit | 48f0c30d47d62e4f35706edb93a1bb2f97eba14c (patch) | |
| tree | 44866d7a61adfdf01a780e0108c370294d3db78b /src/lib/List/Manga | |
| parent | chore(biome): re-enable useAltText rule (diff) | |
| download | due.moe-48f0c30d47d62e4f35706edb93a1bb2f97eba14c.tar.xz due.moe-48f0c30d47d62e4f35706edb93a1bb2f97eba14c.zip | |
chore(biome): enable svelte formatting
Diffstat (limited to 'src/lib/List/Manga')
| -rw-r--r-- | src/lib/List/Manga/CleanMangaList.svelte | 154 | ||||
| -rw-r--r-- | src/lib/List/Manga/MangaListTemplate.svelte | 449 |
2 files changed, 295 insertions, 308 deletions
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} |