diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 16:05:39 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 16:05:39 +0200 |
| commit | e76e27868b30a2b00b1ba42dd8e7ee6071e0c0d7 (patch) | |
| tree | 3c23483207d77adf3b67f290b67a57ce185fb1a1 /packages/meshbay-hub/src/meshbay_hub/static/files-app.js | |
| parent | 0ed078c92cabab1dab0f70f321562032ea549ce6 (diff) | |
| download | meshbay-e76e27868b30a2b00b1ba42dd8e7ee6071e0c0d7.tar.gz | |
feat: groups refactor Phase 1 — root RO/RW model + shared directories UI
Replace the upload boolean with per-root writable/removable/ejected flags.
Backend: new ops (update_root, eject_root, plug_root), MNP 1.1 protocol
messages, live RootSet updates so API always reflects current state, CLI
root subcommand (add/remove/set/list/eject/plug).
Frontend: SharedDirectoriesTable with optimistic toggle switches, eject/plug
in Files and Settings, upload gated on root.writable, ejected-root filtering
in all media apps, updated Create Group wizard, 10-locale i18n.
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 | 35 |
1 files changed, 31 insertions, 4 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 195c385..56311ae 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js @@ -212,6 +212,9 @@ function FilesPanel({ const unavailableHere = currentPath ? [] : subdirs.filter(d => rootState.get(d) && rootState.get(d).available === false); + const currentRootName = currentPath ? currentPath.split('/')[0] : ''; + const currentRoot = currentRootName ? rootState.get(currentRootName) : null; + const currentRootWritable = currentRoot ? currentRoot.writable : false; // A member cannot create a folder at the top of a group: that level is the // set of roots, which is the operator's configuration and not a directory on // anyone's disk. The node refuses it, so offering it would only produce an @@ -338,7 +341,7 @@ function FilesPanel({ ${status === 'connected' && html` <div class="file-toolbar"> <div class="toolbar-group"> - ${mayUpload && html` + ${currentPath && currentRootWritable && html` <label class="tb-btn primary"> <${Icon} name="upload" /> ${t('group.upload')} <input type="file" multiple style="display:none" @@ -419,16 +422,40 @@ function FilesPanel({ const full = currentPath ? currentPath + '/' + d : d; const inside = entriesUnder(entries, full); const bytes = inside.reduce((n, f) => n + (f.entry.size || 0), 0); + const rs = rootState.get(d); + const isEjected = rs && rs.ejected; + const isUnavail = unavailableHere.includes(d); + const isRemovable = rs && rs.removable; return html` - <tr class="file-row dir-row" key=${full} onClick=${() => setCurrentPath(full)}> + <tr class="file-row dir-row${isEjected ? ' root-ejected' : ''}" key=${full} + onClick=${() => { if (!isEjected) 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` + <td>${isEjected ? '\u{23CF}' : isUnavail ? '\u{26A0}' : '\u{1F4C1}'}</td> + <td>${d}${isEjected ? html` + <span class="root-offline"> ${t('group.root_ejected')}</span> + ` : isUnavail ? html` <span class="root-offline"> ${t('group.root_unavailable')}</span> + ` : ''}${rs && rs.writable && !isEjected ? html` + <span class="root-rw" title="${t('group.root_writable')}" style="margin-left:8px;opacity:0.5;font-size:0.9em">✎</span> + ` : ''}${isRemovable && isNodeAdmin && operatorPaired ? html` + <button class="btn-small root-eject-btn" title=${isEjected ? t('group.root_plug') : t('group.root_eject')} + onClick=${(ev) => { + ev.stopPropagation(); + const transport = transportRef.current; + if (!transport) return; + const sk = transport.sessionKeys && transport.sessionKeys.skEdB64; + const signFn = (sk && window.MeshBayKeys) + ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript) + : null; + const fn = isEjected + ? () => transport.plugRoot(groupId, d, signFn) + : () => transport.ejectRoot(groupId, d, signFn); + fn().catch((err) => setError(err.message)); + }}>${isEjected ? '\u{1F50C}' : '\u{23CF}'}</button> ` : ''}</td> <td class="file-size">${inside.length ? formatSize(bytes) : ''}</td> ${showGroup && html`<td></td>`} |