From bbf7092d9f818791d9c0fba108ac58803a52cc3c Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Sat, 13 Jun 2026 16:01:37 -0500 Subject: [PATCH] Fix: Debounce on LibraryToolbar & Grid-Rendering Optimizations --- src/lib/components/library/LibraryGrid.svelte | 34 ++++++++++++++++++- .../components/library/LibraryToolbar.svelte | 5 +++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/components/library/LibraryGrid.svelte b/src/lib/components/library/LibraryGrid.svelte index c672537..3602d10 100644 --- a/src/lib/components/library/LibraryGrid.svelte +++ b/src/lib/components/library/LibraryGrid.svelte @@ -31,6 +31,33 @@ const statsAlways = $derived(settingsState.settings.libraryStatsAlways ?? false) const cropCovers = $derived(settingsState.settings.libraryCropCovers ?? true) + // Virtual rendering — only mount cards up to visibleCount + const PAGE = 48 + let visibleCount = $state(PAGE) + let sentinel: HTMLDivElement | undefined = $state() + let observer: IntersectionObserver | null = null + + const renderedItems = $derived(items.slice(0, visibleCount)) + const hasMore = $derived(visibleCount < items.length) + + // Reset when items list changes (tab switch, filter, etc) + $effect(() => { + items + visibleCount = PAGE + }) + + $effect(() => { + observer?.disconnect() + if (!sentinel) return + observer = new IntersectionObserver((entries) => { + if (entries[0]?.isIntersecting && hasMore) { + visibleCount = Math.min(visibleCount + PAGE, items.length) + } + }, { rootMargin: '200px' }) + observer.observe(sentinel) + return () => observer?.disconnect() + }) + function onDocDown(e: MouseEvent) { if (movePanelOpen && !(e.target as HTMLElement).closest('.move-wrap')) movePanelOpen = false } @@ -111,7 +138,7 @@ {:else}
- {#each items as m (m.id)} + {#each renderedItems as m (m.id)} {@const isSelected = selected.has(m.id)} {@const isCompleted = m.status === 'COMPLETED' || (!m.unreadCount && (m.chapters?.totalCount ?? 0) > 0)}
+ {#if hasMore} + + {/if} {/if} @@ -289,6 +319,8 @@ .title-skeleton { height: 12px; margin-top: var(--sp-2); width: 80%; border-radius: var(--radius-sm); } .skeleton { background: var(--bg-raised); animation: pulse 1.4s ease infinite; } + .sentinel { height: 1px; width: 100%; } + .empty { display: flex; align-items: center; justify-content: center; height: 60%; color: var(--text-muted); font-size: var(--text-sm); diff --git a/src/lib/components/library/LibraryToolbar.svelte b/src/lib/components/library/LibraryToolbar.svelte index 13b0022..1e382ae 100644 --- a/src/lib/components/library/LibraryToolbar.svelte +++ b/src/lib/components/library/LibraryToolbar.svelte @@ -63,7 +63,12 @@ onTabDragStart, onTabDragOver, onTabDragLeave, onTabDrop, onTabDragEnd, }: Props = $props(); + let wheelTimer: ReturnType | null = null + function onTabsWheel(e: WheelEvent) { + e.preventDefault() + if (wheelTimer) return + wheelTimer = setTimeout(() => { wheelTimer = null }, 180) const ids = visibleTabIds.filter(id => id === "library" || id === "downloaded" || visibleCategories.some(c => String(c.id) === id)); const idx = ids.indexOf(tab); if (e.deltaY > 0 && idx < ids.length - 1) onTabChange(ids[idx + 1]);