aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-28 16:11:46 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-28 16:11:46 +0200
commit4ef6ca817e36e207f57dd810727f7225e7d26eff (patch)
tree535de76925d84a03c5f3a5e86ff9e0393c6227f5 /packages/meshbay-hub/src/meshbay_hub/static/files-app.js
parenteba2e6b14484c124f3c87ff95cd7ee640833e5d3 (diff)
downloadmeshbay-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.js27
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>