diff --git a/src/features/extensions/components/ExtensionCard.svelte b/src/features/extensions/components/ExtensionCard.svelte index b83cff8..fa60ee3 100644 --- a/src/features/extensions/components/ExtensionCard.svelte +++ b/src/features/extensions/components/ExtensionCard.svelte @@ -9,11 +9,12 @@ variants: Extension[]; expanded: boolean; working: Set; + anims: boolean; onToggle: (base: string) => void; onMutate: (pkgName: string, op: "install" | "update" | "uninstall") => void; } - let { base, primary, variants, expanded, working, onToggle, onMutate }: Props = $props(); + let { base, primary, variants, expanded, working, anims, onToggle, onMutate }: Props = $props(); const hasVariants = $derived(variants.length > 0); @@ -56,7 +57,7 @@ {#if expanded && hasVariants} -
+
{#each variants as v}
{v.lang.toUpperCase()} @@ -98,7 +99,9 @@ .expand-btn { display: flex; align-items: center; gap: 3px; padding: 4px 6px; border-radius: var(--radius-sm); color: var(--text-faint); flex-shrink: 0; transition: color var(--t-base), background var(--t-base); } .expand-btn:hover { color: var(--text-muted); background: var(--bg-overlay); } .expand-count { font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); } - .variants { display: flex; flex-direction: column; gap: 1px; margin: 1px 0 2px calc(32px + var(--sp-3) + var(--sp-3)); padding-left: var(--sp-3); border-left: 1px solid var(--border-dim); animation: fadeIn 0.1s ease both; } + .variants { display: flex; flex-direction: column; gap: 1px; margin: 1px 0 2px calc(32px + var(--sp-3) + var(--sp-3)); padding-left: var(--sp-3); border-left: 1px solid var(--border-dim); } + .variants-anim { animation: slideDown 0.18s cubic-bezier(0.16,1,0.3,1) both; } + @keyframes slideDown { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } } .variant-row { display: flex; align-items: center; gap: var(--sp-2); padding: 5px var(--sp-2); border-radius: var(--radius-md); transition: background var(--t-fast); } .variant-row:hover { background: var(--bg-raised); } .variant-name { flex: 1; font-size: var(--text-sm); color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } diff --git a/src/features/extensions/components/Extensions.svelte b/src/features/extensions/components/Extensions.svelte index da63845..6e3bb1b 100644 --- a/src/features/extensions/components/Extensions.svelte +++ b/src/features/extensions/components/Extensions.svelte @@ -61,7 +61,10 @@ const d = await gql<{ fetchExtensions: { extensions: Extension[] } }>(FETCH_EXTENSIONS) .catch(console.error) .finally(() => refreshing = false); - if (d) extensions = d.fetchExtensions.extensions; + if (d) { + extensions = d.fetchExtensions.extensions; + addToast({ kind: "success", title: "Extensions refreshed", body: "Extension list is up to date" }); + } } async function loadRepos() { @@ -73,11 +76,15 @@ finally { reposLoading = false; } } - async function saveRepos(updated: string[]) { + async function saveRepos(updated: string[], intent: "add" | "remove") { savingRepos = true; try { const d = await gql<{ setSettings: { settings: { extensionRepos: string[] } } }>(SET_EXTENSION_REPOS, { repos: updated }); repos = d.setSettings.settings.extensionRepos; + addToast(intent === "add" + ? { kind: "success", title: "Repo added", body: updated[updated.length - 1] } + : { kind: "info", title: "Repo removed", body: repos.find(r => !updated.includes(r)) ?? "" } + ); } catch (e: any) { repoError = e instanceof Error ? e.message : "Failed to save"; } finally { savingRepos = false; } @@ -89,10 +96,10 @@ if (err) { repoError = err; return; } if (repos.includes(url)) { repoError = "Repo already added"; return; } repoError = null; newRepoUrl = ""; - saveRepos([...repos, url]); + saveRepos([...repos, url], "add"); } - function removeRepo(url: string) { saveRepos(repos.filter((r) => r !== url)); } + function removeRepo(url: string) { saveRepos(repos.filter((r) => r !== url), "remove"); } async function mutate(pkgName: string, op: "install" | "update" | "uninstall") { working = new Set(working).add(pkgName); @@ -176,7 +183,23 @@ const updateCount = $derived(extensions.filter((e) => e.hasUpdate).length); - $effect(() => { untrack(() => { loadLocalManga(); fetchFromRepo().finally(() => { loading = false; }); }); }); + $effect(() => { + untrack(async () => { + loadLocalManga(); + await load(); + loading = false; + fetchFromRepo(); + }); + }); + + $effect(() => { + if (!panel) return; + function onMouseDown(e: MouseEvent) { + if (!(e.target as HTMLElement).closest(".ext-panel, .icon-btn")) panel = null; + } + document.addEventListener("mousedown", onMouseDown, true); + return () => document.removeEventListener("mousedown", onMouseDown, true); + }); function focusOnMount(node: HTMLElement) { node.focus(); } @@ -194,10 +217,9 @@ /> {#if panel === "apk"} -
+
- Install from APK URL - + Install from APK URL
+
- Extension Repositories - + Extension Repositories
{#if reposLoading}
@@ -242,7 +263,7 @@ {/each}
{/if} -
+
+ \ No newline at end of file