quiz/public/monitor.js

171 lines
4.4 KiB
JavaScript
Raw Normal View History

"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"
2021-02-02 23:05:00 +01:00
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()
2021-02-02 21:43:58 +01:00
const player_list = q("#players")
function _redraw_clients(clients, monitored) {
2021-02-02 23:05:00 +01:00
// Remove all gone players.
for (const el of player_list.querySelectorAll(".player")) {
if (!monitored.includes(el.dataset.cid)) {
2021-02-02 23:05:00 +01:00
el.remove()
}
}
2021-02-02 19:56:48 +01:00
const player_tpl = q("template#player").content.firstElementChild
for (const cid of monitored) {
const c = clients[cid] ?? {id: cid, name:'', points:0, tokens:0}
2021-02-02 23:05:00 +01:00
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 {
2021-02-02 23:05:00 +01:00
player_el = node(player_tpl.cloneNode(true), {
data: { cid: c.id },
appendTo: player_list,
})
}
if (q(".points", player_el).textContent !== prettynum(c.points)) {
2021-02-02 23:05:00 +01:00
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)
}
2021-02-02 19:56:48 +01:00
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)
}
}
2021-02-02 23:05:00 +01:00
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()
const overlay = q('#text-overlay')
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, ...args}} }) => {
if (action === 'monitor' && Array.isArray(args.targets)) {
monitored = args.targets
redraw_clients(clients, monitored)
} else if (action === 'text' && args.text !== undefined) {
if (args.text.length) {
overlay.textContent = args.text
overlay.classList.remove('hidden')
} else {
overlay.classList.add('hidden')
}
}
})
conn.connect(session_url(sid))
}
setup_url()
setup_ws()