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 <noreply@anthropic.com>
This commit is contained in:
Garret Patti
2026-04-12 20:51:29 -04:00
parent 887cc05901
commit b0d146679f

View File

@@ -38,6 +38,9 @@ export default function MixedView({ libraryId, initialPath }: Props) {
const [recursiveLoaded, setRecursiveLoaded] = useState(false) const [recursiveLoaded, setRecursiveLoaded] = useState(false)
const [doomScrollActive, setDoomScrollActive] = useState(false) const [doomScrollActive, setDoomScrollActive] = useState(false)
const [doomScrollLoading, setDoomScrollLoading] = useState(false) const [doomScrollLoading, setDoomScrollLoading] = useState(false)
const [doomScrollEntries, setDoomScrollEntries] = useState<FileEntry[]>([])
const [doomScrollEntriesLoading, setDoomScrollEntriesLoading] = useState(false)
const [doomScrollEntriesLoaded, setDoomScrollEntriesLoaded] = useState(false)
const toggleTag = (tagId: string) => const toggleTag = (tagId: string) =>
setSelectedTagIds((prev) => { setSelectedTagIds((prev) => {
@@ -71,6 +74,14 @@ export default function MixedView({ libraryId, initialPath }: Props) {
loadPath(initialPath) loadPath(initialPath)
}, [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(() => { const fetchAssignments = useCallback(() => {
fetch(`/api/tags/library-assignments?libraryId=${encodeURIComponent(libraryId)}`) fetch(`/api/tags/library-assignments?libraryId=${encodeURIComponent(libraryId)}`)
.then((r) => r.json()) .then((r) => r.json())
@@ -95,6 +106,21 @@ export default function MixedView({ libraryId, initialPath }: Props) {
.finally(() => setRecursiveLoading(false)) .finally(() => setRecursiveLoading(false))
}, [libraryId, recursiveLoaded, recursiveLoading]) }, [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 // Fetch the full recursive listing the first time any filter becomes active
useEffect(() => { useEffect(() => {
if (!filtersActive) return if (!filtersActive) return
@@ -182,25 +208,33 @@ export default function MixedView({ libraryId, initialPath }: Props) {
fetchRecursive() fetchRecursive()
return return
} }
if (recursiveLoaded) { // No filters: scope to current directory
if (doomScrollEntriesLoaded) {
setDoomScrollActive(true) setDoomScrollActive(true)
return return
} }
setDoomScrollLoading(true) 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(() => { useEffect(() => {
if (doomScrollLoading && !recursiveLoading && recursiveLoaded) { if (!doomScrollLoading) return
const filtersDone = filtersActive && !recursiveLoading && recursiveLoaded
const noFiltersDone = !filtersActive && !doomScrollEntriesLoading && doomScrollEntriesLoaded
if (filtersDone || noFiltersDone) {
setDoomScrollLoading(false) setDoomScrollLoading(false)
setDoomScrollActive(true) 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 filters are active, doom scroll uses filteredEntries (already filtered by search/tags).
// When no filters, doom scroll uses the full recursiveEntries. // When no filters, doom scroll uses files recursively under the current directory.
const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : recursiveEntries) const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : doomScrollEntries)
.filter((e) => e.type === 'file' && (e.mediaType === 'video' || e.mediaType === 'image') && e.url && isBrowserPlayable(e.name)) .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' })) .map((e) => ({ url: e.url!, name: e.name, mediaType: e.mediaType as 'video' | 'image' }))