type Times = number[]; type Cats = [string, Times] const MATRIX: Cats[] = [["o0", [1, 5, 8, 9, 11]], ["o1", [2, 5, 6, 10, 11]], ["o2", [3, 6, 7, 9, 11]], ["o3", [4, 7, 8, 10, 11]]]; const TEMPLATE = `
`; type Face = "hours" | "minutes" | "seconds" const isFace = (v: unknown): v is Face => { if (typeof v === "string" && ["hours", "minutes", "seconds"].includes(v)) { return true; } throw new Error(`Expected a Face, got ${v}`); } class DominoClock extends HTMLElement { elements: { [K in Face]: HTMLDivElement }; timer: number; constructor() { super() this.timer = 0; this.innerHTML = TEMPLATE; this.elements = Object.fromEntries( Array.from(this.getElementsByTagName('div')) .filter((element) => element.className === "face") .map((element) => [isFace(element.id) && element.id, element])); this.paint = this.paint.bind(this); } connectedCallback() { this.timer = window.setTimeout(this.paint, 250) } tock(board: Face, time: number) { const dots = Array.from(this.elements[board].getElementsByTagName('div')); dots.forEach((element) => element.style.opacity = "0"); dots.forEach((element) => { const row = MATRIX.find((i) => Array.from(element.classList).includes(i[0])); if (!row) { console.log(`Didn't find a row for ${element.className}?`); return; } if (row[1].includes(time)) { element.style.opacity = "1" } }); } paint() { const now = new Date(); const rawHours = now.getHours(); const hours = rawHours > 12 ? rawHours - 12 : rawHours; this.tock("hours", hours); this.tock("minutes", Math.floor(now.getUTCMinutes() / 5)); this.tock("seconds", Math.floor(now.getUTCSeconds() / 5)); window.clearTimeout(this.timer); this.timer = window.setTimeout(this.paint, 250); } } customElements.define('domino-clock', DominoClock);