From b0d146679f616082eab9222e5841aa2a4498a7be Mon Sep 17 00:00:00 2001 From: Garret Patti <42485635+garretpatti@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:51:29 -0400 Subject: [PATCH] scope doom scroll to current directory when no filters active When no filters are selected, doom scroll now recursively fetches only items under the current directory instead of the entire library root. Navigating to a new directory invalidates the cached listing. Filter- based doom scroll (search or tags) continues to search library-wide. Co-Authored-By: Claude Sonnet 4.6 --- src/components/mixed/MixedView.tsx | 48 +++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/mixed/MixedView.tsx b/src/components/mixed/MixedView.tsx index 6fb5423..bfae948 100644 --- a/src/components/mixed/MixedView.tsx +++ b/src/components/mixed/MixedView.tsx @@ -38,6 +38,9 @@ export default function MixedView({ libraryId, initialPath }: Props) { const [recursiveLoaded, setRecursiveLoaded] = useState(false) const [doomScrollActive, setDoomScrollActive] = useState(false) const [doomScrollLoading, setDoomScrollLoading] = useState(false) + const [doomScrollEntries, setDoomScrollEntries] = useState([]) + const [doomScrollEntriesLoading, setDoomScrollEntriesLoading] = useState(false) + const [doomScrollEntriesLoaded, setDoomScrollEntriesLoaded] = useState(false) const toggleTag = (tagId: string) => setSelectedTagIds((prev) => { @@ -71,6 +74,14 @@ export default function MixedView({ libraryId, initialPath }: Props) { loadPath(initialPath) }, [loadPath, initialPath]) + // Invalidate doom scroll entry cache when the user navigates to a different directory + useEffect(() => { + setDoomScrollEntries([]) + setDoomScrollEntriesLoaded(false) + setDoomScrollEntriesLoading(false) + setDoomScrollLoading(false) + }, [currentPath]) + const fetchAssignments = useCallback(() => { fetch(`/api/tags/library-assignments?libraryId=${encodeURIComponent(libraryId)}`) .then((r) => r.json()) @@ -95,6 +106,21 @@ export default function MixedView({ libraryId, initialPath }: Props) { .finally(() => setRecursiveLoading(false)) }, [libraryId, recursiveLoaded, recursiveLoading]) + const fetchDoomScrollEntries = useCallback(() => { + if (doomScrollEntriesLoaded || doomScrollEntriesLoading) return + setDoomScrollEntriesLoading(true) + fetch( + `/api/browse?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(currentPath)}&recursive=true` + ) + .then((r) => r.json()) + .then((data: DirectoryListing) => { + setDoomScrollEntries(data.entries) + setDoomScrollEntriesLoaded(true) + }) + .catch(() => {}) + .finally(() => setDoomScrollEntriesLoading(false)) + }, [libraryId, currentPath, doomScrollEntriesLoaded, doomScrollEntriesLoading]) + // Fetch the full recursive listing the first time any filter becomes active useEffect(() => { if (!filtersActive) return @@ -182,25 +208,33 @@ export default function MixedView({ libraryId, initialPath }: Props) { fetchRecursive() return } - if (recursiveLoaded) { + // No filters: scope to current directory + if (doomScrollEntriesLoaded) { setDoomScrollActive(true) return } setDoomScrollLoading(true) - fetchRecursive() + fetchDoomScrollEntries() } - // Activate doom scroll once the recursive listing finishes loading (when triggered by button) + // Activate doom scroll once the appropriate listing finishes loading (when triggered by button) useEffect(() => { - if (doomScrollLoading && !recursiveLoading && recursiveLoaded) { + if (!doomScrollLoading) return + const filtersDone = filtersActive && !recursiveLoading && recursiveLoaded + const noFiltersDone = !filtersActive && !doomScrollEntriesLoading && doomScrollEntriesLoaded + if (filtersDone || noFiltersDone) { setDoomScrollLoading(false) setDoomScrollActive(true) } - }, [doomScrollLoading, recursiveLoading, recursiveLoaded]) + }, [ + doomScrollLoading, filtersActive, + recursiveLoading, recursiveLoaded, + doomScrollEntriesLoading, doomScrollEntriesLoaded, + ]) // When filters are active, doom scroll uses filteredEntries (already filtered by search/tags). - // When no filters, doom scroll uses the full recursiveEntries. - const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : recursiveEntries) + // When no filters, doom scroll uses files recursively under the current directory. + const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : doomScrollEntries) .filter((e) => e.type === 'file' && (e.mediaType === 'video' || e.mediaType === 'image') && e.url && isBrowserPlayable(e.name)) .map((e) => ({ url: e.url!, name: e.name, mediaType: e.mediaType as 'video' | 'image' }))