init with crummy buzzer & docker setup
so much boilerplate :O
This commit is contained in:
commit
f6bf544f54
23 changed files with 746 additions and 0 deletions
50
public/buzzer.css
Normal file
50
public/buzzer.css
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
input {
|
||||
width: 20em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1em;
|
||||
}
|
||||
ul {
|
||||
/*list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;*/
|
||||
}
|
||||
li.me::after {
|
||||
content: " — that's you!";
|
||||
font-style: italic;
|
||||
}
|
||||
li.buzzing {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: 2em;
|
||||
list-style-type: none;
|
||||
}
|
||||
li.buzzing.first::before {
|
||||
content: "🥇 ";
|
||||
}
|
||||
li.buzzing.too-late {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
li.buzzing.too-late::before {
|
||||
content: "🥈 ";
|
||||
}
|
||||
#info {
|
||||
position: absolute;
|
||||
}
|
||||
#buzzbox {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 90%;
|
||||
/*position: absolute;*/
|
||||
}
|
||||
#active {
|
||||
color: red;
|
||||
display: none;
|
||||
}
|
||||
23
public/buzzer.html
Normal file
23
public/buzzer.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="buzzer.css" />
|
||||
<body>
|
||||
<div id="info">
|
||||
<label
|
||||
>You:
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
id="username"
|
||||
placeholder="Please put your name here ..."
|
||||
/></label>
|
||||
<h2>All Players</h2>
|
||||
<ul></ul>
|
||||
</div>
|
||||
<div id="buzzbox">
|
||||
<p id="active">BZZZZZ!</p>
|
||||
<p id="ready">Press <strong id="bkey">{key.name}</strong> to activate buzzer.</p>
|
||||
<p id="inactive">Please focus the window to allow the buzzer to work.</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="module" src="./buzzer.js"></script>
|
||||
226
public/buzzer.js
Normal file
226
public/buzzer.js
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
"use strict"
|
||||
|
||||
// TODOs
|
||||
// - measure/report latency
|
||||
// - use server reported time to find winner
|
||||
|
||||
import config from "./config.js"
|
||||
|
||||
const buzzer_key = {
|
||||
code: 0x20,
|
||||
name: "space bar",
|
||||
}
|
||||
|
||||
const q = (selector, root) => (root || document).querySelector(selector)
|
||||
const on = (event, cb) => document.addEventListener(event, cb)
|
||||
function node(type, { appendTo, cls, text, data, ...attrs } = {}) {
|
||||
let elem = document.createElement(type)
|
||||
if (cls) {
|
||||
elem.className = cls
|
||||
}
|
||||
if (text) {
|
||||
elem.textContent = text
|
||||
}
|
||||
for (const name in data ?? {}) {
|
||||
elem.dataset[name] = data[name]
|
||||
}
|
||||
for (const name in attrs) {
|
||||
elem.setAttribute(name, attrs[name])
|
||||
}
|
||||
if (appendTo) {
|
||||
elem = appendTo.appendChild(elem)
|
||||
}
|
||||
return elem
|
||||
}
|
||||
|
||||
/**
|
||||
* Some duration conversion constants.
|
||||
*/
|
||||
const ms_ns = 1_000_000 // nanoseconds in a millisecond
|
||||
const s_ms = 1_000 // milliseconds in a second
|
||||
const s_ns = 1_000_000_000 // nanoseconds in a second
|
||||
|
||||
let socket,
|
||||
servertime,
|
||||
toffset_ms,
|
||||
clients = [],
|
||||
me
|
||||
|
||||
function hide(e) {
|
||||
q(`#${e}`).style.display = "none"
|
||||
}
|
||||
|
||||
function show(e) {
|
||||
q(`#${e}`).style.display = "block"
|
||||
}
|
||||
|
||||
function session_id() {
|
||||
const match = /^#?(.+)/.exec(document.location.hash)
|
||||
return match ? match[1] : null
|
||||
}
|
||||
|
||||
function new_session_id() {
|
||||
if (!window.crypto) {
|
||||
return Math.random().toString(36).substr(2)
|
||||
}
|
||||
const data = new Uint8Array(10)
|
||||
crypto.getRandomValues(data)
|
||||
return Array.from(data, (v) => v.toString(36)).join("")
|
||||
}
|
||||
|
||||
function setup_url() {
|
||||
const sid = session_id() || new_session_id()
|
||||
document.location.hash = sid
|
||||
}
|
||||
|
||||
function send(type, value) {
|
||||
// console.debug('sending', value)
|
||||
socket.send(JSON.stringify({ type, value }))
|
||||
}
|
||||
|
||||
function clear(container) {
|
||||
while (container.children.length > 0) {
|
||||
const child = container.children[0]
|
||||
child.remove()
|
||||
}
|
||||
}
|
||||
|
||||
let ul
|
||||
function redraw_clients(me, clients) {
|
||||
if (!me) {
|
||||
return
|
||||
}
|
||||
clear(ul)
|
||||
for (const c of clients) {
|
||||
node("li", {
|
||||
text: c.name,
|
||||
data: { cid: c.id },
|
||||
appendTo: ul,
|
||||
cls: c.id === me ? "me" : "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const highlights = {}
|
||||
function highlight(client_id, until_ns) {
|
||||
if (highlights[client_id]) {
|
||||
return
|
||||
}
|
||||
const timeout_ms = (until_ns - 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 ul.children) {
|
||||
if (li.dataset.cid === client_id) {
|
||||
li.classList.add("buzzing")
|
||||
if (Object.keys(highlights).length) {
|
||||
li.classList.add("too-late")
|
||||
} else {
|
||||
li.classList.add("first")
|
||||
}
|
||||
highlights[client_id] = setTimeout(() => {
|
||||
delete highlights[client_id]
|
||||
li.classList.remove("buzzing")
|
||||
li.classList.remove("too-late")
|
||||
li.classList.remove("first")
|
||||
}, timeout_ms)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the exact current server time.
|
||||
*/
|
||||
function servertime_now_ns() {
|
||||
const now_ms = performance.now()
|
||||
const delta_ns = ms_ns * (now_ms - toffset_ms)
|
||||
return servertime + delta_ns
|
||||
}
|
||||
|
||||
function setup_ui() {
|
||||
on("focus", (event) => {
|
||||
hide("active")
|
||||
hide("inactive")
|
||||
show("ready")
|
||||
})
|
||||
on("blur", (event) => {
|
||||
hide("active")
|
||||
show("inactive")
|
||||
hide("ready")
|
||||
})
|
||||
if (document.hasFocus()) {
|
||||
hide("inactive")
|
||||
} else {
|
||||
hide("ready")
|
||||
}
|
||||
|
||||
q("#bkey").textContent = buzzer_key.name
|
||||
let buzzing = false
|
||||
on("keydown", (event) => {
|
||||
if (!buzzing && event.keyCode === buzzer_key.code) {
|
||||
buzzing = true
|
||||
send("buzz", servertime_now_ns())
|
||||
show("active")
|
||||
hide("ready")
|
||||
}
|
||||
})
|
||||
on("keyup", (event) => {
|
||||
if (event.keyCode === buzzer_key.code) {
|
||||
buzzing = false
|
||||
hide("active")
|
||||
show("ready")
|
||||
}
|
||||
})
|
||||
|
||||
q("#username").addEventListener("change", (event) => {
|
||||
send("name", event.target.value)
|
||||
})
|
||||
|
||||
ul = q("#info ul")
|
||||
}
|
||||
|
||||
function setup_ws() {
|
||||
const sid = session_id()
|
||||
socket = new WebSocket(`${config.wsurl}/quiz/${sid}`)
|
||||
socket.addEventListener("open", function (event) {
|
||||
send("name", q("#username").value)
|
||||
})
|
||||
socket.addEventListener("message", function (event) {
|
||||
const msg = JSON.parse(event.data)
|
||||
if (msg.type === "time") {
|
||||
servertime = msg.value
|
||||
toffset_ms = performance.now()
|
||||
} else if (msg.type === "id") {
|
||||
me = msg.value
|
||||
redraw_clients(me, clients)
|
||||
} else if (msg.type === "buzz") {
|
||||
const buzztime_ns = msg.time
|
||||
const client_id = msg.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
|
||||
redraw_clients(me, clients)
|
||||
} else if (msg.type === "client") {
|
||||
const client = msg.value
|
||||
for (const c of clients) {
|
||||
if (c.id === client.id) {
|
||||
c.name = client.name
|
||||
redraw_clients(me, clients)
|
||||
return
|
||||
}
|
||||
}
|
||||
clients.push(client)
|
||||
redraw_clients(me, clients)
|
||||
} else {
|
||||
console.error(`Unknown message: ${event.data}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setup_url()
|
||||
setup_ui()
|
||||
setup_ws()
|
||||
3
public/config.js
Normal file
3
public/config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
wsurl: "wss://quiz.dumpr.org:443",
|
||||
}
|
||||
7
public/index.html
Normal file
7
public/index.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<ul>
|
||||
<li><a href="admin.html">admin</a></li>
|
||||
<li><a href="buzzer.html">buzzer</a></li>
|
||||
<li><a href="matrix.html">matrix</a></li>
|
||||
<li><a href="monitor.html">monitor</a></li>
|
||||
</ul>
|
||||
Loading…
Add table
Add a link
Reference in a new issue