doom scroll and viewer improvements
- move play/pause to clicking the video directly; remove dedicated button - replace emoji mute icons with flat minimal SVGs - add view-in-library button in doom scroll that navigates to the file's directory and opens it in the regular viewer - add display text overlay button in doom scroll and image lightbox; shows extracted text (translated by default when available) in a semi-transparent box at the bottom; toggle between translated/original - hide tag panel by default in image lightbox and video player modal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
const [doomScrollEntries, setDoomScrollEntries] = useState<FileEntry[]>([])
|
||||
const [doomScrollEntriesLoading, setDoomScrollEntriesLoading] = useState(false)
|
||||
const [doomScrollEntriesLoaded, setDoomScrollEntriesLoaded] = useState(false)
|
||||
const [pendingOpen, setPendingOpen] = useState<string | null>(null)
|
||||
|
||||
const toggleTag = (tagId: string) =>
|
||||
setSelectedTagIds((prev) => {
|
||||
@@ -234,9 +235,39 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
|
||||
// When filters are active, doom scroll uses filteredEntries (already filtered by search/tags).
|
||||
// When no filters, doom scroll uses files recursively under the current directory.
|
||||
// In both cases entries come from recursive API calls so entry.name is the full relative path.
|
||||
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' }))
|
||||
.map((e) => ({
|
||||
url: e.url!,
|
||||
name: e.name,
|
||||
mediaType: e.mediaType as 'video' | 'image',
|
||||
itemKey: `${libraryId}:mixed_file:${encodeURIComponent(e.name)}`,
|
||||
}))
|
||||
|
||||
const handleViewInLibrary = useCallback((item: DoomScrollItem) => {
|
||||
if (!item.itemKey) return
|
||||
const rel = decodeURIComponent(item.itemKey.split(':mixed_file:')[1])
|
||||
const parts = rel.split('/')
|
||||
parts.pop()
|
||||
const dir = parts.join('/')
|
||||
setDoomScrollActive(false)
|
||||
setPendingOpen(rel)
|
||||
loadPath(dir)
|
||||
}, [loadPath])
|
||||
|
||||
// Auto-open a file after navigating to its directory (from "view in library")
|
||||
useEffect(() => {
|
||||
if (!pendingOpen || !listing) return
|
||||
const filename = pendingOpen.split('/').pop()!
|
||||
const entry = listing.entries.find((e) => e.name === filename && e.type === 'file')
|
||||
if (!entry) return
|
||||
setPendingOpen(null)
|
||||
const idx = mediaEntries.indexOf(entry)
|
||||
openMediaEntry(entry, idx >= 0 ? idx : 0)
|
||||
// openMediaEntry is defined inline and depends on stable state; listing/pendingOpen are the real triggers
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listing, pendingOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -245,6 +276,7 @@ export default function MixedView({ libraryId, initialPath }: Props) {
|
||||
items={doomScrollItems}
|
||||
videoContext="mixed"
|
||||
onClose={() => setDoomScrollActive(false)}
|
||||
onViewInLibrary={handleViewInLibrary}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user