import { useState, useEffect } from 'react' import { ExternalLink, Download, X, ChevronLeft, ChevronRight, Play } from 'lucide-react' import { useTranslation } from '../../i18n' import type { TripFile } from '../../types' import { getAuthUrl } from '../../api/authUrl' import { openFile as openFileUrl } from '../../utils/fileDownload' import { triggerDownload, isVideo } from './FileManager.helpers' import VideoPlayer from '../Journey/VideoPlayer' // Image lightbox with gallery navigation interface ImageLightboxProps { files: (TripFile & { url: string })[] initialIndex: number onClose: () => void } export function ImageLightbox({ files, initialIndex, onClose }: ImageLightboxProps) { const { t } = useTranslation() const [index, setIndex] = useState(initialIndex) const [imgSrc, setImgSrc] = useState('') const [touchStart, setTouchStart] = useState(null) const file = files[index] const fileIsVideo = isVideo(file?.mime_type) useEffect(() => { setImgSrc('') // Images use a one-shot signed URL; a video must use the plain same-origin // URL (cookie auth) so its many Range requests all authenticate (#823). if (file && !isVideo(file.mime_type)) getAuthUrl(file.url, 'download').then(setImgSrc) }, [file?.url, file?.mime_type]) const goPrev = () => setIndex(i => Math.max(0, i - 1)) const goNext = () => setIndex(i => Math.min(files.length - 1, i + 1)) useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() if (e.key === 'ArrowLeft') goPrev() if (e.key === 'ArrowRight') goNext() } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, []) if (!file) return null const hasPrev = index > 0 const hasNext = index < files.length - 1 const navBtn = (side: 'left' | 'right', onClick: () => void, show: boolean): React.ReactNode => show ? ( ) : null return (
setTouchStart(e.touches[0].clientX)} onTouchEnd={e => { if (touchStart === null) return const diff = e.changedTouches[0].clientX - touchStart if (diff > 60) goPrev() else if (diff < -60) goNext() setTouchStart(null) }} > {/* Header */}
e.stopPropagation()}> {file.original_name} {index + 1} / {files.length}
{/* Main image + nav */}
{ if (e.target === e.currentTarget) onClose() }}> {navBtn('left', goPrev, hasPrev)} {fileIsVideo ? (
e.stopPropagation()}>
) : ( imgSrc && {file.original_name} e.stopPropagation()} /> )} {navBtn('right', goNext, hasNext)}
{/* Thumbnail strip */} {files.length > 1 && (
e.stopPropagation()}> {files.map((f, i) => ( setIndex(i)} /> ))}
)}
) } function ThumbImg({ file, active, onClick }: { file: TripFile & { url: string }; active: boolean; onClick: () => void }) { const fileIsVideo = isVideo(file.mime_type) const [src, setSrc] = useState('') // Videos have no stored thumbnail and can't render as an ; show a play // placeholder and don't mint a download token for them (#823). useEffect(() => { if (!fileIsVideo) getAuthUrl(file.url, 'download').then(setSrc) }, [file.url, fileIsVideo]) return ( ) }