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' }))