From 73b73e85d7c42e5fc6f43996ad9b373d76be9a21 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Wed, 18 Mar 2026 23:05:32 -0500 Subject: [PATCH] chore: ported over basics --- package.json | 1 + pnpm-lock.yaml | 27 + src/App.svelte | 6 +- src/components/history/History.svelte | 284 +++++++- src/components/layout/Sidebar.svelte | 28 +- src/components/layout/SplashScreen.svelte | 377 +++++++++- src/components/pages/Library.svelte | 454 +++++++++++- src/components/pages/SeriesDetail.svelte | 802 +++++++++++++++++++++- src/components/shared/ContextMenu.svelte | 135 ++++ src/main.ts | 5 +- src/store/index.ts | 2 + 11 files changed, 2086 insertions(+), 35 deletions(-) create mode 100644 src/components/shared/ContextMenu.svelte diff --git a/package.json b/package.json index 34fe5e4..cc9a23a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@tauri-apps/api": "^2.0.0", "clsx": "^2.1.1", + "phosphor-svelte": "^3.1.0", "svelte-spa-router": "^4.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a5aa9e..d78feb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + phosphor-svelte: + specifier: ^3.1.0 + version: 3.1.0(svelte@5.54.0)(vite@5.4.21) svelte-spa-router: specifier: ^4.0.1 version: 4.0.2 @@ -505,6 +508,9 @@ packages: esrap@2.2.4: resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -601,6 +607,15 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + phosphor-svelte@3.1.0: + resolution: {integrity: sha512-nldtxx+XCgNREvrb7O5xgDsefytXpSkPTx8Rnu3f2qQCUZLDV1rLxYSd2Jcwckuo9lZB1qKMqGR17P4UDC0PrA==} + peerDependencies: + svelte: ^5.0.0 || ^5.0.0-next.96 + vite: '>=5' + peerDependenciesMeta: + vite: + optional: true + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1088,6 +1103,10 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@typescript-eslint/types': 8.57.1 + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -1169,6 +1188,14 @@ snapshots: path-is-absolute@1.0.1: {} + phosphor-svelte@3.1.0(svelte@5.54.0)(vite@5.4.21): + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + svelte: 5.54.0 + optionalDependencies: + vite: 5.4.21 + picocolors@1.1.1: {} picomatch@2.3.1: {} diff --git a/src/App.svelte b/src/App.svelte index 98ecca6..ef20fbe 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -14,7 +14,7 @@ import Settings from "./components/settings/Settings.svelte"; import TitleBar from "./components/layout/TitleBar.svelte"; import Toaster from "./components/layout/Toaster.svelte"; - import SplashScreen, { EXIT_MS } from "./components/layout/SplashScreen.svelte"; + import SplashScreen from "./components/layout/SplashScreen.svelte"; const MAX_ATTEMPTS = 30; @@ -128,7 +128,7 @@ {#if devSplash} setTimeout(() => devSplash = false, EXIT_MS + 20)} /> + onDismiss={() => setTimeout(() => devSplash = false, 340)} /> {:else if !appReady} {#if idle && !$activeChapter} setTimeout(() => idle = false, EXIT_MS + 20)} /> + onDismiss={() => setTimeout(() => idle = false, 340)} /> {/if} {#if !$activeChapter}{/if}
diff --git a/src/components/history/History.svelte b/src/components/history/History.svelte index d713392..cec9424 100644 --- a/src/components/history/History.svelte +++ b/src/components/history/History.svelte @@ -1 +1,283 @@ -
History.svelte
+ + +
+
+

History

+
+
+ + + {#if search} + + {/if} +
+ {#if $history.length > 0} + + {/if} +
+
+ + {#if stats} +
+ + {stats.uniqueChapters} + chapters read + + + + {stats.uniqueManga} + series + + + + {formatReadTime(stats.estimatedMinutes)} + est. read time + +
+ {/if} + + {#if $history.length === 0} +
+ +

No reading history yet

+

Chapters you read will appear here

+
+ {:else if sessions.length === 0} +
+ +

No results for "{search}"

+
+ {:else} +
+ {#each groups as { label, items }} +
+

{label}

+ {#each items as session} + + {/each} +
+ {/each} +
+ {/if} +
+ + diff --git a/src/components/layout/Sidebar.svelte b/src/components/layout/Sidebar.svelte index 92719de..1a2dd4c 100644 --- a/src/components/layout/Sidebar.svelte +++ b/src/components/layout/Sidebar.svelte @@ -1,14 +1,15 @@ -
SplashScreen stub
+ +
+ {#if showCards} + + {#if showFps} + + {/if} + {/if} + + {#if mode === "idle"} +
+
+
+ Moku +
+

press any key to continue

+
+ {:else} +
+ {#if !failed} + + + + + {/if} + Moku +
+

moku

+
+ {#if failed} +

+ Could not reach Suwayomi +

+

+ Make sure tachidesk-server is on your PATH +

+ + {:else} +

+ {ringFull ? "Ready" : `Initializing server${dots}`} +

+ {/if} +
+ {/if} +
+ + diff --git a/src/components/pages/Library.svelte b/src/components/pages/Library.svelte index b11304a..af4192b 100644 --- a/src/components/pages/Library.svelte +++ b/src/components/pages/Library.svelte @@ -1 +1,453 @@ -
Library.svelte
+ + +
{ + if ((e.target as HTMLElement).closest("button")) return; + e.preventDefault(); + emptyCtx = { x: e.clientX, y: e.clientY }; + }} +> + {#if $settings.libraryBranches ?? true} + + {/if} + + {#if error} +
+

Could not reach Suwayomi

+

Make sure the server is running, then retry.

+ +
+ {:else} +
+
+ Library +
+ {#each [["library","Saved"], ["downloaded","Downloaded"], ["all","All"]] as [f, label]} + + {/each} + {#each $settings.folders.filter((f) => f.showTab) as folder} + + {/each} +
+
+
+ + +
+
+ + {#if allTags.length > 0} +
+ {#if $libraryTagFilter.length > 0} + + {/if} + {#each allTags as tag} + + {/each} +
+ {/if} + + {#if loading} +
+ {#each Array(12) as _} +
+
+
+
+ {/each} +
+ {:else if filtered.length === 0} +
+ {$libraryFilter === "library" ? "No manga saved to library — browse sources to add some." + : $libraryFilter === "downloaded" ? "No downloaded manga." + : !isBuiltin($libraryFilter) ? "No manga in this folder yet. Right-click manga to assign them." + : "No manga found."} +
+ {:else} +
+ {#each filtered as m (m.id)} + + {/each} +
+ {/if} + {/if} +
+ +{#if ctx} + ctx = null} /> +{/if} +{#if emptyCtx} + emptyCtx = null} /> +{/if} + + diff --git a/src/components/pages/SeriesDetail.svelte b/src/components/pages/SeriesDetail.svelte index eb84ccc..45ce388 100644 --- a/src/components/pages/SeriesDetail.svelte +++ b/src/components/pages/SeriesDetail.svelte @@ -1 +1,801 @@ -
SeriesDetail.svelte
+ + +{#if $activeManga} +
+ + + + + +
+
+
+ + +
+
+ + + +
+ + {#if folderPickerOpen} +
+ {#if $settings.folders.length === 0 && !folderCreating} +

No folders yet

+ {/if} + {#each $settings.folders as folder} + {@const isIn = $activeManga ? folder.mangaIds.includes($activeManga.id) : false} + + {/each} +
+ {#if folderCreating} +
+ { if (e.key === "Enter") createFolder(); if (e.key === "Escape") { folderCreating = false; folderNewName = ""; } }} + use:focus /> + + +
+ {:else} + + {/if} +
+ {/if} +
+ + + {#if chapters.length > 1} +
+ {#if !jumpOpen} + + {:else} +
+ { + if (e.key === "Escape") { jumpOpen = false; return; } + if (e.key === "Enter") { + const num = parseFloat(jumpInput); + if (!isNaN(num)) { + const target = sortedChapters.find((c) => c.chapterNumber === num) + ?? sortedChapters.reduce((best, c) => Math.abs(c.chapterNumber - num) < Math.abs(best.chapterNumber - num) ? c : best, sortedChapters[0]); + if (target) openReader(target, sortedChapters); + } + jumpOpen = false; + } + }} + /> + +
+ {/if} +
+ {/if} + + + {#if chapters.length > 0} +
+ + {#if dlOpen} +
+ {#if continueChapter} + {@const contIdx = sortedChapters.indexOf(continueChapter.chapter)} + {#if contIdx >= 0} + +
+ {#each [5, 10, 25] as n} + {@const avail = sortedChapters.slice(contIdx, contIdx + n).filter((c) => !c.isDownloaded).length} + + {/each} +
+
+ {/if} + {/if} + {#if !showRange} + + {:else} +
+ + e.key === "Enter" && enqueueRange()} use:focus /> + + e.key === "Enter" && enqueueRange()} /> + +
+ {/if} +
+ + + {#if downloadedCount > 0} +
+ + {/if} +
+ {/if} +
+ {/if} + + {#if totalPages > 1} + + {/if} +
+
+ +
+ {#if loadingChapters && chapters.length === 0} + {#if viewMode === "grid"} + {#each Array(24) as _}
{/each} + {:else} + {#each Array(8) as _}
{/each} + {/if} + {:else if viewMode === "grid"} + {#each sortedChapters as ch, i} + {@const inProgress = !ch.isRead && (ch.lastPageRead ?? 0) > 0} + + {/each} + {:else} + {#each pageChapters as ch} + {@const idxInSorted = sortedChapters.indexOf(ch)} +
openReader(ch, sortedChapters)} + on:keydown={(e) => e.key === "Enter" && openReader(ch, sortedChapters)} + on:contextmenu={(e) => { e.preventDefault(); ctx = { x: e.clientX, y: e.clientY, chapter: ch, idx: idxInSorted }; }}> +
+ {ch.name} +
+ {#if ch.scanlator}{ch.scanlator}{/if} + {#if ch.uploadDate}{formatDate(ch.uploadDate)}{/if} + {#if ch.lastPageRead && ch.lastPageRead > 0 && !ch.isRead}p.{ch.lastPageRead}{/if} +
+
+
+ {#if ch.isRead}{/if} + {#if ch.isDownloaded} + + {:else if enqueueing.has(ch.id)} + + {:else} + + {/if} +
+
+ {/each} + {/if} +
+ + {#if totalPages > 1} +
+ + {chapterPage} / {totalPages} + +
+ {/if} +
+
+ +{#if ctx} + ctx = null} /> +{/if} +{/if} + + + + diff --git a/src/components/shared/ContextMenu.svelte b/src/components/shared/ContextMenu.svelte new file mode 100644 index 0000000..e256b9d --- /dev/null +++ b/src/components/shared/ContextMenu.svelte @@ -0,0 +1,135 @@ + + + + + diff --git a/src/main.ts b/src/main.ts index 1cd4ee6..5826b29 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ +import { mount } from "svelte"; import App from "./App.svelte"; import "./styles/global.css"; -const app = new App({ target: document.getElementById("app")! }); - -export default app; +mount(App, { target: document.getElementById("app")! }); diff --git a/src/store/index.ts b/src/store/index.ts index 763dab3..7ec32e0 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -35,6 +35,7 @@ export interface Settings { preferredExtensionLang: string; keybinds: Keybinds; idleTimeoutMin?: number; splashCards?: boolean; storageLimitGb: number | null; folders: Folder[]; markReadOnNext: boolean; readerDebounceMs: number; theme: Theme; + libraryBranches: boolean; } export const DEFAULT_SETTINGS: Settings = { @@ -48,6 +49,7 @@ export const DEFAULT_SETTINGS: Settings = { autoStartServer: true, preferredExtensionLang: "en", keybinds: DEFAULT_KEYBINDS, idleTimeoutMin: 5, splashCards: true, storageLimitGb: null, folders: [], markReadOnNext: true, readerDebounceMs: 120, theme: "dark", + libraryBranches: true, }; function loadPersisted() {