From f8f080eff3bc40397024c87edbd481e36010638b Mon Sep 17 00:00:00 2001 From: Zerebos Date: Sat, 23 May 2026 02:48:31 -0400 Subject: [PATCH] Finish phase 3 --- src/lib/core/ui/idle.ts | 70 ++-- src/lib/core/ui/zoom.ts | 12 +- src/lib/state/app.svelte.ts | 20 +- src/routes/library/+page.svelte | 473 ++++++++++++++++++++++- src/routes/series/[mangaId]/+page.svelte | 33 ++ 5 files changed, 556 insertions(+), 52 deletions(-) create mode 100644 src/routes/series/[mangaId]/+page.svelte diff --git a/src/lib/core/ui/idle.ts b/src/lib/core/ui/idle.ts index 394442a..ee9569c 100644 --- a/src/lib/core/ui/idle.ts +++ b/src/lib/core/ui/idle.ts @@ -1,49 +1,49 @@ const IDLE_EVENTS = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'wheel'] as const; export function mountIdleDetection( - getTimeoutMinutes: () => number | undefined, - onIdle: () => void, - onActive: () => void, + getTimeoutMinutes: () => number | undefined, + onIdle: () => void, + onActive: () => void, ): () => void { - let timer: ReturnType | null = null; - let idle = false; + let timer: ReturnType | null = null; + let idle = false; - const markActive = () => { - if (!idle) return; - idle = false; - onActive(); - }; + const markActive = () => { + if (!idle) return; + idle = false; + onActive(); + }; - const resetTimer = () => { - if (timer) clearTimeout(timer); + const resetTimer = () => { + if (timer) clearTimeout(timer); - const timeoutMinutes = getTimeoutMinutes() ?? 5; - const timeoutMs = Math.max(0, timeoutMinutes) * 60 * 1000; + const timeoutMinutes = getTimeoutMinutes() ?? 5; + const timeoutMs = Math.max(0, timeoutMinutes) * 60 * 1000; - if (timeoutMs === 0) { - markActive(); - return; - } + if (timeoutMs === 0) { + markActive(); + return; + } - markActive(); + markActive(); - timer = setTimeout(() => { - if (idle) return; - idle = true; - onIdle(); - }, timeoutMs); - }; + timer = setTimeout(() => { + if (idle) return; + idle = true; + onIdle(); + }, timeoutMs); + }; - IDLE_EVENTS.forEach((eventName) => { - window.addEventListener(eventName, resetTimer, { passive: true }); - }); - - resetTimer(); - - return () => { - if (timer) clearTimeout(timer); IDLE_EVENTS.forEach((eventName) => { - window.removeEventListener(eventName, resetTimer); + window.addEventListener(eventName, resetTimer, {passive: true}); }); - }; + + resetTimer(); + + return () => { + if (timer) clearTimeout(timer); + IDLE_EVENTS.forEach((eventName) => { + window.removeEventListener(eventName, resetTimer); + }); + }; } \ No newline at end of file diff --git a/src/lib/core/ui/zoom.ts b/src/lib/core/ui/zoom.ts index dbc8860..9a3edac 100644 --- a/src/lib/core/ui/zoom.ts +++ b/src/lib/core/ui/zoom.ts @@ -16,9 +16,9 @@ export function applyZoom(uiZoom: number) { export function zoomDelta(e: KeyboardEvent, current: number): number | null { if (!e.ctrlKey) return null; - if (e.key === "=" || e.key === "+") { e.preventDefault(); return Math.min(2.0, Math.round((current + 0.1) * 10) / 10); } - if (e.key === "-") { e.preventDefault(); return Math.max(0.5, Math.round((current - 0.1) * 10) / 10); } - if (e.key === "0") { e.preventDefault(); return 1.0; } + if (e.key === "=" || e.key === "+") {e.preventDefault(); return Math.min(2.0, Math.round((current + 0.1) * 10) / 10);} + if (e.key === "-") {e.preventDefault(); return Math.max(0.5, Math.round((current - 0.1) * 10) / 10);} + if (e.key === "0") {e.preventDefault(); return 1.0;} return null; } @@ -43,19 +43,19 @@ export function clampZoom(z: number, min: number, max: number): number { export function captureZoomAnchor( containerEl: HTMLElement | null, style: string, - out: { el: HTMLElement | null; offset: number }, + out: {el: HTMLElement | null; offset: number;}, ) { if (!containerEl || style !== "longstrip") return; const containerTop = containerEl.getBoundingClientRect().top; for (const img of containerEl.querySelectorAll("img[data-local-page]")) { const rect = img.getBoundingClientRect(); - if (rect.bottom > containerTop) { out.el = img; out.offset = rect.top - containerTop; return; } + if (rect.bottom > containerTop) {out.el = img; out.offset = rect.top - containerTop; return;} } } export function restoreZoomAnchor( containerEl: HTMLElement | null, - out: { el: HTMLElement | null; offset: number }, + out: {el: HTMLElement | null; offset: number;}, ) { if (!out.el || !containerEl) return; const el = out.el; diff --git a/src/lib/state/app.svelte.ts b/src/lib/state/app.svelte.ts index 0af799b..3805ec9 100644 --- a/src/lib/state/app.svelte.ts +++ b/src/lib/state/app.svelte.ts @@ -1,12 +1,12 @@ -export type AppStatus = 'booting' | 'not-configured' | 'auth' | 'ready' | 'error' +export type AppStatus = 'booting' | 'not-configured' | 'auth' | 'ready' | 'error'; export const appState = $state({ - status: 'booting' as AppStatus, - error: null as string | null, - serverUrl: '', - authenticated: false, - authMode: 'NONE' as 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN', - platform: 'web' as 'web' | 'tauri' | 'capacitor', - version: '', - idle: false, -}) \ No newline at end of file + status: 'booting' as AppStatus, + error: null as string | null, + serverUrl: '', + authenticated: false, + authMode: 'NONE' as 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN', + platform: 'web' as 'web' | 'tauri' | 'capacitor', + version: '', + idle: false, +}); \ No newline at end of file diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte index f245826..21c5d88 100644 --- a/src/routes/library/+page.svelte +++ b/src/routes/library/+page.svelte @@ -1 +1,472 @@ -

library

\ No newline at end of file + + +
+
+
+

Library

+

{filteredItems.length} manga

+
+ +
+ + + + + + + + +
+ + +
+
+
+ + {#if filtersOpen} + + {/if} + + {#if libraryState.loading} +
Loading library...
+ {:else if libraryState.error} +
+

Failed to load library.

+ {libraryState.error} + +
+ {:else if !hasResults} +
No manga match the current filters.
+ {:else} +
+ {#each filteredItems as manga (manga.id)} + + {/each} +
+ {/if} +
+ + \ No newline at end of file diff --git a/src/routes/series/[mangaId]/+page.svelte b/src/routes/series/[mangaId]/+page.svelte new file mode 100644 index 0000000..dfb456a --- /dev/null +++ b/src/routes/series/[mangaId]/+page.svelte @@ -0,0 +1,33 @@ + + +
+

Series

+

Selected manga id: {mangaId}

+

Series detail UI will be implemented in Phase 4.

+
+ +