import { useState, useEffect } from 'react'; import { Dialog, Box, IconButton, CircularProgress, Typography, } from '@mui/material'; import { Close as CloseIcon, NavigateBefore as PrevIcon, NavigateNext as NextIcon, Download as DownloadIcon, Share as ShareIcon, Delete as DeleteIcon, } from '@mui/icons-material'; import type { Asset } from '../types'; import api from '../services/api'; interface ViewerModalProps { asset: Asset | null; assets: Asset[]; onClose: () => void; onDelete?: (assetId: string) => void; onShare?: (assetId: string) => void; } export default function ViewerModal({ asset, assets, onClose, onDelete, onShare, }: ViewerModalProps) { const [currentUrl, setCurrentUrl] = useState(''); const [loading, setLoading] = useState(true); const [currentIndex, setCurrentIndex] = useState(-1); useEffect(() => { if (asset) { const index = assets.findIndex((a) => a.id === asset.id); setCurrentIndex(index); loadMedia(asset); } // Cleanup blob URL on unmount or asset change return () => { if (currentUrl) { URL.revokeObjectURL(currentUrl); } }; }, [asset]); useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { if (!asset) return; if (e.key === 'Escape') { onClose(); } else if (e.key === 'ArrowLeft') { if (currentIndex > 0) { const prevAsset = assets[currentIndex - 1]; loadMedia(prevAsset); setCurrentIndex(currentIndex - 1); } } else if (e.key === 'ArrowRight') { if (currentIndex < assets.length - 1) { const nextAsset = assets[currentIndex + 1]; loadMedia(nextAsset); setCurrentIndex(currentIndex + 1); } } }; window.addEventListener('keydown', handleKeyPress); return () => window.removeEventListener('keydown', handleKeyPress); }, [asset, currentIndex, assets, onClose]); const loadMedia = async (asset: Asset) => { try { setLoading(true); // Revoke previous blob URL to prevent memory leaks if (currentUrl) { URL.revokeObjectURL(currentUrl); } // Load media through backend proxy with auth const blob = await api.getMediaBlob(asset.id, 'original'); const url = URL.createObjectURL(blob); setCurrentUrl(url); } catch (error) { console.error('Failed to load media:', error); } finally { setLoading(false); } }; const handlePrev = () => { if (currentIndex > 0) { const prevAsset = assets[currentIndex - 1]; loadMedia(prevAsset); setCurrentIndex(currentIndex - 1); } }; const handleNext = () => { if (currentIndex < assets.length - 1) { const nextAsset = assets[currentIndex + 1]; loadMedia(nextAsset); setCurrentIndex(currentIndex + 1); } }; const handleDownload = () => { if (currentUrl && asset) { const link = document.createElement('a'); link.href = currentUrl; link.download = asset.original_filename; link.click(); } }; const handleDelete = () => { if (asset && onDelete) { onDelete(asset.id); onClose(); } }; const handleShare = () => { if (asset && onShare) { onShare(asset.id); } }; if (!asset) return null; return ( {/* Top bar */} {asset.original_filename} {onShare && ( )} {onDelete && ( )} {/* Navigation buttons */} {currentIndex > 0 && ( )} {currentIndex < assets.length - 1 && ( )} {/* Content */} {loading && ( )} {!loading && asset.type === 'photo' && ( )} {!loading && asset.type === 'video' && ( )} {/* Bottom info */} {currentIndex + 1} / {assets.length} {(asset.size_bytes / 1024 / 1024).toFixed(2)} MB {asset.width && asset.height && ( {asset.width} × {asset.height} )} ); }