// Drive.jsx - the drive itself: sidebar, browser, upload. function Drive({ email, onSignedOut, toast }) { const [drive, setDrive] = React.useState('user:me'); const [path, setPath] = React.useState('/'); const [entries, setEntries] = React.useState([]); const [quota, setQuota] = React.useState(null); const [groups, setGroups] = React.useState([]); const [busy, setBusy] = React.useState(true); const [uploads, setUploads] = React.useState([]); const [dropping, setDropping] = React.useState(false); const [preview, setPreview] = React.useState(null); const fileInput = React.useRef(null); const refresh = React.useCallback(async (d = drive, p = path) => { setBusy(true); try { const [list, q] = await Promise.all([ api.drive({ op: 'ls', drive: d, path: p }), api.drive({ op: 'quota', drive: d }), ]); setEntries(list.entries || []); setQuota(q); } catch (err) { toast('error', err.message); setEntries([]); } finally { setBusy(false); } }, [drive, path, toast]); React.useEffect(() => { refresh(drive, path); }, [drive, path]); React.useEffect(() => { api.drive({ op: 'groups' }) .then(g => setGroups(g.groups || [])) .catch(() => setGroups([])); // groups are optional; a failure here is not worth a toast }, []); function go(p) { setPath(p); } function switchDrive(d) { setDrive(d); setPath('/'); } async function upload(files) { for (const file of Array.from(files)) { const id = `${file.name}-${Date.now()}-${Math.random()}`; setUploads(u => [...u, { id, name: file.name, progress: 0 }]); try { await api.upload(drive, path, file, (pct) => { setUploads(u => u.map(x => x.id === id ? { ...x, progress: pct } : x)); }); toast('ok', `${file.name} uploaded`); } catch (err) { toast('error', `${file.name}: ${err.message}`); } finally { setUploads(u => u.filter(x => x.id !== id)); } } refresh(); } async function run(input, okMessage) { try { await api.drive(input); if (okMessage) toast('ok', okMessage); refresh(); } catch (err) { toast('error', err.message); } } function newFolder() { const name = window.prompt('Folder name'); if (name) run({ op: 'mkdir', drive, path, name }, `Created ${name}`); } function remove(e) { if (!window.confirm(`Delete ${e.name}?${e.kind === 'folder' ? ' The folder must be empty.' : ''}`)) return; run({ op: 'rm', drive, path: e.path }, `Deleted ${e.name}`); } function rename(e) { const to = window.prompt('New name', e.name); if (to && to !== e.name) run({ op: 'mv', drive, path: e.path, to_name: to }, `Renamed to ${to}`); } function share(e) { const user = window.prompt(`Share ${e.name} with which email?`); if (user) run({ op: 'share', drive, path: e.path, user, access: 'read' }, `Shared with ${user}`); } async function signOut() { await api.logout().catch(() => {}); onSignedOut(); } const drives = [ { key: 'user:me', label: 'My drive', icon: 'drive' }, ...groups.map(g => ({ key: `group:${g.id}`, label: g.name, icon: 'folder' })), ]; return (