diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 27 |
1 files changed, 25 insertions, 2 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index cb7a462..4a4e712 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -1240,6 +1240,10 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, // Directories are not index entries, so a new empty one needs a nudge // to appear in the breadcrumb listing. const [nodeDirs, setNodeDirs] = useState([]); + // The group's roots and whether each is readable. A root whose drive is + // unplugged keeps its files listed — they are frozen, not deleted — so this is + // the only thing that lets the UI say which of the two it is. + const [nodeRoots, setNodeRoots] = useState([]); const [isNodeAdmin, setIsNodeAdmin] = useState(false); // Paired ≠ operator account. `is_node_admin` says the hub account owning this @@ -1275,6 +1279,7 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, const fresh = indexMsg.entries || []; setEntries(fresh); if (indexMsg.dirs) setNodeDirs(indexMsg.dirs); + if (indexMsg.roots) setNodeRoots(indexMsg.roots); cacheGroupIndex(groupId, group ? group.name : groupId, fresh); }, [groupId, group]); @@ -1492,6 +1497,7 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, const indexMsg = await transport.fetchIndex(); if (indexMsg.entries) setEntries(indexMsg.entries); if (indexMsg.dirs) setNodeDirs(indexMsg.dirs); + if (indexMsg.roots) setNodeRoots(indexMsg.roots); } catch (err) { setError(err.message); } @@ -1671,6 +1677,19 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, } const subdirs = [...dirs].sort(); + // At the top of a group the folders on screen ARE the roots, so their state + // belongs there. Deeper in, everything shown lives inside one readable root + // and there is nothing to flag. + const rootState = new Map(nodeRoots.map(r => [r.name, r])); + const unavailableHere = currentPath + ? [] + : subdirs.filter(d => rootState.get(d) && rootState.get(d).available === 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 + // error nobody can act on. + const canCreateDir = Boolean(currentPath); + const baseLabel = { idle: t('status.idle'), discovering: t('status.discovering'), @@ -1875,9 +1894,11 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, <input type="file" multiple style="display:none" onChange=${uploadFile} /> </label> + ${canCreateDir && html` <button class="tb-btn" onClick=${makeDirectory}> <${Icon} name="folder-plus" /> ${t('group.mkdir')} </button> + `} </div> <div class="breadcrumbs"> @@ -1944,8 +1965,10 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth, onChange=${() => toggle(dirKey(d))} /> </td> `} - <td>\u{1F4C1}</td> - <td>${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> + ` : ''}</td> <td class="file-size">${inside.length ? formatSize(bytes) : ''}</td> <td class="td-type"></td> <td class="td-date"></td> |