// Preview.jsx - show a file without downloading it. // // Media and PDFs are embedded straight from bkn's signed URL: the browser // handles those cross-origin without help. Text comes back through // /api/preview, because a cross-origin fetch of the blob would need CORS // headers bkn does not send. function Preview({ file, drive, onClose, toast, siblings = [], onNavigate }) { const [state, setState] = React.useState({ loading: true }); React.useEffect(() => { if (!file) return; let live = true; setState({ loading: true }); apiCall(`/api/preview?drive=${encodeURIComponent(drive)}&path=${encodeURIComponent(file.path)}`) .then(r => { if (live) setState({ loading: false, ...r }); }) .catch(err => { if (!live) return; setState({ loading: false, kind: 'error', message: err.message }); }); return () => { live = false; }; }, [file, drive]); // Where this file sits among its neighbours, so the arrows know what is next. const at = file ? siblings.findIndex(s => s.id === file.id) : -1; const prev = at > 0 ? siblings[at - 1] : null; const next = at >= 0 && at < siblings.length - 1 ? siblings[at + 1] : null; // Escape closes, because a modal that traps you is worse than no modal. // Arrows walk the gallery: opening one photo and being stuck with it is the // thing that makes people close the viewer and open another. React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') return onClose(); if (!onNavigate) return; if (e.key === 'ArrowLeft' && prev) onNavigate(prev); if (e.key === 'ArrowRight' && next) onNavigate(next); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose, onNavigate, prev, next]); if (!file) return null; const src = api.downloadURL(drive, file.path); return (
{state.text}
{state.truncated && — truncated —}
);
case 'too_big':
return (