add original version of old crummy monitor

This commit is contained in:
ducklet 2021-02-02 00:42:02 +01:00
parent b00f8a357c
commit ab7f6014a4
7 changed files with 368 additions and 114 deletions

View file

@ -42,8 +42,7 @@
<li class="player" data-cid="client ID">
<span class="name">player name</span>
<div class="admin-only">
(<span class="points">points</span> pts)
(<span class="tokens">tokens</span> tks)
(<span class="points">points</span> pts) (<span class="tokens">tokens</span> tks)
<button data-eval="send('control', {target: client.id, action: 'monitor'})">
monitor
</button>

View file

@ -10,89 +10,27 @@ const storage = window.sessionStorage
// - measure/report latency
// - use server reported time to find winner
import config from "./config.js"
import {
clear,
isEmpty,
isString,
ms_ns,
node,
off,
on,
q,
s_ns,
servertime_now_ns,
session_id,
session_id_from_url,
session_url,
} from "./shared.js"
const buzzer_key = {
code: 0x20,
name: "space bar",
}
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 = isString(type) ? document.createElement(type) : type
if (cls) {
if (isString(cls)) {
elem.className = cls
} else {
for (const [name, on] of Object.entries(cls)) {
if (on) {
elem.classList.add(name)
} else {
elem.classList.remove(name)
}
}
}
}
if (text) {
elem.textContent = text
}
Object.assign(elem.dataset, data ?? {})
Object.assign(elem.style, style ?? {})
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,
@ -114,11 +52,6 @@ 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
}
function new_session_id() {
if (!crypto) {
return Math.random().toString(36).substr(2)
@ -130,7 +63,7 @@ function new_session_id() {
function setup_url() {
const sid = session_id() || new_session_id()
document.location.hash = sid
location.hash = sid
}
function send(type, value) {
@ -138,13 +71,6 @@ function send(type, value) {
socket.send(JSON.stringify({ type, value }))
}
function clear(container) {
while (container.children.length > 0) {
const child = container.children[0]
child.remove()
}
}
const player_list = q("#info ul")
function redraw_clients(me, clients) {
if (!me) {
@ -196,15 +122,6 @@ function highlight(client_id, until_ns) {
}
}
/**
* 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")
@ -306,20 +223,10 @@ function set_name(name) {
send("name", name)
}
function session_url(sid) {
return `${config.wsurl}/${sid}`
}
function session_id_from_url(url) {
const wsurl = new URL(config.wsurl)
const match = RegExp(`${wsurl.pathname}/([^/]+)$`).exec(url)
return !match ? null : match[1]
}
function setup_ws() {
const sid = session_id()
const credentials = { id: storage["my_uid"], key: storage["my_key"] }
socket = new WebSocket(`${session_url(sid)}`)
socket = new WebSocket(session_url(sid))
socket.addEventListener("open", function (event) {
if (sid === storage["my_sid"]) {
send("login", credentials)
@ -362,6 +269,8 @@ function setup_ws() {
clients.push(client)
}
redraw_clients(me, clients)
} else if (type === "control") {
// ignore
} else if (type === "error") {
console.error(`Error: ${value.reason}`)
const errorbox = q("#error")

60
public/monitor.css Normal file
View file

@ -0,0 +1,60 @@
body {
font-family: "Arial Rounded MT Bold", sans-serif;
background-color: black;
margin: 0px auto;
overflow: hidden;
}
* {
margin: 0;
padding: 0;
}
/*
# colors
citrus: c2d72f
bleue: 0093ff
*/
#info {
background-color: #c2d72f;
padding: 1em 0 0 1em;
width: 1280px;
height: 720px;
outline: 10px solid gold;
}
.player {
width: 336px;
display: flex;
flex-direction: column;
position: relative;
}
.player + .player {
margin-top: 1.5em;
}
.box {
background-color: #0f0;
height: 189px;
outline: 0.3em solid grey;
-moz-outline-radius: 1em;
}
.points {
position: absolute;
top: 1em;
right: 1em;
background-color: #c2d72f;
width: 3em;
padding: 0.2em;
text-align: right;
border-radius: 0.5em;
box-shadow: 0 0 0.3em #c2d72f, 0 0 0.8em #c2d72f, 0 0 1em #c2d72f, 0 0 1.2em #c2d72f;
}
.player.buzzing .box {
outline: 0.7em solid #0093ff;
}
.name {
margin-top: 0.5em;
text-align: center;
letter-spacing: 0.3em;
font-variant: small-caps;
/*font-weight: bold;*/
}
/*1280 x 720*/
/*1920 x 1080*/

24
public/monitor.html Normal file
View file

@ -0,0 +1,24 @@
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="monitor.css" />
<body>
<div id="info">
<div class="player">
<div class="points">-36'000</div>
<div class="box"></div>
<div class="name">Player 1</div>
</div>
<div class="player buzzing">
<div class="points">0</div>
<div class="box"></div>
<div class="name">Player 2</div>
</div>
<div class="player">
<div class="points">900</div>
<div class="box"></div>
<div class="name">Player 3</div>
</div>
</div>
</body>
<script type="module" src="./monitor.js"></script>

149
public/monitor.js Normal file
View file

@ -0,0 +1,149 @@
"use strict"
/* global document, window */
const location = document.location
const performance = window.performance
import {
clear,
ms_ns,
node,
q,
s_ns,
servertime_now_ns,
session_id,
session_url,
} from "./shared.js"
let socket,
servertime,
toffset_ms,
clients = {},
me
function setup_url() {
const sid = session_id() || ""
location.hash = sid
}
function send(type, value) {
// console.debug('sending', value)
socket.send(JSON.stringify({ type, value }))
}
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("#info")
function redraw_clients(me, clients) {
if (!me) {
return
}
clear(player_list)
for (const c of Object.values(clients)) {
if (c.id === me.id) {
continue
}
const player = node("div", { data: { cid: c.id }, cls: "player" })
node("div", { cls: "points", text: prettynum(c.points), appendTo: player })
node("div", { cls: "box", appendTo: player })
node("div", { cls: "name", text: c.name, appendTo: player })
player_list.appendChild(player)
}
}
let highlighted = false
function highlight(client_id, until_ns) {
if (highlighted) {
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 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() {
const sid = session_id()
socket = new WebSocket(session_url(sid))
socket.addEventListener("open", function (event) {
send("name", "Monitor")
})
socket.addEventListener("message", function (event) {
const msg = JSON.parse(event.data)
const { type, value } = msg
if (msg.type === "time") {
servertime = value.time
toffset_ms = performance.now()
} else if (msg.type === "id") {
me = value
redraw_clients(me, clients)
} else if (msg.type === "buzz") {
const buzztime_ns = value.time
const client_id = value.client
const duration_ns = 12 * s_ns
const until_ns = buzztime_ns + duration_ns
highlight(client_id, until_ns)
} else if (msg.type === "clients") {
clients = Object.fromEntries(value.clients.map((c) => [c.id, c]))
redraw_clients(me, clients)
} else if (msg.type === "client") {
const client = value.client
clients[client.id] = client
redraw_clients(me, clients)
} else if (type === "session_key") {
// ignore
} else if (type === "error") {
console.error(`Error: ${value.reason}`)
} else {
console.error(`Unknown message: ${event.data}`)
}
})
}
setup_url()
setup_ws()

115
public/shared.js Normal file
View file

@ -0,0 +1,115 @@
"use strict"
/* global document, window */
const location = document.location
const performance = window.performance
import config from "./config.js"
export function isString(x) {
return typeof x === "string"
}
export function isEmpty(x) {
return (Array.isArray(x) ? x : Object.keys(x)).length === 0
}
export 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
*/
export 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
}
export 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]
}
}
export function node(type, { appendTo, cls, text, data, style, ...attrs } = {}) {
let elem = isString(type) ? document.createElement(type) : type
if (cls) {
if (isString(cls)) {
elem.className = cls
} else {
for (const [name, on] of Object.entries(cls)) {
if (on) {
elem.classList.add(name)
} else {
elem.classList.remove(name)
}
}
}
}
if (text) {
elem.textContent = text
}
Object.assign(elem.dataset, data ?? {})
Object.assign(elem.style, style ?? {})
for (const name in attrs) {
elem.setAttribute(name, attrs[name])
}
if (appendTo) {
elem = appendTo.appendChild(elem)
}
return elem
}
/**
* Some duration conversion constants.
*/
export const ms_ns = 1_000_000 // nanoseconds in a millisecond
export const s_ms = 1_000 // milliseconds in a second
export const s_ns = 1_000_000_000 // nanoseconds in a second
export function session_url(sid) {
return `${config.wsurl}/${sid}`
}
export function session_id_from_url(url) {
const wsurl = new URL(config.wsurl)
const match = RegExp(`${wsurl.pathname}/([^/]+)$`).exec(url)
return !match ? null : match[1]
}
export function clear(container) {
while (container.children.length > 0) {
const child = container.children[0]
child.remove()
}
}
export function session_id() {
const match = /^#?(.+)/.exec(location.hash)
return match ? match[1] : null
}
/**
* Guess the exact current server time.
*/
export function servertime_now_ns() {
const now_ms = performance.now()
const delta_ns = ms_ns * (now_ms - toffset_ms)
return servertime + delta_ns
}

View file

@ -104,5 +104,3 @@ class Serve:
__iter__ = __await__
serve = Serve
WebSocketServerProtocol