From e687d41574ff433e1c1f1d4ecaa7a40579ffdd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20T=C3=B6rnberg?= Date: Tue, 22 Sep 2026 10:06:01 +0200 Subject: [PATCH] fix(ui): compact user panel with status icon and room indicator --- public/app.js | 26 +++++++++++++++++++++----- public/index.html | 9 ++++++--- public/style.css | 33 ++++++++++++++++++++++++--------- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/public/app.js b/public/app.js index 5d52b6e..42d1d0e 100644 --- a/public/app.js +++ b/public/app.js @@ -34,7 +34,8 @@ window.__dbg = S; // POC debug handle const $ = (sel) => document.querySelector(sel); const els = { stage: $('#stage'), screens: $('#screens'), - spatial: $('#spatial'), status: $('#voice-status'), nick: $('#nick'), + spatial: $('#spatial'), status: $('#voice-status'), statusDot: $('#status-dot'), + statusRow: $('#status-row'), nick: $('#nick'), meAvatar: $('#me-avatar'), mic: $('#btn-mic'), deafen: $('#btn-deafen'), }; @@ -75,7 +76,7 @@ async function joinVoice(room) { if (S.room === room || S.connecting) return; if (S.room) leaveVoice('switching channel'); S.connecting = true; - setStatus(`connecting to ${room}…`); + setStatus(`connecting to ${room}…`, 'connecting'); try { S.iceServers = await fetchIce(); @@ -135,7 +136,22 @@ function leaveVoice(reason) { setStatus(reason || 'not connected'); } -function setStatus(text) { els.status.textContent = text; } +function setStatus(text, state = '') { + if (els.status) { + // Concise display text + let shortText = text; + if (text.startsWith('voice connected')) { + shortText = S.room ? `#${S.room}` : 'Connected'; + } else if (text.startsWith('connecting to')) { + shortText = 'Connecting…'; + } + els.status.textContent = shortText; + } + if (els.statusRow) els.statusRow.title = text; + if (els.statusDot) { + els.statusDot.className = 'status-indicator' + (state ? ` ${state}` : ''); + } +} // --------------------------------------------------------------------------- // signaling @@ -146,7 +162,7 @@ function openSignaling(room) { const url = `${proto}://${location.host}/ws?room=${encodeURIComponent(room)}&name=${encodeURIComponent(S.name)}`; const ws = new WebSocket(url); S.ws = ws; - ws.onerror = () => { S.connecting = false; setStatus('connection failed'); reject(new Error('websocket failed')); }; + ws.onerror = () => { S.connecting = false; setStatus('connection failed', 'error'); reject(new Error('websocket failed')); }; ws.onmessage = (ev) => { let msg; try { msg = JSON.parse(ev.data); } catch { return; } @@ -185,7 +201,7 @@ function handleSignal(msg) { } sendMyPos(true); renderAll(); - setStatus(`voice connected — ${msg.peers.length + 1} in ${msg.room}`); + setStatus(`voice connected — ${msg.peers.length + 1} in ${msg.room}`, 'online'); toast(`Joined ${msg.room}`, 'ok'); break; } diff --git a/public/index.html b/public/index.html index 622acdb..fcee514 100644 --- a/public/index.html +++ b/public/index.html @@ -39,8 +39,11 @@
?
- - not connected + +
+ + Disconnected +
@@ -96,6 +99,6 @@
- + diff --git a/public/style.css b/public/style.css index 1f3ff94..140e19a 100644 --- a/public/style.css +++ b/public/style.css @@ -66,25 +66,40 @@ body { #user-panel { margin-top: auto; background: var(--bg-0); padding: 8px; - display: flex; align-items: center; gap: 8px; + display: flex; align-items: center; justify-content: space-between; gap: 6px; + min-height: 52px; } -.me { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; } -.me-meta { display: flex; flex-direction: column; min-width: 0; } +.me { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; } +.me-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; overflow: hidden; } #nick { background: transparent; border: none; color: var(--text-0); font: inherit; - font-weight: 600; width: 100%; padding: 0; outline: none; border-bottom: 1px dashed transparent; + font-weight: 600; font-size: 13px; width: 100%; padding: 0; outline: none; border-bottom: 1px dashed transparent; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #nick:hover, #nick:focus { border-bottom-color: var(--text-2); } -#voice-status { font-size: 11px; color: var(--text-2); } -.user-buttons { display: flex; gap: 2px; } +.status-row { + display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-2); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; +} +.status-indicator { + width: 7px; height: 7px; border-radius: 50%; background: var(--text-2); flex-shrink: 0; +} +.status-indicator.online { background: var(--green); } +.status-indicator.connecting { background: var(--yellow); } +.status-indicator.error { background: var(--red); } +#voice-status { + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.user-buttons { display: flex; gap: 2px; flex-shrink: 0; } .ctl { - background: transparent; border: none; color: var(--text-1); font-size: 16px; - width: 32px; height: 32px; border-radius: 6px; cursor: pointer; position: relative; + background: transparent; border: none; color: var(--text-1); font-size: 15px; + width: 28px; height: 28px; border-radius: 6px; cursor: pointer; position: relative; + display: grid; place-items: center; padding: 0; } .ctl:hover { background: var(--bg-3); color: var(--text-0); } .ctl.off { color: var(--red); } .ctl.off::after { - content: ""; position: absolute; left: 7px; top: 15px; width: 18px; height: 2px; + content: ""; position: absolute; left: 5px; top: 13px; width: 18px; height: 2px; background: var(--red); transform: rotate(-40deg); border-radius: 1px; } .ctl:disabled { opacity: .35; cursor: not-allowed; }