"use strict" /* global document, window */ const location = document.location const performance = window.performance import { Connection, ms_ns, node, q, s_ns, session_id, session_url } from "./shared.js" let conn function setup_url() { const sid = session_id() || "" location.hash = sid } function prettynum(n) { let i = Math.abs(n) | 0 let s = [] while (true) { const m = i % 1000 i = (i / 1000) | 0 if (i === 0) { s.unshift(String(m)) break } else { s.unshift(String(m).padStart(3, "0")) } } return (n < 0 ? "-" : "") + s.join("'") } function assert(expected, got) { if (expected !== got) { throw Error("Assertion failed.") } } function test_prettynum() { assert("0", prettynum(0)) assert("1", prettynum(1)) assert("1'000", prettynum(1000)) assert("36'000", prettynum(36000)) assert("0", prettynum(-0)) assert("-1", prettynum(-1)) assert("-1'000", prettynum(-1000)) assert("-36'000", prettynum(-36000)) assert("-36'600", prettynum(-36600)) } test_prettynum() const player_list = q("#players") function _redraw_clients(clients, monitored) { // Remove all gone players. for (const el of player_list.querySelectorAll(".player")) { if (!monitored.includes(el.dataset.cid)) { el.remove() } } const player_tpl = q("template#player").content.firstElementChild for (const cid of monitored) { const c = clients[cid] ?? {id: cid, name:'', points:0, tokens:0} let player_el = q(`.player[data-cid='${c.id}']`) if (player_el) { // Ensure the element is at the correct position. player_list.appendChild(player_el) } else { player_el = node(player_tpl.cloneNode(true), { data: { cid: c.id }, appendTo: player_list, }) } if (q(".points", player_el).textContent !== prettynum(c.points)) { q(".points.fg", player_el).classList.add("big") q(".points.bg", player_el).classList.add("big") setTimeout(() => { q(".points.fg", player_el).classList.remove("big") q(".points.bg", player_el).classList.remove("big") }, 200) } q(".name", player_el).textContent = c.name q(".points.fg", player_el).textContent = prettynum(c.points) q(".points.bg", player_el).textContent = prettynum(c.points) q(".tokens", player_el).textContent = "🔴 ".repeat(c.tokens) } } function debounce(func, ms) { let timer return function (...args) { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { timer = null func(...args) }, ms) } } const redraw_clients = debounce(_redraw_clients, 200) let highlighted = false function highlight(client_id, until_ns) { if (highlighted) { return } const timeout_ms = (until_ns - conn.servertime_now_ns()) / ms_ns if (timeout_ms <= 10) { console.warn("That highlight timeout was ridiculously low:", client_id, timeout_ms) return } for (const li of player_list.children) { if (li.dataset.cid === client_id) { li.classList.add("buzzing") highlighted = true setTimeout(() => { highlighted = false li.classList.remove("buzzing") }, timeout_ms) return } } } function setup_ws() { let clients = {}, me, monitored = [] const sid = session_id() conn = new Connection() conn.on("helo", () => { conn.send("name", "Monitor") }) conn.on("id", ({ value }) => { me = value }) conn.on("buzz", ({ value }) => { const { time: buzztime_ns, client: client_id } = value const duration_ns = 12 * s_ns const until_ns = buzztime_ns + duration_ns highlight(client_id, until_ns) }) conn.on("clients", ({ value }) => { clients = Object.fromEntries(value.clients.map((c) => [c.id, c])) redraw_clients(clients, monitored) }) conn.on("client", ({ value: { client } }) => { clients[client.id] = client redraw_clients(clients, monitored) }) conn.on("control", ({ value: {payload: {action, targets}} }) => { if (action === 'monitor' && Array.isArray(targets)) { monitored = targets redraw_clients(clients, monitored) } }) conn.connect(session_url(sid)) } setup_url() setup_ws()