diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-28 16:11:46 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-28 16:11:46 +0200 |
| commit | 4ef6ca817e36e207f57dd810727f7225e7d26eff (patch) | |
| tree | 535de76925d84a03c5f3a5e86ff9e0393c6227f5 /packages/meshbay-hub/src/meshbay_hub/static/files-app.js | |
| parent | eba2e6b14484c124f3c87ff95cd7ee640833e5d3 (diff) | |
| download | meshbay-4ef6ca817e36e207f57dd810727f7225e7d26eff.tar.gz | |
feat(hub): search downloads, photos view, pre-connect optimization
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/files-app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/files-app.js | 27 |
1 files changed, 16 insertions, 11 deletions
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({ <input type="text" placeholder="${t('group.filter')}" value=${filter} onInput=${e => setFilter(e.target.value)} /> </div>`} - ${!readOnly && html`<button class="tb-btn ${selecting ? 'active' : ''}" + <button class="tb-btn ${selecting ? 'active' : ''}" onClick=${() => { setSelecting(v => !v); setSelected(new Set()); }}> <${Icon} name=${selecting ? 'check' : 'checkbox'} /> ${selecting ? t('group.select_done') : t('group.select')} - </button>`} + </button> ${selecting && html`<div class="tb-actions">${actionItems}</div>`} </div> </div> |