fix(ui): compact user panel with status icon and room indicator
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 22s

This commit is contained in:
mats committed 2026-09-22 10:06:01 +02:00
1 parent a2be7cf6a5
commit e687d41574
3 files changed
+51 -17

No files matched your search

+21 -5
View File
@@ -34,7 +34,8 @@ window.__dbg = S; // POC debug handle
const $ = (sel) => document.querySelector(sel); const $ = (sel) => document.querySelector(sel);
const els = { const els = {
stage: $('#stage'), screens: $('#screens'), 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'), 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 === room || S.connecting) return;
if (S.room) leaveVoice('switching channel'); if (S.room) leaveVoice('switching channel');
S.connecting = true; S.connecting = true;
setStatus(`connecting to ${room}…`); setStatus(`connecting to ${room}…`, 'connecting');
try { try {
S.iceServers = await fetchIce(); S.iceServers = await fetchIce();
@@ -135,7 +136,22 @@ function leaveVoice(reason) {
setStatus(reason || 'not connected'); 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 // signaling
@@ -146,7 +162,7 @@ function openSignaling(room) {
const url = `${proto}://${location.host}/ws?room=${encodeURIComponent(room)}&name=${encodeURIComponent(S.name)}`; const url = `${proto}://${location.host}/ws?room=${encodeURIComponent(room)}&name=${encodeURIComponent(S.name)}`;
const ws = new WebSocket(url); const ws = new WebSocket(url);
S.ws = ws; 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) => { ws.onmessage = (ev) => {
let msg; let msg;
try { msg = JSON.parse(ev.data); } catch { return; } try { msg = JSON.parse(ev.data); } catch { return; }
@@ -185,7 +201,7 @@ function handleSignal(msg) {
} }
sendMyPos(true); sendMyPos(true);
renderAll(); 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'); toast(`Joined ${msg.room}`, 'ok');
break; break;
} }
+6 -3
View File
@@ -39,8 +39,11 @@
<div class="me"> <div class="me">
<div class="avatar" id="me-avatar">?</div> <div class="avatar" id="me-avatar">?</div>
<div class="me-meta"> <div class="me-meta">
<input id="nick" maxlength="24" spellcheck="false" title="Nickname" /> <input id="nick" maxlength="24" spellcheck="false" title="Click to edit nickname" />
<span id="voice-status">not connected</span> <div class="status-row" id="status-row" title="Not connected">
<span class="status-indicator" id="status-dot"></span>
<span id="voice-status">Disconnected</span>
</div>
</div> </div>
</div> </div>
<div class="user-buttons"> <div class="user-buttons">
@@ -96,6 +99,6 @@
<div id="toasts"></div> <div id="toasts"></div>
<script src="app.js?v=9"></script> <script src="app.js?v=10"></script>
</body> </body>
</html> </html>
+24 -9
View File
@@ -66,25 +66,40 @@ body {
#user-panel { #user-panel {
margin-top: auto; background: var(--bg-0); padding: 8px; 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 { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
.me-meta { display: flex; flex-direction: column; min-width: 0; } .me-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; overflow: hidden; }
#nick { #nick {
background: transparent; border: none; color: var(--text-0); font: inherit; 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); } #nick:hover, #nick:focus { border-bottom-color: var(--text-2); }
#voice-status { font-size: 11px; color: var(--text-2); } .status-row {
.user-buttons { display: flex; gap: 2px; } 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 { .ctl {
background: transparent; border: none; color: var(--text-1); font-size: 16px; background: transparent; border: none; color: var(--text-1); font-size: 15px;
width: 32px; height: 32px; border-radius: 6px; cursor: pointer; position: relative; 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:hover { background: var(--bg-3); color: var(--text-0); }
.ctl.off { color: var(--red); } .ctl.off { color: var(--red); }
.ctl.off::after { .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; background: var(--red); transform: rotate(-40deg); border-radius: 1px;
} }
.ctl:disabled { opacity: .35; cursor: not-allowed; } .ctl:disabled { opacity: .35; cursor: not-allowed; }