fix(ui): compact user panel with status icon and room indicator
This commit is contained in:
1 parent
a2be7cf6a5
commit
e687d41574
3 files changed
+51
-17
No files matched your search
+21
-5
@@ -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;
|
||||
}
|
||||
|
||||
+6
-3
@@ -39,8 +39,11 @@
|
||||
<div class="me">
|
||||
<div class="avatar" id="me-avatar">?</div>
|
||||
<div class="me-meta">
|
||||
<input id="nick" maxlength="24" spellcheck="false" title="Nickname" />
|
||||
<span id="voice-status">not connected</span>
|
||||
<input id="nick" maxlength="24" spellcheck="false" title="Click to edit nickname" />
|
||||
<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 class="user-buttons">
|
||||
@@ -96,6 +99,6 @@
|
||||
|
||||
<div id="toasts"></div>
|
||||
|
||||
<script src="app.js?v=9"></script>
|
||||
<script src="app.js?v=10"></script>
|
||||
</body>
|
||||
</html>
|
||||
+24
-9
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user