add tagging to tv
This commit is contained in:
@@ -31,6 +31,9 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
const [assignments, setAssignments] = useState<Record<string, string[]>>({})
|
||||
const [filterRefreshKey, setFilterRefreshKey] = useState(0)
|
||||
const [showFilters, setShowFilters] = useState(true)
|
||||
const [recursiveEntries, setRecursiveEntries] = useState<FileEntry[]>([])
|
||||
const [recursiveLoading, setRecursiveLoading] = useState(false)
|
||||
const [recursiveLoaded, setRecursiveLoaded] = useState(false)
|
||||
|
||||
const toggleTag = (tagId: string) =>
|
||||
setSelectedTagIds((prev) => {
|
||||
@@ -73,6 +76,29 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
|
||||
useEffect(() => { fetchAssignments() }, [fetchAssignments])
|
||||
|
||||
const filtersActive = search !== '' || selectedTagIds.size > 0
|
||||
|
||||
// Fetch the full recursive listing the first time any filter becomes active
|
||||
useEffect(() => {
|
||||
if (!filtersActive || recursiveLoaded || recursiveLoading) return
|
||||
setRecursiveLoading(true)
|
||||
fetch(`/api/browse?libraryId=${encodeURIComponent(libraryId)}&path=&recursive=true`)
|
||||
.then((r) => r.json())
|
||||
.then((data: DirectoryListing) => {
|
||||
setRecursiveEntries(data.entries)
|
||||
setRecursiveLoaded(true)
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setRecursiveLoading(false))
|
||||
}, [filtersActive, libraryId, recursiveLoaded, recursiveLoading])
|
||||
|
||||
const mediaKeyFor = (entry: FileEntry) => {
|
||||
// In recursive mode entry.name is already the full relative path from the library root
|
||||
if (filtersActive) return `${libraryId}:${encodeURIComponent(entry.name)}`
|
||||
const rel = currentPath ? `${currentPath}/${entry.name}` : entry.name
|
||||
return `${libraryId}:${encodeURIComponent(rel)}`
|
||||
}
|
||||
|
||||
const handleEntry = (entry: FileEntry) => {
|
||||
if (entry.type === 'directory') {
|
||||
const newPath = currentPath ? `${currentPath}/${entry.name}` : entry.name
|
||||
@@ -85,15 +111,12 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
} else if (entry.mediaType === 'image') {
|
||||
setModal({ type: 'image', url: entry.url, name: entry.name, mediaKey: mediaKeyFor(entry) })
|
||||
} else {
|
||||
// Download other file types
|
||||
window.open(entry.url, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
const handleTagEntry = (entry: FileEntry) => {
|
||||
const relativePath = currentPath ? `${currentPath}/${entry.name}` : entry.name
|
||||
const mediaKey = `${libraryId}:${encodeURIComponent(relativePath)}`
|
||||
setTagPanel({ entry, mediaKey })
|
||||
setTagPanel({ entry, mediaKey: mediaKeyFor(entry) })
|
||||
}
|
||||
|
||||
const navigateUp = () => {
|
||||
@@ -107,12 +130,9 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
? currentPath.split('/').filter(Boolean)
|
||||
: []
|
||||
|
||||
const mediaKeyFor = (entry: FileEntry) => {
|
||||
const rel = currentPath ? `${currentPath}/${entry.name}` : entry.name
|
||||
return `${libraryId}:${encodeURIComponent(rel)}`
|
||||
}
|
||||
const sourceEntries = filtersActive ? recursiveEntries : (listing?.entries ?? [])
|
||||
|
||||
const filteredEntries = (listing?.entries ?? []).filter((entry) => {
|
||||
const filteredEntries = sourceEntries.filter((entry) => {
|
||||
if (search && !entry.name.toLowerCase().includes(search.toLowerCase())) return false
|
||||
if (selectedTagIds.size > 0 && entry.type !== 'directory') {
|
||||
const entryTags = assignments[mediaKeyFor(entry)] ?? []
|
||||
@@ -121,8 +141,6 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
return true
|
||||
})
|
||||
|
||||
const filtersActive = search !== '' || selectedTagIds.size > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
@@ -184,23 +202,23 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{loading && <LoadingSkeleton />}
|
||||
{(loading || recursiveLoading) && <LoadingSkeleton />}
|
||||
{error && (
|
||||
<div className="rounded-lg border p-8 text-center" style={{ borderColor: 'var(--border)', color: 'var(--text-secondary)' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && listing && (
|
||||
{!loading && !recursiveLoading && !error && (filtersActive || listing) && (
|
||||
<>
|
||||
{filteredEntries.length === 0 ? (
|
||||
<div className="rounded-lg border p-12 text-center" style={{ borderColor: 'var(--border)', color: 'var(--text-secondary)' }}>
|
||||
This folder is empty.
|
||||
{filtersActive ? 'No results found.' : 'This folder is empty.'}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
|
||||
{/* Up button */}
|
||||
{breadcrumbs.length > 0 && (
|
||||
{/* Up button — hidden during recursive search */}
|
||||
{!filtersActive && breadcrumbs.length > 0 && (
|
||||
<button
|
||||
onClick={navigateUp}
|
||||
className="flex flex-col items-center justify-center gap-2 rounded-xl border p-4 text-xs transition-colors"
|
||||
|
||||
Reference in New Issue
Block a user