diff options
| -rw-r--r-- | src/lib/CommandPalette/syncActions.ts | 1 | ||||
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 56 | ||||
| -rw-r--r-- | src/lib/List/Anime/DueAnimeList.svelte | 14 | ||||
| -rw-r--r-- | src/lib/Locale/english.ts | 4 | ||||
| -rw-r--r-- | src/lib/Locale/japanese.ts | 4 | ||||
| -rw-r--r-- | src/lib/Locale/layout.ts | 4 | ||||
| -rw-r--r-- | src/lib/Media/Anime/cache.ts | 16 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/Debug.svelte | 7 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/SettingSync.svelte | 2 | ||||
| -rw-r--r-- | src/lib/Tooltip/LinkedTooltip.svelte | 1 | ||||
| -rw-r--r-- | src/lib/Tooltip/tooltip.ts | 2 | ||||
| -rw-r--r-- | src/stores/settings.ts | 3 | ||||
| -rw-r--r-- | src/styles/input.css | 1 |
13 files changed, 75 insertions, 40 deletions
diff --git a/src/lib/CommandPalette/syncActions.ts b/src/lib/CommandPalette/syncActions.ts index 49859221..90c6a931 100644 --- a/src/lib/CommandPalette/syncActions.ts +++ b/src/lib/CommandPalette/syncActions.ts @@ -24,6 +24,7 @@ export const syncActions = ( fetch(root(`/api/configuration`), { method: "PUT", + headers: { "Content-Type": "application/json" }, body: JSON.stringify(get(settings)), }) .then((response) => { diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 76701a93..42f4d933 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -9,6 +9,7 @@ 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 { hasDueEpisodes } from "$lib/Media/Anime/Airing/classify"; import { browser } from "$app/environment"; import identity from "$stores/identity"; import "../covers.css"; @@ -109,6 +110,9 @@ $: filteredMedia = ? media : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]); +$: if (browser && !dummy && media && previousAnimeList !== media) + previousAnimeList = media; + const updateSelectedList = (event: Event) => { const nextSelectedList = (event.currentTarget as HTMLSelectElement).value; @@ -169,7 +173,7 @@ const scheduleAiringRefresh = () => { m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < now, ) ) - animeLists = cleanCache(user, $identity); + $revalidateAnime = $revalidateAnime + 1; scheduleAiringRefresh(); }, @@ -206,28 +210,30 @@ $: if (browser && !dummy) { onDestroy(() => clearAiringRefreshTimeout()); const increment = (anime: Media, progress: number) => { - if (!dummy && pendingUpdate !== anime.id) { - $revalidateAnime = $revalidateAnime + 1; - 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; - }, - ); - } + if (dummy || pendingUpdate === anime.id) return; + + pendingUpdate = anime.id; + lastUpdatedMedia = anime.id; + + const target = media.find((m) => m.id === anime.id); + + if (target?.mediaListEntry) target.mediaListEntry.progress = progress + 1; + + media = + !completed && target && !hasDueEpisodes(target) + ? media.filter((m) => m.id !== anime.id) + : media; + + incrementMediaProgress( + anime.id, + anime.mediaListEntry?.progress, + user, + (skipped) => { + pendingUpdate = null; + + if (!skipped) $revalidateAnime = $revalidateAnime + 1; + }, + ); }; </script> @@ -279,7 +285,7 @@ const increment = (anime: Media, progress: number) => { > <div slot="title" let:title={anime} let:progress> {#if !upcoming && !notYetReleased} - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + {progress}{@html totalEpisodes(anime)} <button class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} @@ -312,7 +318,7 @@ const increment = (anime: Media, progress: number) => { {/if} {#if !upcoming || notYetReleased} <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + {progress}{@html totalEpisodes(anime)} <button class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte index d2c47ebe..29af4a88 100644 --- a/src/lib/List/Anime/DueAnimeList.svelte +++ b/src/lib/List/Anime/DueAnimeList.svelte @@ -31,19 +31,7 @@ const restartKeyCacher = (cacheMinutes: number) => { keyCacheMinutes = cacheMinutes; keyCacher = setInterval( () => { - startTime = performance.now(); - endTime = -1; - animeLists = mediaListCollection( - user, - $identity, - Type.Anime, - $anime, - $lastPruneTimes.anime, - { - forcePrune: true, - addNotification, - }, - ); + $revalidateAnime = $revalidateAnime + 1; }, cacheMinutes * 1000 * 60, ); diff --git a/src/lib/Locale/english.ts b/src/lib/Locale/english.ts index 0cfc4035..f0195948 100644 --- a/src/lib/Locale/english.ts +++ b/src/lib/Locale/english.ts @@ -284,6 +284,10 @@ const English: Locale = { "If you are having issues with data loading or logging out, it is recommended to clear your site data.", }, dummyLists: "Use dummy media lists", + dryRunMutations: { + title: "Dry-run mutations", + hint: "Block outgoing list updates (e.g., the + button) and skip the post-mutation refresh so optimistic UI changes persist locally for testing. No data is sent to AniList.", + }, }, hololive: { live: "LIVE", diff --git a/src/lib/Locale/japanese.ts b/src/lib/Locale/japanese.ts index af309a25..8e3744d4 100644 --- a/src/lib/Locale/japanese.ts +++ b/src/lib/Locale/japanese.ts @@ -284,6 +284,10 @@ const Japanese: Locale = { "データの読み込みやログアウトに問題がある場合、サイトデータをクリアすることをお勧めします。", }, dummyLists: "ダミーメディアリストを使用する", + dryRunMutations: { + title: "ドライランモード", + hint: "リストの更新リクエスト(+ボタンなど)の送信をブロックし、更新後の再取得もスキップします。楽観的UIの変更をローカルで保持してテストできます。AniListにはデータは送信されません。", + }, }, hololive: { live: "ライブ", diff --git a/src/lib/Locale/layout.ts b/src/lib/Locale/layout.ts index f317ab7f..697404f2 100644 --- a/src/lib/Locale/layout.ts +++ b/src/lib/Locale/layout.ts @@ -282,6 +282,10 @@ export interface Locale { hint2: LocaleValue; }; dummyLists: LocaleValue; + dryRunMutations: { + title: LocaleValue; + hint: LocaleValue; + }; }; hololive: { live: LocaleValue; diff --git a/src/lib/Media/Anime/cache.ts b/src/lib/Media/Anime/cache.ts index e988f255..e1a711ca 100644 --- a/src/lib/Media/Anime/cache.ts +++ b/src/lib/Media/Anime/cache.ts @@ -2,6 +2,7 @@ import { get } from "svelte/store"; import anime from "$stores/anime"; import { mediaListCollection, Type } from "../../Data/AniList/media"; import lastPruneTimes from "$stores/lastPruneTimes"; +import settings from "$stores/settings"; import type { AniListAuthorisation, UserIdentity, @@ -26,8 +27,19 @@ export const incrementMediaProgress = ( id: number, progress: number | undefined, user: AniListAuthorisation, - callback: () => void, + callback: (skipped?: boolean) => void, ) => { + if (get(settings).debugDryRunMutations) { + console.log( + `[dry-run] SaveMediaListEntry(mediaId: ${id}, progress: ${ + (progress || 0) + 1 + }) skipped`, + ); + callback(true); + + return; + } + fetch("https://graphql.anilist.co", { method: "POST", headers: { @@ -40,5 +52,5 @@ export const incrementMediaProgress = ( (progress || 0) + 1 }) { id } }`, }), - }).then(callback); + }).then(() => callback(false)); }; diff --git a/src/lib/Settings/Categories/Debug.svelte b/src/lib/Settings/Categories/Debug.svelte index ad388fee..6da4b6ae 100644 --- a/src/lib/Settings/Categories/Debug.svelte +++ b/src/lib/Settings/Categories/Debug.svelte @@ -15,6 +15,13 @@ import { invalidateListCaches } from "$lib/Media/invalidate"; setting="debugShowListTimings" text={$locale().debug.showListTimings} /> +<SettingCheckboxToggle + setting="debugDryRunMutations" + text={$locale().debug.dryRunMutations.title} +/> +<SettingHint lineBreak> + {$locale().debug.dryRunMutations.hint} +</SettingHint> <br /> <button onclick={invalidateListCaches}>{$locale().debug.clearCaches}</button> diff --git a/src/lib/Settings/Categories/SettingSync.svelte b/src/lib/Settings/Categories/SettingSync.svelte index 0cfe9261..867b2b47 100644 --- a/src/lib/Settings/Categories/SettingSync.svelte +++ b/src/lib/Settings/Categories/SettingSync.svelte @@ -29,6 +29,7 @@ import settingsSyncTimes from "$stores/settingsSyncTimes"; } else { fetch(root(`/api/configuration`), { method: 'PUT', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify($settings) }).then((response) => { if (response.ok) @@ -56,6 +57,7 @@ import settingsSyncTimes from "$stores/settingsSyncTimes"; fetch(root(`/api/configuration`), { method: 'PUT', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify($settings) }).then((response) => { if (response.ok) diff --git a/src/lib/Tooltip/LinkedTooltip.svelte b/src/lib/Tooltip/LinkedTooltip.svelte index 2a7961f5..4776322d 100644 --- a/src/lib/Tooltip/LinkedTooltip.svelte +++ b/src/lib/Tooltip/LinkedTooltip.svelte @@ -217,6 +217,7 @@ const hideTooltip = () => { const handleMouseEnter = (event: MouseEvent) => { if (disable) return; + if (window.matchMedia("(pointer: coarse)").matches) return; if (hideTimeout !== null) { clearTimeout(hideTimeout); diff --git a/src/lib/Tooltip/tooltip.ts b/src/lib/Tooltip/tooltip.ts index 3024bbff..830ff47f 100644 --- a/src/lib/Tooltip/tooltip.ts +++ b/src/lib/Tooltip/tooltip.ts @@ -108,6 +108,8 @@ const tooltip = (element: HTMLElement) => { }; const handleMouseEnter = (event: MouseEvent) => { + if (window.matchMedia("(pointer: coarse)").matches) return; + const title = element.getAttribute("title"); if (title) { diff --git a/src/stores/settings.ts b/src/stores/settings.ts index 24d9eef0..50dd4013 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -71,6 +71,7 @@ export interface Settings { displayDataSaver: boolean; debugDummyLists: boolean; debugShowListTimings: boolean; + debugDryRunMutations: boolean; displayScheduleFilterList: boolean; displayReverseSort: boolean; displayAnimeSort: "difference" | "start_date" | "end_date" | "time_remaining"; @@ -128,6 +129,7 @@ const defaultSettings: Settings = { // Debug debugDummyLists: false, debugShowListTimings: false, + debugDryRunMutations: false, // Calculation calculateChaptersRoundedDown: true, @@ -220,6 +222,7 @@ settings.subscribe((value) => { if (data?.configuration && !isEqualsJson(data.configuration, value)) fetch(root(`/api/configuration`), { method: "PUT", + headers: { "Content-Type": "application/json" }, body: JSON.stringify(value), }) .then((response) => { diff --git a/src/styles/input.css b/src/styles/input.css index a46c9b13..aaadd9a2 100644 --- a/src/styles/input.css +++ b/src/styles/input.css @@ -66,6 +66,7 @@ select { select { padding: 0.25em 0.5em; + max-width: 100%; } input[type="checkbox"] { |