From f5a66ab5d1ab8b0763bfeecc74bb121b304f8179 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Mon, 20 Apr 2026 20:59:42 -0500 Subject: [PATCH] Fix: Local Source & QOL Animations --- src/api/queries/extensions.ts | 10 ++- .../discover/components/Search.svelte | 51 +++++++++-- .../discover/components/SourceTab.svelte | 20 ++++- .../components/ExtensionCard.svelte | 2 +- .../components/ExtensionFilters.svelte | 20 +++-- .../extensions/components/Extensions.svelte | 88 +++++++++++++++---- .../library/components/LibraryToolbar.svelte | 5 +- src/features/settings/components/Settings.css | 2 + .../settings/sections/StorageSettings.svelte | 34 ++++--- src/shared/chrome/AuthGate.svelte | 7 +- src/shared/chrome/Layout.svelte | 2 - src/shared/chrome/RecentActivity.svelte | 5 +- src/shared/chrome/SplashScreen.svelte | 5 +- 13 files changed, 190 insertions(+), 61 deletions(-) diff --git a/src/api/queries/extensions.ts b/src/api/queries/extensions.ts index 2d3820f..8c41453 100644 --- a/src/api/queries/extensions.ts +++ b/src/api/queries/extensions.ts @@ -1,3 +1,11 @@ +export const GET_LOCAL_MANGA = ` + query GetLocalManga { + mangas(condition: { sourceId: "0" }) { + nodes { id title thumbnailUrl inLibrary } + } + } +`; + export const GET_EXTENSIONS = ` query GetExtensions { extensions { @@ -32,4 +40,4 @@ export const GET_SERVER_SECURITY = ` flareSolverrSessionName flareSolverrSessionTtl flareSolverrAsResponseFallback } } -`; +`; \ No newline at end of file diff --git a/src/features/discover/components/Search.svelte b/src/features/discover/components/Search.svelte index a3d972f..5d5066e 100644 --- a/src/features/discover/components/Search.svelte +++ b/src/features/discover/components/Search.svelte @@ -19,6 +19,29 @@ import TagTab from "./TagTab.svelte"; import SourceTab from "./SourceTab.svelte"; + const anims = $derived(store.settings.qolAnimations ?? true); + + const TABS = ["keyword", "tag", "source"] as const; + + let tabsEl = $state(undefined); + let tabIndicator = $state({ left: 0, width: 0 }); + + function updateIndicator() { + if (!tabsEl) return; + const active = tabsEl.querySelector(".tab.tabActive"); + if (!active) return; + const containerLeft = tabsEl.getBoundingClientRect().left; + tabIndicator = { + left: active.getBoundingClientRect().left - containerLeft, + width: active.offsetWidth, + }; + } + + $effect(() => { + tab; // reactive on tab change + if (anims) requestAnimationFrame(updateIndicator); + }); + const SEARCH_PAGES = 3; const SEARCH_LIMIT = 200; const SEARCH_BATCH = 20; @@ -40,6 +63,7 @@ }); let allSources: Source[] = $state([]); + let localSource: Source | null = $state(null); let loadingSources = $state(false); const preferredLang = store.settings?.preferredExtensionLang ?? "en"; @@ -49,7 +73,9 @@ loadingSources = true; gql<{ sources: { nodes: Source[] } }>(GET_SOURCES) .then((d) => { - allSources = d.sources.nodes.filter((src: Source) => src.id !== "0"); + const nodes = d.sources.nodes; + localSource = nodes.find((src: Source) => src.id === "0") ?? null; + allSources = nodes.filter((src: Source) => src.id !== "0"); startSourceCacheBuild(); popularStart(allSources); }) @@ -230,10 +256,14 @@ }); -
+
-

Search

-
+ Search + +
+ {#if anims && tabIndicator.width > 0} + + {/if}
{:else}
+ {#if localSource} + +
+ {/if} {#each src_visibleSources as src (src.id)} @@ -186,7 +220,7 @@ {/if} {#if panel === "repos"} -
+
Extension Repositories @@ -227,10 +261,18 @@ {#if loading}
- {:else if groups.length === 0} -
No extensions found.
{:else}
+ {#if showLocal} +
+
+
+ Local Source + Built-in · {localMangaCount} {localMangaCount === 1 ? "manga" : "manga"} +
+ Built-in +
+ {/if} {#each groups as { base, primary, variants }} {/each} + {#if !showLocal && groups.length === 0} +
No extensions found.
+ {/if}
{/if}
+ .local-row { display: flex; align-items: center; gap: var(--sp-3); padding: 8px var(--sp-3); border-radius: var(--radius-md); border: 1px solid transparent; transition: background var(--t-fast), border-color var(--t-fast); margin-bottom: 1px; } + .local-row:hover { background: var(--bg-raised); border-color: var(--border-dim); } + .local-icon { width: 32px; height: 32px; border-radius: var(--radius-md); background: var(--accent-muted); border: 1px solid var(--accent-dim); display: flex; align-items: center; justify-content: center; color: var(--accent-fg); flex-shrink: 0; } + .info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; } + .name { font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .meta { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); } + .local-badge { font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 3px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim); color: var(--text-faint); flex-shrink: 0; } + \ No newline at end of file diff --git a/src/features/library/components/LibraryToolbar.svelte b/src/features/library/components/LibraryToolbar.svelte index 11cfc19..532ff17 100644 --- a/src/features/library/components/LibraryToolbar.svelte +++ b/src/features/library/components/LibraryToolbar.svelte @@ -152,7 +152,7 @@ {#if sortPanelOpen} -
@@ -465,6 +477,9 @@ bind:value={localSourcePathInput} placeholder="Optional" spellcheck="false" onkeydown={(e) => e.key === "Enter" && savePaths()} oninput={() => { pathsFieldError = { ...pathsFieldError, loc: undefined }; }} /> + {#if !isExternalServer} + + {/if} {#if pathsFieldError.loc && !isExternalServer} + {#if !isExternalServer} + + {/if} +
-
-
- -
{/if}
diff --git a/src/shared/chrome/AuthGate.svelte b/src/shared/chrome/AuthGate.svelte index 2279239..f615b7d 100644 --- a/src/shared/chrome/AuthGate.svelte +++ b/src/shared/chrome/AuthGate.svelte @@ -16,7 +16,7 @@ {#if boot.unsupportedMode}
-
+

moku

{ @@ -35,7 +35,7 @@
{:else if boot.loginRequired}
-
+

moku

Basic Auth @@ -62,8 +62,7 @@ diff --git a/src/shared/chrome/SplashScreen.svelte b/src/shared/chrome/SplashScreen.svelte index 7931e1b..5371e05 100644 --- a/src/shared/chrome/SplashScreen.svelte +++ b/src/shared/chrome/SplashScreen.svelte @@ -416,7 +416,7 @@
{#if failed || notConfigured} -
+

{failed ? "Could not reach server" : "Server not configured"}

@@ -455,14 +455,13 @@ @keyframes spOut { from { opacity:1; transform:scale(1) } to { opacity:0; transform:scale(0.96) } } @keyframes logoBreathe { 0%,100% { transform:scale(1); filter:drop-shadow(0 0 0px rgba(255,255,255,0)) } 50% { transform:scale(1.04); filter:drop-shadow(0 0 18px rgba(255,255,255,0.12)) } } @keyframes hintFade { 0%,100% { opacity:0.35 } 50% { opacity:0.7 } } - @keyframes errIn { from { opacity:0; transform:translateY(4px) } to { opacity:1; transform:translateY(0) } } @keyframes pinShake { 0%,100% { transform:translateX(0) } 20%,60% { transform:translateX(-6px) } 40%,80% { transform:translateX(6px) } } .logo-glow { position: absolute; inset: -20px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 70%); animation: logoBreathe 4s ease-in-out infinite; } .logo-breathe { animation: logoBreathe 4s ease-in-out infinite; } .hint { font-family: var(--font-ui); font-size: 10px; color: var(--text-faint); letter-spacing: 0.22em; text-transform: uppercase; margin: 0; user-select: none; animation: hintFade 3.5s ease-in-out infinite; } - .error-box { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 20px; border-radius: var(--radius-lg); background: var(--bg-surface); border: 1px solid var(--border-base); min-width: 200px; text-align: center; animation: errIn 0.25s cubic-bezier(0,0,0.2,1) both; } + .error-box { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 20px; border-radius: var(--radius-lg); background: var(--bg-surface); border: 1px solid var(--border-base); min-width: 200px; text-align: center; } .error-label { font-family: var(--font-ui); font-size: 11px; font-weight: 500; color: var(--text-muted); letter-spacing: 0.06em; margin: 0; } .error-actions { display: flex; gap: 6px; } .err-btn { padding: 5px 14px; border-radius: var(--radius-md); border: 1px solid var(--border-base); background: transparent; color: var(--text-muted); cursor: pointer; font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.04em; transition: border-color 0.15s, color 0.15s; }