diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-12 01:27:00 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-12 01:27:00 +0200 |
| commit | 3324b4eb701e34f64e859dd90937793ccf7f75c5 (patch) | |
| tree | f22af245c36b76a1a158b91cc97f98df763ecb0f /packages/meshbay-hub/src/meshbay_hub/static/app.js | |
| parent | 14911aae21ec4975f7a805b91e3c4867cb41974b (diff) | |
| download | meshbay-3324b4eb701e34f64e859dd90937793ccf7f75c5.tar.gz | |
feat: keep the sidebar for groups; search in the nav, the rest in the user menu
Search is an icon in the nav, left of the tray button and the bell, and marks
itself current on its page. Administration, Node, Create group and Android
Sync moved to the user menu, between Settings and the theme, each under the
condition it had in the sidebar. The sidebar keeps Public groups and the
groups.
On a phone, a tablet or the Android application the menu trigger is the
avatar alone (max-width 768px or a coarse pointer); the name is the dropdown's
first line, one line with an ellipsis. Measured: a 20-character handle put the
trigger 159 px off a 320 px screen with search and transfers in the nav, and a
64-character one pushed the dropdown 122 px off the left.
Creating a group is a desktop-application feature by design: a hand-typed
#/create-group no longer opens the form in a browser, and QUICKSTART step 6
now says to use the desktop application instead of the browser.
test_layout_responsive read only the first 768px media block; it reads them
all now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 86 |
1 files changed, 47 insertions, 39 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index b45903a..ea4cee6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -86,7 +86,7 @@ const BRAND_M = new URL('./meshbay-m.png', import.meta.url).href; // ── User Menu ──────────────────────────────────────────────────────────────── -function UserMenu({ user, theme, onThemeChange, onLogout }) { +function UserMenu({ user, theme, onThemeChange, onLogout, showNode }) { const [open, setOpen] = useState(false); const [langOpen, setLangOpen] = useState(false); const ref = useRef(null); @@ -101,10 +101,13 @@ function UserMenu({ user, theme, onThemeChange, onLogout }) { }, [open]); const resolved = resolveTheme(theme); + const isStaff = user.role === 'moderator' || user.role === 'admin'; + const go = (path) => { setOpen(false); navigate(path); }; return html` <div class="user-menu-wrap" ref=${ref}> - <button class="user-menu-trigger" onClick=${() => setOpen(o => !o)}> + <button class="user-menu-trigger" aria-label=${user.username} + aria-expanded=${open ? 'true' : 'false'} onClick=${() => setOpen(o => !o)}> <span class="user-menu-avatar">${user.username[0].toUpperCase()}</span> <span class="user-menu-name">${user.username}</span> <${Icon} name="chevron" cls="user-menu-caret ${open ? 'flip' : ''}" /> @@ -114,7 +117,7 @@ function UserMenu({ user, theme, onThemeChange, onLogout }) { <div class="user-menu-header"> <span class="user-menu-avatar lg">${user.username[0].toUpperCase()}</span> <div> - <div class="user-menu-uname">${user.username}</div> + <div class="user-menu-uname" title=${user.username}>${user.username}</div> <div class="user-menu-role">${user.role || 'user'}</div> </div> </div> @@ -137,6 +140,26 @@ function UserMenu({ user, theme, onThemeChange, onLogout }) { <button class="user-menu-item" onClick=${() => { setOpen(false); navigate('/settings'); }}> <${Icon} name="gear" cls="umi-icon" /> ${t('usermenu.settings')} </button> + ${/* Here rather than in the sidebar, which is kept for the groups: + these are reached now and then, the groups all the time. */''} + ${isStaff && html` + <button class="user-menu-item" onClick=${() => go('/admin')}> + <${Icon} name="shield" cls="umi-icon" /> ${t('admin.title')} + </button> + `} + ${showNode && html` + <button class="user-menu-item" onClick=${() => go('/node')}> + <${Icon} name="server" cls="umi-icon" /> ${t('node.title')} + </button> + <button class="user-menu-item" onClick=${() => go('/create-group')}> + <${Icon} name="plus" cls="umi-icon" /> ${t('sidebar.create_group')} + </button> + `} + ${PHONE_SYNC && html` + <button class="user-menu-item" onClick=${() => go('/android-sync')}> + <${Icon} name="phone" cls="umi-icon" /> ${t('androidsync.title')} + </button> + `} <button class="user-menu-item" onClick=${() => { onThemeChange(resolved === 'dark' ? 'light' : 'dark'); setOpen(false); @@ -395,7 +418,7 @@ function formatEta(seconds) { // ── Nav ────────────────────────────────────────────────────────────────────── function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, - hubUnset }) { + hubUnset, route, showNode }) { return html` <nav class="nav"> <div class="nav-left"> @@ -409,6 +432,13 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, </div> <div class="nav-right"> ${user && html`<${TransferWidget} />`} + ${user && html` + <a class="nav-notif nav-search" href="#/search" + aria-current=${route === '/search' ? 'page' : null} + title=${t('sidebar.search')} aria-label=${t('sidebar.search')}> + <${Icon} name="search" /> + </a> + `} ${platform.capabilities.tray && html` <button class="nav-tray" onClick=${() => platform.minimizeToTray(trayLabels())} title=${t('nav.minimize_tray')} aria-label=${t('nav.minimize_tray')}> @@ -422,7 +452,7 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, </a> `} ${user ? html` - <${UserMenu} user=${user} theme=${theme} + <${UserMenu} user=${user} theme=${theme} showNode=${showNode} onThemeChange=${onThemeChange} onLogout=${onLogout} /> ` : hubUnset ? null : html` <a class="nav-btn" href="#/login">${t('nav.login')}</a> @@ -434,40 +464,17 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, // ── Sidebar ────────────────────────────────────────────────────────────────── -function Sidebar({ groups, presence, indexProgressPct, route, menuOpen, role, showNode, +// Groups, and little else: search is in the nav; administration, the node, +// group creation and Android Sync in the user menu. +function Sidebar({ groups, presence, indexProgressPct, route, menuOpen, allowPublicGroups = true }) { - const isStaff = role === 'moderator' || role === 'admin'; return html` <aside class="sidebar ${menuOpen ? 'open' : ''}"> - ${isStaff && html` + ${allowPublicGroups && html` <div class="sidebar-section"> - <div class="sidebar-heading">${t('sidebar.admin')}</div> - <a class="sidebar-item ${route === '/admin' ? 'active' : ''}" - href="#/admin"><${Icon} name="shield" /> ${t('admin.title')}</a> - </div> - `} - <div class="sidebar-section"> - <div class="sidebar-heading">${t('sidebar.discover')}</div> - ${allowPublicGroups && html` + <div class="sidebar-heading">${t('sidebar.discover')}</div> <a class="sidebar-item ${route === '/explore' ? 'active' : ''}" - href="#/explore"><${Icon} name="globe" /> ${t('sidebar.public_groups')}</a>`} - <a class="sidebar-item ${route === '/search' ? 'active' : ''}" - href="#/search"><${Icon} name="search" /> ${t('sidebar.search')}</a> - </div> - ${platform.capabilities.nodeAdmin && showNode && html` - <div class="sidebar-section"> - <div class="sidebar-heading">${t('sidebar.node')}</div> - <a class="sidebar-item ${route === '/node' ? 'active' : ''}" - href="#/node"><${Icon} name="server" /> ${t('node.title')}</a> - <a class="sidebar-item ${route === '/create-group' ? 'active' : ''}" - href="#/create-group"><${Icon} name="plus" /> ${t('sidebar.create_group')}</a> - </div> - `} - ${PHONE_SYNC && html` - <div class="sidebar-section"> - <div class="sidebar-heading">${t('sidebar.phone')}</div> - <a class="sidebar-item ${route === '/android-sync' ? 'active' : ''}" - href="#/android-sync"><${Icon} name="phone" /> ${t('androidsync.title')}</a> + href="#/explore"><${Icon} name="globe" /> ${t('sidebar.public_groups')}</a> </div> `} <div class="sidebar-section"> @@ -1434,7 +1441,9 @@ function App() { page = html`<${ExplorePage} token=${user.token} myGroupIds=${groups.map(g => g.id)} allowPublicGroups=${allowPublicGroups} />`; - } else if (route === '/create-group') { + } else if (route === '/create-group' && platform.capabilities.nodeAdmin) { + // The desktop application only, by design: a browser is offered no way to + // create a group, and a hand-typed address does not open one either. page = html`<${LazyCreateGroupPage} token=${user.token} username=${user.username} allowPublicGroups=${allowPublicGroups} onNodeLinked=${refreshNodeKey} @@ -1500,7 +1509,8 @@ function App() { onThemeChange=${changeTheme} onLogout=${authCtx.logout} onMenuToggle=${() => setMenuOpen(o => !o)} - unreadCount=${unreadCount} hubUnset=${needsHub} /> + unreadCount=${unreadCount} hubUnset=${needsHub} route=${groupRoute} + showNode=${platform.capabilities.nodeAdmin && (hasNodeKey || localNode)} /> <div class="layout"> ${user && html`<${Sidebar} groups=${groups} @@ -1508,9 +1518,7 @@ function App() { indexProgressPct=${indexProgressPct} route=${groupRoute} menuOpen=${menuOpen} - role=${user.role} - allowPublicGroups=${allowPublicGroups} - showNode=${hasNodeKey || localNode} />`} + allowPublicGroups=${allowPublicGroups} />`} ${menuOpen && html`<div class="overlay visible" onClick=${() => setMenuOpen(false)} />`} <main class="main"> |