From 84eeb1bb818302460b9d12a3d54394458f3b21d3 Mon Sep 17 00:00:00 2001 From: Interitio Date: Tue, 2 Sep 2025 22:28:58 +1000 Subject: [PATCH] Initial timer --- index.html | 22 +++++++ timer.css | 35 ++++++++++ timer.js | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 index.html create mode 100644 timer.css create mode 100644 timer.js diff --git a/index.html b/index.html new file mode 100644 index 0000000..f3b27a0 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + + Croccy Timer + + +
+
+
+ 00:00 +
+
+
+
+
+
+ + diff --git a/timer.css b/timer.css new file mode 100644 index 0000000..961c19f --- /dev/null +++ b/timer.css @@ -0,0 +1,35 @@ +:root { + --break-color: #9020BA; + --focus-color: #EED59F; + --stage-color: var(--break-color); + --clip-path: none; +} + + +.timer-box{ + height: 175px; + width: 397px; + padding: 5px; +} + + +.timer-time { + font-size: 100px; + font-family: Inter; + font-weight: 400; + letter-spacing: 0%; + text-align: center; + color: #453960; +} +.timer-details { + display: fill; + font-family: Inter; + font-weight: 600; + font-size: 16px; + justify-content: space-between; + align-items: center; + text-align: center; + width: 65%; + margin: auto; + color: #453960; +} diff --git a/timer.js b/timer.js new file mode 100644 index 0000000..31ca2da --- /dev/null +++ b/timer.js @@ -0,0 +1,189 @@ +window.addEventListener("DOMContentLoaded", () => { + const websocket = new WebSocket("ws://adamwalsh.name:9533"); + + websocket.addEventListener("open", () => { + communicate(websocket); + }); + +}); + + +class Timer { + constructor(renderer, end_time, running) { + this.renderer = renderer; + this.end_time = end_time; + this.running = running; + + this.destroying = false; + } + + render_time() { + // Render timer status to the document + var timestr; + + if (this.counter < 0) { + timestr = "--:--"; + } else { + // How many seconds have passed in this block + var dur_seconds = Math.floor( (this.end_time - new Date()) / 1000); + var seconds = dur_seconds % 60; + dur_seconds = dur_seconds - seconds; + var minutes = Math.floor(dur_seconds / 60); + var hours = Math.floor(minutes / 60); + minutes = minutes - 60 * hours; + + timestr = String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0'); + } + + if (this.running) { + this.renderer.update_time(timestr); + } + } + + tick() { + // Recursive call run per second to update and change stage + + if (this.destroying) { + return + } + this.render_time(); + setTimeout(this.tick.bind(this), 1000); + } +} + +class TimerRenderer { + constructor (){ + this.background = document.getElementsByClassName("timer-bg")[0]; + this.time_element = document.getElementsByClassName("timer-time")[0]; + + this.bg_width = parseInt( + window.getComputedStyle( + this.background, null + ).getPropertyValue('width'), + 10 + ); + this.bg_height = parseInt( + window.getComputedStyle( + this.background, null + ).getPropertyValue('height'), + 10 + ) + } + + update_time (timestr) { + this.time_element.textContent = timestr; + } + + set_break_color () { + this.background.style.setProperty('--stage-color', 'var(--break-color)'); + this.time_element.style.color = "var(--break-color)"; + } + + set_focus_color () { + this.background.style.setProperty('--stage-color', 'var(--focus-color)'); + this.time_element.style.color = "var(--focus-color)"; + } + +} + +function rectBounds(prop, rectHeight, rectWidth) { + var totalCircum = 2 * rectHeight + 2 * rectWidth; + var propCircum = prop * totalCircum; + let bounds; + + if (propCircum < rectWidth / 2) { + var xprop = Math.floor(( + (rectWidth/2 + propCircum) / rectWidth + ) * 1000)/10; + bounds = [ + "50% -5%", + `${xprop}% -5%`, + `${xprop}% 4%`, + "50% 4%" + ]; + } else if (propCircum < rectWidth / 2 + rectHeight) { + var yprop = ( + (propCircum - rectWidth / 2) / rectHeight + ) * 100; + bounds = [ + "50% -5%", "110% -5%", + `110% ${yprop}%`, `96% ${yprop}%`, + "95% 4%", "50% 4%" + ]; + } else if (propCircum < 3 * rectWidth / 2 + rectHeight) { + var xprop = ( + ((3 * rectWidth/2 + rectHeight) - propCircum) / rectWidth + ) * 100; + bounds = [ + "50% -5%", "110% -5%", + "110% 110%", + `${xprop}% 110%`, `${xprop}% 96%`, + "95% 96%", + "95% 4%", "50% 4%" + ]; + } else if (propCircum < 3 * rectWidth / 2 + 2 * rectHeight) { + var yprop = ( + ((3 * rectWidth/2 + 2 * rectHeight) - propCircum) / rectHeight + ) * 100; + bounds = [ + "50% -5%", "110% -5%", + "110% 110%", "-5% 110%", + `-5% ${yprop}%`, `4% ${yprop}%`, + "5% 96%", "95% 96%", + "95% 4%", "50% 4%" + ]; + } else { + var xprop = ( + (propCircum - (3 * rectWidth/2 + 2 * rectHeight)) / rectWidth + ) * 100; + bounds = [ + "50% -5%", "110% -5%", + "110% 110%", "-5% 110%", + "-5% -5%", + `${xprop}% -5%`, `${xprop}% 4%`, + "4% 4%", + "5% 96%", "95% 96%", + "95% 4%", "50% 4%" + ]; + } + return bounds; +} + +function communicate(websocket) { + console.log("Communicating"); + const params = new URLSearchParams(window.location.search); + websocket.send(JSON.stringify({type: "init", channel: "SubTimer", channelid: params.get('channelid')})); + + var renderer = new TimerRenderer(); + let timer; + + websocket.addEventListener("message", ({ data }) => { + console.log("Rec Event " + data); + const event = JSON.parse(data); + switch (event.type) { + case "DO": + let args = event.args; + + // Call the specified method + switch (event.method) { + case "setTimer": + if (timer != null) { + timer.destroying = true; + } + timer = new Timer( + renderer, + new Date(args.end_at), + true + ) + timer.tick(); + timer.running = args.running; + break; + default: + throw new Error(`Unsupported method requested: ${event.method}.`) + } + break; + default: + throw new Error(`Unsupported event type: ${event.type}.`); + } + }); +}