Compare commits

..
2 Commits
Author SHA1 Message Date
CodingPhoenix 93a81fd82c Move competition back button onto the page with a real icon
The back button moves out of the topbar to the top-left of the page
above the competition name, and now renders an inline SVG arrow
(iconArrowLeft) instead of the "←" character.
2026-09-06 06:23:54 +02:00
CodingPhoenix 357888e310 Fix WebSocket status label not updating and move it into the topbar
The onopen/onclose handlers only toggled the CSS class on the status
dot, never the separate text span, so it stayed "offline" after a
reconnect. Replace the querySelector-based toggling with an
updateWsIndicator() helper that updates both the dot and its label
from stored element refs.

The indicator now lives in the topbar (via renderTopbar's extra slot)
instead of next to the competition name, with its own .ws-indicator
flex wrapper so the dot and label line up vertically.
2026-09-06 06:23:42 +02:00
2 changed files with 48 additions and 10 deletions
+26 -9
View File
@@ -1313,14 +1313,35 @@
// ---- Main render ------------------------------------------------------- // ---- Main render -------------------------------------------------------
function iconArrowLeft() {
const s = el("span", { class: "icon" });
s.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>';
return s;
}
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() { function render() {
clearNode(root); clearNode(root);
const backBtn = el("button", { class: "ghost", const backBtn = el("button", { class: "ghost btn-back",
onclick: () => { if (state.ws) { state.ws.close(); state.ws = null; } navigate("competitions"); } }, onclick: () => { if (state.ws) { state.ws.close(); state.ws = null; } navigate("competitions"); } },
"← " + t("back")); iconArrowLeft(), t("back"));
root.appendChild(renderTopbar(user, { extra: backBtn })); root.appendChild(renderTopbar(user, { extra: wsIndicator() }));
const container = el("div", { class: "container" }); const container = el("div", { class: "container" });
container.appendChild(backBtn);
container.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.5rem" } }, container.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.5rem" } },
el("h2", { style: { margin: 0 } }, el("h2", { style: { margin: 0 } },
state.competition.name, state.competition.name,
@@ -1329,10 +1350,6 @@
isClosed() ? " " : null, isClosed() ? " " : null,
isClosed() ? el("span", { class: "badge warn" }, t("closed")) : 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" }); const tabs = el("div", { class: "tabs" });
@@ -1393,8 +1410,8 @@
await loadAll(); await loadAll();
state.ws = openCompetitionWS(competitionId, { state.ws = openCompetitionWS(competitionId, {
onopen: () => { state.wsOnline = true; const e = document.querySelector(".connection-status"); if (e) { e.classList.add("online"); e.classList.remove("offline"); } }, onopen: () => { state.wsOnline = true; updateWsIndicator(); },
onclose: () => { state.wsOnline = false; const e = document.querySelector(".connection-status"); if (e) { e.classList.remove("online"); e.classList.add("offline"); } }, onclose: () => { state.wsOnline = false; updateWsIndicator(); },
onmessage: handleWSMessage, onmessage: handleWSMessage,
}); });
render(); render();
+22 -1
View File
@@ -387,11 +387,32 @@ tbody tr.transferred { background: #f3f6fb; }
height: 8px; height: 8px;
border-radius: 50%; border-radius: 50%;
background: #d1d5db; background: #d1d5db;
margin-right: 0.5rem; flex: none;
} }
.connection-status.online { background: #10b981; } .connection-status.online { background: #10b981; }
.connection-status.offline { background: #ef4444; } .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; }
.btn-back {
display: inline-flex;
align-items: center;
gap: 0.35rem;
margin-bottom: 0.75rem;
padding-left: 0.3rem;
}
.icon { display: inline-flex; }
.icon svg { display: block; }
textarea { resize: vertical; min-height: 60px; } textarea { resize: vertical; min-height: 60px; }
@media (max-width: 700px) { @media (max-width: 700px) {