Fix: Debounce on LibraryToolbar & Grid-Rendering Optimizations

This commit is contained in:
Youwes09
2026-06-13 16:01:37 -05:00
parent b1bc3c81f9
commit bbf7092d9f
2 changed files with 38 additions and 1 deletions
+33 -1
View File
@@ -31,6 +31,33 @@
const statsAlways = $derived(settingsState.settings.libraryStatsAlways ?? false) const statsAlways = $derived(settingsState.settings.libraryStatsAlways ?? false)
const cropCovers = $derived(settingsState.settings.libraryCropCovers ?? true) 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) { function onDocDown(e: MouseEvent) {
if (movePanelOpen && !(e.target as HTMLElement).closest('.move-wrap')) movePanelOpen = false if (movePanelOpen && !(e.target as HTMLElement).closest('.move-wrap')) movePanelOpen = false
} }
@@ -111,7 +138,7 @@
{:else} {:else}
<div class="grid"> <div class="grid">
{#each items as m (m.id)} {#each renderedItems as m (m.id)}
{@const isSelected = selected.has(m.id)} {@const isSelected = selected.has(m.id)}
{@const isCompleted = m.status === 'COMPLETED' || (!m.unreadCount && (m.chapters?.totalCount ?? 0) > 0)} {@const isCompleted = m.status === 'COMPLETED' || (!m.unreadCount && (m.chapters?.totalCount ?? 0) > 0)}
<button <button
@@ -152,6 +179,9 @@
</button> </button>
{/each} {/each}
</div> </div>
{#if hasMore}
<div bind:this={sentinel} class="sentinel" aria-hidden="true"></div>
{/if}
{/if} {/if}
</div> </div>
@@ -289,6 +319,8 @@
.title-skeleton { height: 12px; margin-top: var(--sp-2); width: 80%; border-radius: var(--radius-sm); } .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; } .skeleton { background: var(--bg-raised); animation: pulse 1.4s ease infinite; }
.sentinel { height: 1px; width: 100%; }
.empty { .empty {
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
height: 60%; color: var(--text-muted); font-size: var(--text-sm); height: 60%; color: var(--text-muted); font-size: var(--text-sm);
@@ -63,7 +63,12 @@
onTabDragStart, onTabDragOver, onTabDragLeave, onTabDrop, onTabDragEnd, onTabDragStart, onTabDragOver, onTabDragLeave, onTabDrop, onTabDragEnd,
}: Props = $props(); }: Props = $props();
let wheelTimer: ReturnType<typeof setTimeout> | null = null
function onTabsWheel(e: WheelEvent) { 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 ids = visibleTabIds.filter(id => id === "library" || id === "downloaded" || visibleCategories.some(c => String(c.id) === id));
const idx = ids.indexOf(tab); const idx = ids.indexOf(tab);
if (e.deltaY > 0 && idx < ids.length - 1) onTabChange(ids[idx + 1]); if (e.deltaY > 0 && idx < ids.length - 1) onTabChange(ids[idx + 1]);