diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub')
12 files changed, 79 insertions, 70 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"> diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index 264cd5b..afa1ba0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -556,7 +556,6 @@ export default { // Sidebar 'sidebar.create_group': 'Gruppe erstellen', 'sidebar.settings': 'Einstellungen', - 'sidebar.admin': 'Verwaltung', // User menu 'usermenu.language': 'Sprache', @@ -927,9 +926,7 @@ export default { 'lazy.reload': 'Seite neu laden', // Sidebar - 'sidebar.phone': 'Telefon', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index a2d7699..b4346b9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -544,7 +544,6 @@ export default { // Sidebar 'sidebar.create_group': 'Create group', 'sidebar.settings': 'Settings', - 'sidebar.admin': 'Admin', // User menu 'usermenu.language': 'Language', @@ -1028,9 +1027,7 @@ export default { 'usermenu.profile': 'Profile', // Node management (D5) - 'sidebar.phone': 'Phone', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', 'node.title': 'Node', 'node.service_checking': 'Checking node service…', 'node.service_not_installed': 'Not installed as a service', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index e7d313e..9bb26e1 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -551,7 +551,6 @@ export default { // Sidebar 'sidebar.create_group': 'Crear un grupo', 'sidebar.settings': 'Ajustes', - 'sidebar.admin': 'Administración', // User menu 'usermenu.language': 'Idioma', @@ -921,9 +920,7 @@ export default { 'lazy.reload': 'Recargar la página', // Sidebar - 'sidebar.phone': 'Teléfono', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index e1994dd..9f32a19 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -554,7 +554,6 @@ export default { // Sidebar 'sidebar.create_group': 'Créer un groupe', 'sidebar.settings': 'Paramètres', - 'sidebar.admin': 'Administration', // User menu 'usermenu.language': 'Langue', @@ -924,9 +923,7 @@ export default { 'lazy.reload': 'Recharger la page', // Sidebar - 'sidebar.phone': 'Téléphone', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index eb96a49..5578028 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -554,7 +554,6 @@ export default { // Sidebar 'sidebar.create_group': 'Crea un gruppo', 'sidebar.settings': 'Impostazioni', - 'sidebar.admin': 'Amministrazione', // User menu 'usermenu.language': 'Lingua', @@ -923,9 +922,7 @@ export default { 'lazy.reload': 'Ricarica la pagina', // Sidebar - 'sidebar.phone': 'Telefono', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index 2a4584a..b5e091c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -548,7 +548,6 @@ export default { // Sidebar 'sidebar.create_group': 'グループを作成', 'sidebar.settings': '設定', - 'sidebar.admin': '管理', // User menu 'usermenu.language': '言語', @@ -911,9 +910,7 @@ export default { 'lazy.reload': 'ページを再読み込み', // Sidebar - 'sidebar.phone': 'スマートフォン', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index ce4acbe..86e01a2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -555,7 +555,6 @@ export default { // Sidebar 'sidebar.create_group': 'Groep aanmaken', 'sidebar.settings': 'Instellingen', - 'sidebar.admin': 'Beheer', // User menu 'usermenu.language': 'Taal', @@ -925,9 +924,7 @@ export default { 'lazy.reload': 'Pagina opnieuw laden', // Sidebar - 'sidebar.phone': 'Telefoon', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index 72ace3a..090be3f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -569,7 +569,6 @@ export default { // Sidebar 'sidebar.create_group': 'Utwórz grupę', 'sidebar.settings': 'Ustawienia', - 'sidebar.admin': 'Administracja', // User menu 'usermenu.language': 'Język', @@ -945,9 +944,7 @@ export default { 'lazy.reload': 'Wczytaj stronę ponownie', // Sidebar (supplementary) - 'sidebar.phone': 'Telefon', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index ad46dc2..bfac600 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -553,7 +553,6 @@ export default { // Sidebar 'sidebar.create_group': 'Criar um grupo', 'sidebar.settings': 'Configurações', - 'sidebar.admin': 'Administração', // User menu 'usermenu.language': 'Idioma', @@ -922,9 +921,7 @@ export default { 'lazy.reload': 'Recarregar a página', // Sidebar - 'sidebar.phone': 'Celular', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index ccaf059..367efa0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -542,7 +542,6 @@ export default { // Sidebar 'sidebar.create_group': '创建群组', 'sidebar.settings': '设置', - 'sidebar.admin': '管理', // User menu 'usermenu.language': '语言', @@ -899,9 +898,7 @@ export default { 'lazy.reload': '重新加载页面', // Sidebar - 'sidebar.phone': '手机', 'androidsync.title': 'Android Sync', - 'sidebar.node': 'Node', // Node admin 'node.title': 'Node', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css index f6cb57c..b5996d8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/style.css +++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css @@ -328,6 +328,7 @@ a:hover { text-decoration: underline; } border-radius: 10px; box-shadow: var(--shadow-lg); min-width: 220px; + max-width: calc(100vw - 32px); z-index: 120; /* The language submenu expands to ten entries inside this dropdown, which overruns a short viewport. Clip horizontally as before, scroll vertically @@ -342,7 +343,14 @@ a:hover { text-decoration: underline; } display: flex; align-items: center; gap: 10px; padding: 12px 14px 8px; } -.user-menu-uname { font-weight: 600; font-size: 0.9em; color: var(--text); } +/* One line, whatever the name. Usernames go to 64 characters, and unclipped one + stretched the dropdown 122 px off the left of a phone — where, on a phone, + this line is now the only place the name is shown. */ +.user-menu-header > div { min-width: 0; } +.user-menu-uname { + font-weight: 600; font-size: 0.9em; color: var(--text); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} .user-menu-role { font-size: 0.75em; color: var(--text-dim); text-transform: capitalize; } .user-menu-divider { height: 1px; background: var(--border); margin: 4px 0; } @@ -2914,6 +2922,20 @@ a.transfer-name { /* ── Responsive ───────────────────────────────────────────────────────────── */ +/* On a phone or a tablet — the Android application included, in either + orientation — the user menu is the avatar alone; the name is the dropdown's + first line. The nav there holds the hamburger, the wordmark, transfers, + search, the bell and the menu: with the name in the trigger, a 20-character + handle put the menu 159 px off a 320 px screen. `pointer: coarse` is what + catches a phone held sideways, which is wider than the breakpoint. */ +@media (max-width: 768px), (pointer: coarse) { + .user-menu-trigger { padding: 3px; } + .user-menu-name, .user-menu-caret { display: none; } +} +@media (max-width: 520px) { + .nav-right { gap: 6px; } +} + @media (max-width: 768px) { .nav-hamburger { display: flex; } @@ -3123,6 +3145,15 @@ a.transfer-name { font-size: 1.25rem; margin-right: 4px; } +/* Search left the sidebar for the nav, and took its "you are here" with it. */ +.nav-search { + display: inline-flex; + align-items: center; + padding: 4px; + border-radius: 6px; +} +.nav-search:hover, +.nav-search[aria-current="page"] { background: rgba(255, 255, 255, 0.1); } .notif-badge { position: absolute; top: -6px; |