Compare commits

..
3 Commits
Author SHA1 Message Date
CodingPhoenix ddaeccbea9 Nudge the WS status dot up 1px to line up with its label
top: 1px pushed the dot the wrong way; the label's glyphs sit slightly
above the flex centre line, so the dot needs to move up, not down, to
match.
2026-09-06 06:26:18 +02:00
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 49 additions and 10 deletions
+26 -9
View File
@@ -1313,14 +1313,35 @@
// ---- 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() {
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"); } },
"← " + t("back"));
root.appendChild(renderTopbar(user, { extra: backBtn }));
iconArrowLeft(), t("back"));
root.appendChild(renderTopbar(user, { extra: wsIndicator() }));
const container = el("div", { class: "container" });
container.appendChild(backBtn);
container.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.5rem" } },
el("h2", { style: { margin: 0 } },
state.competition.name,
@@ -1329,10 +1350,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 +1410,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();
+23 -1
View File
@@ -387,11 +387,33 @@ 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;
}
/* Nudge the dot up ~1px: the label (Online/Offline) has unused descender
space, so its glyphs sit above the flex centre line while the dot is
dead-centred — lifting the dot lines the two up optically. */
.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; }
@media (max-width: 700px) {