// 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 }) { 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]); // Escape closes, because a modal that traps you is worse than no modal. React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]); if (!file) return null; const src = api.downloadURL(drive, file.path); return (
e.stopPropagation()}>
{file.name} {humanBytes(file.size)}
{/* A floor on the height: without it a 4KB icon collapses the modal to a strip and it reads as broken rather than small. Text is the one kind that must not be centred -- a document starts at the top. */}
); } function PreviewBody({ state, src, file }) { if (state.loading) { return
Loading preview…
; } switch (state.kind) { case 'image': return {file.name}; case 'video': return