add support for setting points, tokens, and broadcasting control msgs
Control messages are messages that are broadcast to all clients and have no clearly defined content. The idea is that this can be used to control a monitor without having to keep adding support for specific commands on the protocol layer. This also changes some of the existing messages and adds another ridiculous convenience layer to our HTML/JS templating: data-eval. We should probably just bite the bullet and use some reactive framework.
This commit is contained in:
parent
f406627042
commit
b00f8a357c
4 changed files with 242 additions and 65 deletions
175
public/buzzer.js
175
public/buzzer.js
|
|
@ -17,12 +17,50 @@ const buzzer_key = {
|
|||
name: "space bar",
|
||||
}
|
||||
|
||||
const q = (selector, root) => (root || document).querySelector(selector)
|
||||
const on = (event, cb) => document.addEventListener(event, cb)
|
||||
function isString(x) {
|
||||
return typeof x === "string"
|
||||
}
|
||||
|
||||
function isEmpty(x) {
|
||||
return (Array.isArray(x) ? x : Object.keys(x)).length === 0
|
||||
}
|
||||
|
||||
const q = (selector, root = document) => root.querySelector(selector)
|
||||
|
||||
const _evlisteners = {}
|
||||
/**
|
||||
* @param {String} event [description]
|
||||
* @param {Function} cb [description]
|
||||
* @param {?{once: bool, target: Element = document}} options
|
||||
*/
|
||||
function on(event, cb, options = {}) {
|
||||
const target = options.target || document
|
||||
if ((_evlisteners[target] ?? {})[event]) {
|
||||
console.warn(`Replacing previous event listener for '${event}' on target:`, target)
|
||||
off(event, target)
|
||||
}
|
||||
target.addEventListener(event, cb, { once: !!options.once })
|
||||
if (!_evlisteners[target]) {
|
||||
_evlisteners[target] = {}
|
||||
}
|
||||
_evlisteners[target][event] = cb
|
||||
}
|
||||
|
||||
function off(event, target = document) {
|
||||
if (!_evlisteners[target] || !_evlisteners[target][event]) {
|
||||
return
|
||||
}
|
||||
target.removeEventListener(event, _evlisteners[target][event])
|
||||
delete _evlisteners[target][event]
|
||||
if (isEmpty(_evlisteners[target])) {
|
||||
delete _evlisteners[target]
|
||||
}
|
||||
}
|
||||
|
||||
function node(type, { appendTo, cls, text, data, style, ...attrs } = {}) {
|
||||
let elem = typeof type === "string" ? document.createElement(type) : type
|
||||
let elem = isString(type) ? document.createElement(type) : type
|
||||
if (cls) {
|
||||
if (typeof cls === "string") {
|
||||
if (isString(cls)) {
|
||||
elem.className = cls
|
||||
} else {
|
||||
for (const [name, on] of Object.entries(cls)) {
|
||||
|
|
@ -63,15 +101,19 @@ let socket,
|
|||
session_key
|
||||
|
||||
function hide(e) {
|
||||
e = typeof e === "string" ? q(`#${e}`) : e
|
||||
e = isString(e) ? q(`#${e}`) : e
|
||||
e.style.display = "none"
|
||||
}
|
||||
|
||||
function show(e) {
|
||||
e = typeof e === "string" ? q(`#${e}`) : e
|
||||
e = isString(e) ? q(`#${e}`) : e
|
||||
e.style.display = "block"
|
||||
}
|
||||
|
||||
function find_client(client_id) {
|
||||
return clients.find((c) => c.id === client_id)
|
||||
}
|
||||
|
||||
function session_id() {
|
||||
const match = /^#?(.+)/.exec(document.location.hash)
|
||||
return match ? match[1] : null
|
||||
|
|
@ -103,23 +145,25 @@ function clear(container) {
|
|||
}
|
||||
}
|
||||
|
||||
let ul
|
||||
const player_list = q("#info ul")
|
||||
function redraw_clients(me, clients) {
|
||||
if (!me) {
|
||||
return
|
||||
}
|
||||
clear(ul)
|
||||
clear(player_list)
|
||||
const player_tpl = q("template#player").content.firstElementChild
|
||||
for (const c of clients) {
|
||||
node(player_tpl.cloneNode(), {
|
||||
text: c.name,
|
||||
const player_el = node(player_tpl.cloneNode(true), {
|
||||
data: { cid: c.id },
|
||||
appendTo: ul,
|
||||
appendTo: player_list,
|
||||
cls: {
|
||||
me: c.id === me.id,
|
||||
inactive: !c.active,
|
||||
},
|
||||
})
|
||||
q(".name", player_el).textContent = c.name
|
||||
q(".points", player_el).textContent = c.points
|
||||
q(".tokens", player_el).textContent = c.tokens
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +177,10 @@ function highlight(client_id, until_ns) {
|
|||
console.warn("That highlight timeout was ridiculously low:", client_id, timeout_ms)
|
||||
return
|
||||
}
|
||||
for (const li of ul.children) {
|
||||
for (const li of player_list.children) {
|
||||
if (li.dataset.cid === client_id) {
|
||||
li.classList.add("buzzing")
|
||||
if (Object.keys(highlights).length) {
|
||||
if (!isEmpty(highlights)) {
|
||||
li.classList.add("too-late")
|
||||
} else {
|
||||
li.classList.add("first")
|
||||
|
|
@ -200,11 +244,61 @@ function setup_ui() {
|
|||
if (storage["my_name"]) {
|
||||
username_el.value = storage["my_name"]
|
||||
}
|
||||
username_el.addEventListener("change", (event) => {
|
||||
set_name(event.target.value)
|
||||
})
|
||||
on(
|
||||
"change",
|
||||
(event) => {
|
||||
set_name(event.target.value)
|
||||
},
|
||||
{ target: username_el },
|
||||
)
|
||||
}
|
||||
|
||||
ul = q("#info ul")
|
||||
function disable_player_ui() {
|
||||
off("focus")
|
||||
off("blur")
|
||||
off("keydown")
|
||||
off("keyup")
|
||||
node(q("body"), { cls: { player: 0 } })
|
||||
}
|
||||
|
||||
function enable_admin_ui() {
|
||||
const body = q("body")
|
||||
if (body.classList.contains("admin")) {
|
||||
return
|
||||
}
|
||||
body.classList.add("admin")
|
||||
|
||||
const named = Object.fromEntries(
|
||||
Array.from(document.querySelectorAll("[name]")).map((el) => [el.name, el]),
|
||||
)
|
||||
on(
|
||||
"click",
|
||||
({ target }) => {
|
||||
if (!target.dataset.eval) {
|
||||
return
|
||||
}
|
||||
eval(target.dataset.eval)
|
||||
},
|
||||
{ target: q("#points-admin") },
|
||||
)
|
||||
on(
|
||||
"click",
|
||||
({ target }) => {
|
||||
let node = target
|
||||
while (!node.dataset.cid && node.parentElement) {
|
||||
node = node.parentElement
|
||||
}
|
||||
if (!target.dataset.eval) {
|
||||
return
|
||||
}
|
||||
const client = find_client(node.dataset.cid)
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
eval(target.dataset.eval)
|
||||
},
|
||||
{ target: q("#info .players") },
|
||||
)
|
||||
}
|
||||
|
||||
function set_name(name) {
|
||||
|
|
@ -234,44 +328,45 @@ function setup_ws() {
|
|||
})
|
||||
socket.addEventListener("message", function (event) {
|
||||
const msg = JSON.parse(event.data)
|
||||
if (msg.type === "time") {
|
||||
servertime = msg.value
|
||||
const { type, value } = msg
|
||||
if (type === "time") {
|
||||
servertime = value.time
|
||||
toffset_ms = performance.now()
|
||||
} else if (msg.type === "id") {
|
||||
me = { id: msg.id, key: msg.key, path: msg.path }
|
||||
} else if (type === "id") {
|
||||
me = value
|
||||
storage["my_uid"] = me.id
|
||||
storage["my_key"] = me.key
|
||||
storage["my_sid"] = session_id_from_url(me.path)
|
||||
redraw_clients(me, clients)
|
||||
} else if (msg.type === "session_key") {
|
||||
session_key = { path: msg.path, key: msg.key }
|
||||
} else if (type === "session_key") {
|
||||
session_key = value
|
||||
storage["session_path"] = session_key.path
|
||||
storage["session_key"] = session_key.key
|
||||
} else if (msg.type === "buzz") {
|
||||
const buzztime_ns = msg.time
|
||||
const client_id = msg.client
|
||||
disable_player_ui()
|
||||
enable_admin_ui()
|
||||
} else if (type === "buzz") {
|
||||
const buzztime_ns = value.time
|
||||
const client_id = value.client
|
||||
const duration_ns = 3 * s_ns
|
||||
const until_ns = buzztime_ns + duration_ns
|
||||
highlight(client_id, until_ns)
|
||||
} else if (msg.type === "clients") {
|
||||
clients = msg.value
|
||||
} else if (type === "clients") {
|
||||
clients = value.clients
|
||||
redraw_clients(me, clients)
|
||||
} else if (msg.type === "client") {
|
||||
const client = { name: msg.name, id: msg.id, active: msg.active }
|
||||
for (const c of clients) {
|
||||
if (c.id === client.id) {
|
||||
c.name = client.name
|
||||
redraw_clients(me, clients)
|
||||
return
|
||||
}
|
||||
} else if (type === "client") {
|
||||
const client = value.client
|
||||
const idx = clients.findIndex((c) => c.id === client.id)
|
||||
if (idx >= 0) {
|
||||
clients[idx] = client
|
||||
} else {
|
||||
clients.push(client)
|
||||
}
|
||||
clients.push(client)
|
||||
redraw_clients(me, clients)
|
||||
} else if (msg.type === "error") {
|
||||
console.error(`Error: ${msg.reason}`)
|
||||
} else if (type === "error") {
|
||||
console.error(`Error: ${value.reason}`)
|
||||
const errorbox = q("#error")
|
||||
q("code", errorbox).textContent = JSON.stringify(msg, null, 2)
|
||||
show(errorbox)
|
||||
q("body").classList.add("error")
|
||||
} else {
|
||||
console.error(`Unknown message: ${event.data}`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue