From 9372ea95e4bd4da36c22438afb58f15717af198c Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 6 Sep 2026 02:20:58 +0200 Subject: feat(node): always-on file selection with select-all checkbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the Select/Done toggle — checkboxes and action buttons are always visible. A select-all checkbox in the header row follows the standard pattern: check selects all visible items, uncheck clears the entire selection, indeterminate when partially selected. Navigation and file preview are unaffected (only the checkbox selects). Co-Authored-By: Claude Opus 4.6 --- .../src/meshbay_hub/static/files-app.js | 57 ++++++++++++---------- 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'packages') 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 ab7e254..d6cb3b9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js @@ -29,7 +29,6 @@ function FilesPanel({ isNodeAdmin, operatorPaired, mayUpload, userId, setError, onPreview, showGroup, readOnly, getTransport, onRefreshIndex, showRefresh, }) { - const [selecting, setSelecting] = useState(false); const [selected, setSelected] = useState(() => new Set()); const [sortKey, setSortKey] = useState('name'); const [sortAsc, setSortAsc] = useState(true); @@ -234,13 +233,27 @@ function FilesPanel({ return next; }); + const allVisibleKeys = [...subdirs.map(d => dirKey(d)), ...sorted.map(e => e.id)]; + const allSelected = allVisibleKeys.length > 0 && allVisibleKeys.every(k => selected.has(k)); + const someSelected = allVisibleKeys.some(k => selected.has(k)); + const toggleAll = () => { + if (allSelected) { + setSelected(new Set()); + } else { + setSelected(prev => { + const next = new Set(prev); + for (const k of allVisibleKeys) next.add(k); + return next; + }); + } + }; + const onlyFile = selectedFiles.length === 1 && selectedDirs.length === 0 ? selectedFiles[0] : null; const deletableFiles = selectedFiles.filter( e => isNodeAdmin || (userId && e.uploader_id === userId)); const run = (fn) => { - setSelecting(false); setSelected(new Set()); Promise.resolve().then(fn).catch(err => { if (err && err.name !== 'AbortError') setError(err.message); @@ -365,21 +378,17 @@ function FilesPanel({ setFilter(e.target.value)} /> `} - - ${selecting && html`
${actionItems}
`} +
${actionItems}
- ${selecting && html``} + - selecting ? toggle(dirKey(d)) : setCurrentPath(full)}> - ${selecting && html` + setCurrentPath(full)}> - `} `; })} ${sorted.map(e => html` - selecting && toggle(e.id)}> - ${selecting && html` - - `} + + `)} ${sorted.length === 0 && subdirs.length === 0 && html` - `} -- cgit v1.2.3
+ { if (el) el.indeterminate = someSelected && !allSelected; }} + onChange=${toggleAll} /> + toggleSort('name')}> ${t('group.col_name')} ${sortKey === 'name' ? (sortAsc ? '▲' : '▼') : ''} @@ -402,15 +411,12 @@ function FilesPanel({ const inside = entriesUnder(entries, full); const bytes = inside.reduce((n, f) => n + (f.entry.size || 0), 0); return html` -
ev.stopPropagation()} onChange=${() => toggle(dirKey(d))} /> ${unavailableHere.includes(d) ? '\u{26A0}' : '\u{1F4C1}'} ${d}${unavailableHere.includes(d) ? html` ${t('group.root_unavailable')} @@ -422,18 +428,15 @@ function FilesPanel({
- ev.stopPropagation()} - onChange=${() => toggle(e.id)} /> -
+ ev.stopPropagation()} + onChange=${() => toggle(e.id)} /> + ${FILE_ICONS[e.type] || FILE_ICONS.other} - ${!selecting && canPreview(e) + ${canPreview(e) ? html` onPreview(e)}>${e.name}` : e.name } @@ -450,7 +453,7 @@ function FilesPanel({
+
${filter ? t('group.empty_filter') : t('group.empty_dir')}