From 4ef6ca817e36e207f57dd810727f7225e7d26eff Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 28 Aug 2026 16:11:46 +0200 Subject: feat(hub): search downloads, photos view, pre-connect optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downloads from search: FilesPanel accepts a getTransport callback for cross-group file downloads. Select/Download are available in search; delete and zip are hidden (readOnly). Each download connects to the entry's group via the pool on demand. Photos view: PhotosApp reused in search with the same pattern as Videos/Music — synthetic root, per-entry transport refs, group badges on album cards. Lightbox uses per-entry transport for full-resolution image fetches. Zip download hidden in search context. Pre-connecting: connections to all groups start as soon as indexing finishes, regardless of the active view. Thumbnails cache across pool evictions, so they're ready when the user switches views. Co-Authored-By: Claude Opus 4.6 --- .../src/meshbay_hub/static/files-app.js | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/files-app.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js index daf13af..75005e2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js @@ -27,7 +27,7 @@ function FilesPanel({ groupId, transportRef, gekRef, status, entries, nodeDirs, nodeRoots, setEntries, setNodeDirs, setNodeRoots, applyIndex, isNodeAdmin, operatorPaired, mayUpload, userId, setError, onPreview, - showGroup, readOnly, + showGroup, readOnly, getTransport, }) { const [selecting, setSelecting] = useState(false); const [selected, setSelected] = useState(() => new Set()); @@ -42,13 +42,18 @@ function FilesPanel({ useEffect(() => { setCurrentPath(''); setSelected(new Set()); setFilter(''); }, [groupId]); const downloadFile = useCallback(async (entry) => { - const transport = transportRef.current; + let transport, gek; + if (getTransport) { + const conn = await getTransport(entry); + transport = conn.transport; + gek = conn.gek; + } else { + transport = transportRef.current; + gek = gekRef.current; + } if (!transport || !transport.connected) return; - // Both of these have to happen inside the click: a browser grants a file - // picker, and re-grants a folder, only from a user gesture — so this stays - // a click handler all the way down into downloadEntry's own picker call. - await downloadEntry(transfers, transport, gekRef.current, entry); - }, []); + await downloadEntry(transfers, transport, gek, entry); + }, [getTransport]); const uploadFile = useCallback((e) => { const files = [...(e.target.files || [])]; @@ -271,14 +276,14 @@ function FilesPanel({ // first file ever downloaded. for (const e of selectedFiles) await downloadFile(e); }), { disabled: selectedFiles.length === 0 })} - ${action('archive', + ${!readOnly && action('archive', selectedDirs.length ? t('group.download_zip_n', { n: selectedDirs.length }) : t('group.download_zip_n', { n: 0 }), () => run(async () => { for (const d of selectedDirs) await downloadDirectory(d); }), { disabled: selectedDirs.length === 0 })} - ${mayEverDelete && action('trash', + ${mayEverDelete && !readOnly && action('trash', deletableCount ? t('group.delete_n', { n: deletableCount }) : t('group.delete'), () => { const names = [...deletableFiles.map(e => e.name), @@ -337,14 +342,14 @@ function FilesPanel({ setFilter(e.target.value)} /> `} - ${!readOnly && html``} + ${selecting && html`
${actionItems}
`} -- cgit v1.2.3