From 9dad1fb329235235976994626b2dac634f828b98 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Fri, 12 Jun 2026 17:27:08 -0500 Subject: [PATCH] Feat: Recent Tab (Unread State) + Bug Fixes --- src/hooks.client.ts | 26 +- .../components/browse/GenreDrillPage.svelte | 2 +- src/lib/components/chrome/SplashScreen.svelte | 74 +++-- src/lib/components/chrome/TitleBar.svelte | 43 ++- src/lib/components/chrome/splashCanvas.ts | 171 ---------- .../components/downloads/DownloadItem.svelte | 6 +- .../components/downloads/DownloadQueue.svelte | 14 +- src/lib/components/downloads/Downloads.svelte | 8 +- .../extensions/ExtensionLibrary.svelte | 4 +- .../extensions/lib/extensionLibrary.ts | 2 +- .../panels/SourceMigrateModal.svelte | 28 +- src/lib/components/home/Home.svelte | 5 +- src/lib/components/reader/Reader.svelte | 34 +- .../components/reader/lib/chapterActions.ts | 6 +- .../reader/viewer/LongstripViewer.svelte | 4 +- src/lib/components/recent/Recent.svelte | 45 ++- .../components/recent/RecentToolbar.svelte | 23 +- src/lib/components/recent/UpdatesTab.svelte | 42 ++- .../components/recent/lib/recentUpdates.ts | 19 +- src/lib/components/series/ChapterList.svelte | 72 +++-- .../components/series/SeriesActions.svelte | 30 +- src/lib/components/series/SeriesDetail.svelte | 73 ++--- src/lib/components/series/SeriesHeader.svelte | 2 +- src/lib/components/series/lib/mangaPrefs.ts | 19 -- .../series/panels/AutomationPanel.svelte | 2 +- .../series/panels/CoverPickerPanel.svelte | 2 +- src/lib/components/settings/lib/bugReport.ts | 6 +- .../settings/sections/FoldersSettings.svelte | 24 +- src/lib/core/persistence/credentialVault.ts | 13 +- src/lib/request-manager/manga.ts | 30 +- src/lib/server-adapters/suwayomi/chapters.ts | 4 +- src/lib/server-adapters/suwayomi/index.ts | 27 +- src/lib/server-adapters/suwayomi/manga.ts | 6 +- src/lib/server-adapters/types.ts | 301 ++++++++++++------ src/lib/state/history.svelte.ts | 2 +- src/lib/state/library.svelte.ts | 2 +- src/lib/state/reader.svelte.ts | 14 +- src/lib/state/series.svelte.ts | 141 ++++---- src/lib/types/index.ts | 3 +- src/lib/types/manga.ts | 7 +- 40 files changed, 668 insertions(+), 668 deletions(-) delete mode 100644 src/lib/components/chrome/splashCanvas.ts delete mode 100644 src/lib/components/series/lib/mangaPrefs.ts diff --git a/src/hooks.client.ts b/src/hooks.client.ts index b80e60f..d29d5e9 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,12 +1,13 @@ -import { detectAdapter } from '$lib/platform-adapters' -import { initPlatformService } from '$lib/platform-service' -import { initRequestManager } from '$lib/request-manager' -import { appState } from '$lib/state/app.svelte' -import { configureAuth, probeServer } from '$lib/core/auth' -import { loadSettings, loadLibrary, loadUpdates } from '$lib/core/persistence/persist' -import { loadSettingsIntoState } from '$lib/state/settings.svelte' -import { historyState } from '$lib/state/history.svelte' -import { readerState } from '$lib/state/reader.svelte' +import { detectAdapter } from '$lib/platform-adapters' +import { initPlatformService } from '$lib/platform-service' +import { initRequestManager } from '$lib/request-manager' +import { appState } from '$lib/state/app.svelte' +import { configureAuth, probeServer } from '$lib/core/auth' +import { loadSettings, loadLibrary } from '$lib/core/persistence/persist' +import { loadSettingsIntoState, settingsState } from '$lib/state/settings.svelte' +import { historyState } from '$lib/state/history.svelte' +import { readerState } from '$lib/state/reader.svelte' +import { seriesState } from '$lib/state/series.svelte' const KEY_URL = 'moku_server_url' const KEY_AUTH = 'moku_auth_config' @@ -34,12 +35,11 @@ async function boot() { const [settingsData, libraryData] = await Promise.all([ loadSettings(), loadLibrary(), - loadUpdates(), ]) await loadSettingsIntoState(settingsData.settings) - readerState.bookmarks = libraryData.bookmarks + seriesState.bookmarks = libraryData.bookmarks readerState.markers = libraryData.markers historyState.load(libraryData.sessions, libraryData.dailyReadCounts) @@ -49,8 +49,6 @@ async function boot() { appState.serverUrl = savedUrl appState.authMode = savedAuth.mode - appState.authUser = savedAuth.user ?? '' - appState.authPass = savedAuth.pass ?? '' configureAuth(savedUrl, savedAuth.mode, savedAuth.user, savedAuth.pass) @@ -63,7 +61,7 @@ async function boot() { }) const isTauri = platformAdapter.platform === 'tauri' - const autoStartServer = settingsData.settings.autoStartServer ?? false + const autoStartServer = settingsState.settings.autoStartServer if (isTauri && autoStartServer) { appState.status = 'booting' diff --git a/src/lib/components/browse/GenreDrillPage.svelte b/src/lib/components/browse/GenreDrillPage.svelte index 7e6c9b0..dbe6032 100644 --- a/src/lib/components/browse/GenreDrillPage.svelte +++ b/src/lib/components/browse/GenreDrillPage.svelte @@ -170,7 +170,7 @@ label: "New folder & add", icon: FolderSimplePlusIcon, onClick: async () => { - const name = prompt("FolderIcon name:"); + const name = prompt("Folder name:"); if (!name?.trim()) return; const cat = await getAdapter().createCategory(name.trim()).catch(console.error); if (cat) { diff --git a/src/lib/components/chrome/SplashScreen.svelte b/src/lib/components/chrome/SplashScreen.svelte index 05e5415..38a435e 100644 --- a/src/lib/components/chrome/SplashScreen.svelte +++ b/src/lib/components/chrome/SplashScreen.svelte @@ -43,20 +43,20 @@ const isDev = import.meta.env.DEV interface Props { - mode?: 'loading' | 'idle' | 'locked' - ringFull?: boolean - failed?: boolean - notConfigured?: boolean - showCards?: boolean - showFps?: boolean + mode?: 'loading' | 'idle' | 'locked' + ringFull?: boolean + failed?: boolean + notConfigured?: boolean + showCards?: boolean + showFps?: boolean showDevOverlay?: boolean - pinLen?: number - pinCorrect?: string - onReady?: () => void - onUnlock?: () => void - onRetry?: () => void - onBypass?: () => void - onDismiss?: () => void + pinLen?: number + pinCorrect?: string + onReady?: () => void + onUnlock?: () => void + onRetry?: () => void + onBypass?: () => void + onDismiss?: () => void } let { @@ -97,7 +97,7 @@ setTimeout(() => cb?.(), EXIT_MS) } - let animFrame: number + let animFrame = 0 let animStart: number | null = null let animPhase = 1 @@ -117,19 +117,18 @@ } $effect(() => { - if (mode === 'loading' && !failed && !notConfigured && !ringFull) { - if (!isTauri) return - animStart = null - animPhase = 1 - animFrame = requestAnimationFrame(animateRing) - return () => cancelAnimationFrame(animFrame) - } + if (mode !== 'loading' || failed || notConfigured || ringFull || !isTauri) return + animStart = null + animPhase = 1 + animFrame = requestAnimationFrame(animateRing) + return () => cancelAnimationFrame(animFrame) }) $effect(() => { if (!ringFull || mode === 'locked') { exitLock = false; exiting = false; return } cancelAnimationFrame(animFrame) - ringProg = 1 + animFrame = 0 + ringProg = 1 setTimeout(() => triggerExit(onReady), 650) }) @@ -179,6 +178,7 @@ window.removeEventListener('touchstart', handler) } } + return () => clearInterval(iv) }) @@ -212,7 +212,7 @@ const speed = cfg.speedMin + hash(seed + 5) * (cfg.speedMax - cfg.speedMin) const travel = vh + h + BUF cards.push({ - cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2), + cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2), w, h, lines: 1 + Math.floor(hash(seed + 7) * 3), alpha: cfg.alpha, @@ -307,11 +307,12 @@ ctx.drawImage(vignette, 0, 0, cw, ch) } - let fps = 0, fpsFrames = 0, fpsLast = 0 + let fpsFrames = 0, fpsLast = -1 function tickFps(now: number) { + if (fpsLast < 0) { fpsLast = now; return } fpsFrames++ if (now - fpsLast >= 500) { - fps = Math.round(fpsFrames / ((now - fpsLast) / 1000)) + const fps = Math.round(fpsFrames / ((now - fpsLast) / 1000)) fpsFrames = 0 fpsLast = now if (fpsEl) fpsEl.textContent = `${fps} fps` @@ -370,7 +371,7 @@ function cleanup() { if (live) { live.stamps.forEach(c => { c.width = 0; c.height = 0 }) - live.vignette.width = 0 + live.vignette.width = 0 live.vignette.height = 0 live = null } @@ -444,14 +445,17 @@ document.addEventListener('visibilitychange', onVis) raf = requestAnimationFrame(frame) - return () => { - cancelAnimationFrame(raf) - cleanup() - extraCleanup?.() - document.removeEventListener('visibilitychange', onVis) - if (isDev && mode === 'idle') { - splashDevUnregister(el) - devLiveCount = splashDevLiveCount() + + return { + destroy() { + cancelAnimationFrame(raf) + cleanup() + extraCleanup?.() + document.removeEventListener('visibilitychange', onVis) + if (isDev && mode === 'idle') { + splashDevUnregister(el) + devLiveCount = splashDevLiveCount() + } } } } @@ -548,7 +552,7 @@ .logo-wrap { position:relative; width:72px; height:72px; display:flex; align-items:center; justify-content:center; } - .pin-card { z-index:1; width:min(280px,calc(100vw - 48px)); background:var(--bg-surface); border:1px solid var(--border-base); border-radius:var(--radius-xl); padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; align-items:center; gap:var(--sp-3); box-shadow:0 32px 80px rgba(0,0,0,0.75); animation:cardIn 0.38s cubic-bezier(0.22,1,0.36,1) 0.06s both; } + .pin-card { z-index:1; width:min(280px,calc(100vw - 48px)); background:var(--bg-surface); border:1px solid var(--border-base); border-radius:var(--radius-xl); padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; align-items:center; gap:var(--sp-3); box-shadow:0 32px 80px rgba(0,0,0,0.75); animation:cardIn 0.38s cubic-bezier(0.22,1,0.36,1) 0.06s both; } .pin-card--leaving { animation:cardOut 0.28s cubic-bezier(0.4,0,1,1) both; } .pin-label { font-family:var(--font-ui); font-size:var(--text-xs); color:var(--text-faint); letter-spacing:var(--tracking-wider); text-transform:uppercase; margin:0; } diff --git a/src/lib/components/chrome/TitleBar.svelte b/src/lib/components/chrome/TitleBar.svelte index b2a579f..c51f587 100644 --- a/src/lib/components/chrome/TitleBar.svelte +++ b/src/lib/components/chrome/TitleBar.svelte @@ -16,15 +16,22 @@ let closeDialogOpen = $state(false) let closeRemember = $state(false) - onMount(async () => { - isFullscreen = await win.isFullscreen() - const unlistenResize = await win.onResized(async () => { + onMount(() => { + let unlistenResize: (() => void) | undefined + let unlistenClose: (() => void) | undefined + + win.isFullscreen().then(v => { isFullscreen = v }) + + win.onResized(async () => { isFullscreen = await win.isFullscreen() - }) - const unlistenClose = await win.listen('tauri://close-requested', handleCloseRequested) + }).then(u => { unlistenResize = u }) + + win.listen('tauri://close-requested', handleCloseRequested) + .then(u => { unlistenClose = u }) + return () => { - unlistenResize() - unlistenClose() + unlistenResize?.() + unlistenClose?.() } }) @@ -56,6 +63,10 @@ if (choice === 'tray') await doHide() else await doQuit() } + + function onBackdropKey(e: KeyboardEvent) { + if (e.key === 'Escape') { closeDialogOpen = false; closeRemember = false } + } {#if !isFullscreen} @@ -99,8 +110,21 @@ {/if} {#if closeDialogOpen} -