diff --git a/src/features/library/components/Library.svelte b/src/features/library/components/Library.svelte index 6122255..6bbfd67 100644 --- a/src/features/library/components/Library.svelte +++ b/src/features/library/components/Library.svelte @@ -85,10 +85,40 @@ const hasActiveFilters = $derived(tabStatus !== "ALL" || Object.values(tabFilters).some(Boolean)); const cols = $derived(Math.max(1, Math.floor((containerWidth + CARD_GAP) / (CARD_MIN_W + CARD_GAP)))); + const BUILTIN_TABS = ["library", "downloaded"] as const; + + const completedCatId = $derived( + store.categories.find(c => c.name === COMPLETED_NAME && c.id !== 0)?.id ?? null + ); + + const allTabIds = $derived((() => { + const catIds = store.categories.filter(c => c.id !== 0).map(c => String(c.id)); + const pinned = store.settings.libraryPinnedTabOrder ?? []; + const known = new Set([...BUILTIN_TABS, ...catIds]); + const ordered = [...pinned.filter(id => known.has(id))]; + const inOrder = new Set(ordered); + for (const id of [...BUILTIN_TABS, ...catIds]) { + if (!inOrder.has(id)) ordered.push(id); + } + return ordered; + })()); + + const hiddenTabs = $derived(new Set(store.settings.hiddenLibraryTabs ?? [])); + + const visibleTabIds = $derived(allTabIds.filter(id => !hiddenTabs.has(id))); + + const virtualTabIds = $derived(visibleTabIds.filter(id => + id === "library" || id === "downloaded" || (completedCatId !== null && id === String(completedCatId)) + )); + + const folderTabIds = $derived(visibleTabIds.filter(id => + id !== "library" && id !== "downloaded" && (completedCatId === null || id !== String(completedCatId)) + )); + const visibleCategories = $derived((() => { const defaultId = store.settings.defaultLibraryCategoryId ?? null; return store.categories - .filter(c => c.id !== 0 && !(store.settings.hiddenCategoryIds ?? []).includes(c.id)) + .filter(c => c.id !== 0 && !hiddenTabs.has(String(c.id))) .sort((a, b) => { if (a.id === defaultId) return -1; if (b.id === defaultId) return 1; @@ -172,7 +202,7 @@ $effect(() => { filtered; untrack(() => { renderVisible = paginator.reset(); }); }); $effect(() => { retryCount; loading = true; error = null; if (retryCount > 0) cache.clear(CACHE_KEYS.LIBRARY); untrack(() => loadData()); }); - let prevTab = tab; + let prevTab = $state(tab); $effect(() => { const nextTab = tab; if (scrollEl && nextTab !== prevTab) { @@ -605,6 +635,10 @@ {hasActiveFilters} {anims} {visibleCategories} + {visibleTabIds} + {virtualTabIds} + {folderTabIds} + {completedCatId} {counts} {search} {refreshing} diff --git a/src/features/library/components/LibraryToolbar.svelte b/src/features/library/components/LibraryToolbar.svelte index d603ece..9bf3895 100644 --- a/src/features/library/components/LibraryToolbar.svelte +++ b/src/features/library/components/LibraryToolbar.svelte @@ -1,7 +1,7 @@
Library
- {#each [["library", "Saved"], ["downloaded", "Downloaded"]] as [f, label]} - + {#each visibleTabIds as id, idx} + {@const cat = visibleCategories.find(c => String(c.id) === id)} + {#if id === "library" || id === "downloaded" || cat} + {#if cat && dragInsertIdx === idx && activeDragKind === "tab"} + + {/if} + + {#if cat && id !== String(completedCatId) && dragInsertIdx === idx + 1 && activeDragKind === "tab" && idx === visibleTabIds.length - 1} + + {/if} + {/if} {/each} - {#if visibleCategories.length > 0} - -
- {#each visibleCategories as cat, idx} - {#if dragInsertIdx === idx && activeDragKind === "tab"} - - {/if} - - {#if dragInsertIdx === idx + 1 && activeDragKind === "tab" && idx === visibleCategories.length - 1} - - {/if} - {/each} -
- {/if}
@@ -204,10 +198,8 @@ .header { position: relative; z-index: 100; display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; min-width: 0; } .header-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; flex-shrink: 0; } .heading { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; flex-shrink: 0; } - .tabs { display: flex; align-items: center; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; flex-shrink: 1; min-width: 0; overflow: hidden; } - .tabs-scroll { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; min-width: 0; flex-shrink: 1; } - .tabs-scroll::-webkit-scrollbar { display: none; } - .tab-separator { width: 1px; height: 16px; background: var(--border-dim); flex-shrink: 0; margin: 0 2px; } + .tabs { display: flex; align-items: center; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; flex-shrink: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; } + .tabs::-webkit-scrollbar { display: none; } .tab { position: relative; z-index: 1; display: flex; align-items: center; gap: 5px; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 4px 10px; border-radius: var(--radius-sm); border: 1px solid transparent; color: var(--text-faint); white-space: nowrap; transition: background var(--t-base), color var(--t-base), border-color var(--t-base); cursor: grab; flex-shrink: 0; } .tab:hover { color: var(--text-muted); } .tab.active { background: var(--accent-muted); color: var(--accent-fg); border-color: var(--accent-dim); } diff --git a/src/features/settings/sections/FoldersSettings.svelte b/src/features/settings/sections/FoldersSettings.svelte index 9b7e04a..d95f732 100644 --- a/src/features/settings/sections/FoldersSettings.svelte +++ b/src/features/settings/sections/FoldersSettings.svelte @@ -1,10 +1,19 @@