diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 02:20:58 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 02:20:58 +0200 |
| commit | 9372ea95e4bd4da36c22438afb58f15717af198c (patch) | |
| tree | 927ae69ecdc4c0fe89e97d9e51605c28d1c56d49 /packages | |
| parent | a00988d2437f4eb109d25814f5c725ed0438bab6 (diff) | |
| download | meshbay-9372ea95e4bd4da36c22438afb58f15717af198c.tar.gz | |
feat(node): always-on file selection with select-all checkbox
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 <noreply@anthropic.com>
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/files-app.js | 57 |
1 files changed, 30 insertions, 27 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 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({ <input type="text" placeholder="${t('group.filter')}" value=${filter} onInput=${e => setFilter(e.target.value)} /> </div>`} - <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> - ${selecting && html`<div class="tb-actions">${actionItems}</div>`} + <div class="tb-actions">${actionItems}</div> </div> </div> <table class="file-table"> <thead> <tr> - ${selecting && html`<th class="sel-cell"></th>`} + <th class="sel-cell"> + <input type="checkbox" checked=${allSelected} + ref=${(el) => { if (el) el.indeterminate = someSelected && !allSelected; }} + onChange=${toggleAll} /> + </th> <th></th> <th class="sortable" onClick=${() => 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` - <tr class="file-row dir-row" key=${full} onClick=${() => - selecting ? toggle(dirKey(d)) : setCurrentPath(full)}> - ${selecting && html` + <tr class="file-row dir-row" key=${full} onClick=${() => setCurrentPath(full)}> <td class="sel-cell"> <input type="checkbox" checked=${selected.has(dirKey(d))} onClick=${(ev) => ev.stopPropagation()} onChange=${() => toggle(dirKey(d))} /> </td> - `} <td>${unavailableHere.includes(d) ? '\u{26A0}' : '\u{1F4C1}'}</td> <td>${d}${unavailableHere.includes(d) ? html` <span class="root-offline"> ${t('group.root_unavailable')}</span> @@ -422,18 +428,15 @@ function FilesPanel({ </tr> `; })} ${sorted.map(e => html` - <tr class="file-row" key=${e.id} - onClick=${() => selecting && toggle(e.id)}> - ${selecting && html` - <td class="sel-cell"> - <input type="checkbox" checked=${selected.has(e.id)} - onClick=${(ev) => ev.stopPropagation()} - onChange=${() => toggle(e.id)} /> - </td> - `} + <tr class="file-row" key=${e.id}> + <td class="sel-cell"> + <input type="checkbox" checked=${selected.has(e.id)} + onClick=${(ev) => ev.stopPropagation()} + onChange=${() => toggle(e.id)} /> + </td> <td>${FILE_ICONS[e.type] || FILE_ICONS.other}</td> <td class="file-name"> - ${!selecting && canPreview(e) + ${canPreview(e) ? html`<a class="file-link" onClick=${() => onPreview(e)}>${e.name}</a>` : e.name } @@ -450,7 +453,7 @@ function FilesPanel({ </tr> `)} ${sorted.length === 0 && subdirs.length === 0 && html` - <tr><td colspan=${(selecting ? 6 : 5) + (showGroup ? 1 : 0)} class="file-empty"> + <tr><td colspan=${6 + (showGroup ? 1 : 0)} class="file-empty"> ${filter ? t('group.empty_filter') : t('group.empty_dir')} </td></tr> `} |