diff --git a/web/competition.js b/web/competition.js index 61f4864..1c372b9 100644 --- a/web/competition.js +++ b/web/competition.js @@ -1313,12 +1313,26 @@ // ---- Main render ------------------------------------------------------- + function wsIndicator() { + state.wsDot = el("span", { class: "connection-status " + (state.wsOnline ? "online" : "offline") }); + state.wsText = el("span", { class: "muted" }, state.wsOnline ? t("online") : t("offline")); + return el("span", { class: "ws-indicator" }, state.wsDot, state.wsText); + } + + function updateWsIndicator() { + if (state.wsDot) { + state.wsDot.classList.toggle("online", state.wsOnline); + state.wsDot.classList.toggle("offline", !state.wsOnline); + } + if (state.wsText) state.wsText.textContent = state.wsOnline ? t("online") : t("offline"); + } + function render() { clearNode(root); const backBtn = el("button", { class: "ghost", onclick: () => { if (state.ws) { state.ws.close(); state.ws = null; } navigate("competitions"); } }, "← " + t("back")); - root.appendChild(renderTopbar(user, { extra: backBtn })); + root.appendChild(renderTopbar(user, { extra: [wsIndicator(), backBtn] })); const container = el("div", { class: "container" }); container.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.5rem" } }, @@ -1329,10 +1343,6 @@ isClosed() ? " " : null, isClosed() ? el("span", { class: "badge warn" }, t("closed")) : null, ), - el("div", { class: "row" }, - el("span", { class: "connection-status " + (state.wsOnline ? "online" : "offline") }), - el("span", { class: "muted" }, state.wsOnline ? t("online") : t("offline")), - ), )); const tabs = el("div", { class: "tabs" }); @@ -1393,8 +1403,8 @@ await loadAll(); state.ws = openCompetitionWS(competitionId, { - onopen: () => { state.wsOnline = true; const e = document.querySelector(".connection-status"); if (e) { e.classList.add("online"); e.classList.remove("offline"); } }, - onclose: () => { state.wsOnline = false; const e = document.querySelector(".connection-status"); if (e) { e.classList.remove("online"); e.classList.add("offline"); } }, + onopen: () => { state.wsOnline = true; updateWsIndicator(); }, + onclose: () => { state.wsOnline = false; updateWsIndicator(); }, onmessage: handleWSMessage, }); render(); diff --git a/web/style.css b/web/style.css index 7b8c552..6426ea6 100644 --- a/web/style.css +++ b/web/style.css @@ -387,11 +387,22 @@ tbody tr.transferred { background: #f3f6fb; } height: 8px; border-radius: 50%; background: #d1d5db; - margin-right: 0.5rem; + flex: none; } .connection-status.online { background: #10b981; } .connection-status.offline { background: #ef4444; } +.ws-indicator { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.85rem; + line-height: 1; +} +/* Optically drop the dot ~1px: an all-caps label (Online/Offline) has + unused descender space, so its glyphs sit above the flex centre line. */ +.ws-indicator .connection-status { position: relative; top: 1px; } + textarea { resize: vertical; min-height: 60px; } @media (max-width: 700px) {