330 lines
9.0 KiB
JavaScript
330 lines
9.0 KiB
JavaScript
var renderer;
|
|
var timer;
|
|
|
|
window.addEventListener("DOMContentLoaded", () => {
|
|
renderer = new TimerRenderer();
|
|
|
|
const queryString = window.location.search;
|
|
console.log(queryString);
|
|
// Set colours from querystring too, background, focus, break
|
|
if (queryString) {
|
|
updateTimerFromQuery(queryString)
|
|
} else {
|
|
const websocket = new WebSocket("wss://croccy.thewisewolf.dev/ws");
|
|
|
|
websocket.addEventListener("open", () => {
|
|
communicate(websocket);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
function updateTimerFromQuery(queryString) {
|
|
const params = new URLSearchParams(queryString);
|
|
|
|
const block_number = parseInt(params.get('block') || 1);
|
|
const focus_length = parseInt(params.get('focus') || 50) * 60;
|
|
const break_length = parseInt(params.get('break') || 10) * 60;
|
|
const block_goal = parseInt(params.get('goal') || 0);
|
|
|
|
const focus_colour = "#" + (params.get('focuscolour') || "EED59F");
|
|
const break_colour = "#" + (params.get('breakcolour') || "8CD5CA");
|
|
const background = "#" + (params.get('background') || "1E202F");
|
|
|
|
const root = document.documentElement;
|
|
root.style.setProperty('--focus-color', focus_colour);
|
|
root.style.setProperty('--break-color', break_colour);
|
|
root.style.setProperty('--bg-color', background);
|
|
|
|
|
|
|
|
const now = new Date();
|
|
const start_at = new Date(now.getTime() - (focus_length + break_length) * 1000 * (block_number - 1));
|
|
update_timer(start_at, focus_length, break_length, block_goal);
|
|
}
|
|
|
|
class Timer {
|
|
constructor(renderer, start_at, focus_length, break_length, block_goal) {
|
|
this.renderer = renderer;
|
|
this.start_at = start_at;
|
|
this.focus_length = focus_length;
|
|
this.break_length = break_length;
|
|
this.block_goal = block_goal;
|
|
|
|
this.counter = this.count_now;
|
|
this.break_notified = true;
|
|
|
|
this.destroying = false;
|
|
}
|
|
|
|
get block_length() {
|
|
// Return total block duration in seconds
|
|
return this.focus_length + this.break_length;
|
|
}
|
|
|
|
get block_start() {
|
|
// Get the Date when this block started.
|
|
return new Date(this.start_at.getTime() + (this.counter * this.block_length) * 1000);
|
|
}
|
|
|
|
get count_now() {
|
|
// Current actual block number. 0 for the first block, -1 if not running
|
|
var diff = Math.floor( (new Date() - this.start_at) / 1000 );
|
|
if (diff < 0) {
|
|
return -1;
|
|
} else {
|
|
var stage = Math.floor( diff / this.block_length );
|
|
return stage;
|
|
}
|
|
}
|
|
|
|
get is_break() {
|
|
// Whether the current block is on break.
|
|
if (this.counter < 0){
|
|
return true
|
|
} else{
|
|
return ((new Date() - this.block_start) / 1000) > this.focus_length;
|
|
}
|
|
}
|
|
|
|
render_time() {
|
|
// Render timer status to the document
|
|
let timestr;
|
|
let proportion;
|
|
|
|
if (this.counter < 0) {
|
|
timestr = "--:--";
|
|
proportion = 0;
|
|
} else {
|
|
// How many seconds have passed in this block
|
|
var dur_seconds = Math.floor( (new Date() - this.block_start) / 1000);
|
|
if (dur_seconds < this.focus_length) {
|
|
// How many seconds are left in the focus session
|
|
dur_seconds = this.focus_length - dur_seconds;
|
|
// What proportion of the focus session remains
|
|
proportion = dur_seconds / this.focus_length;
|
|
} else {
|
|
dur_seconds = this.block_length - dur_seconds;
|
|
proportion = dur_seconds / (this.break_length);
|
|
}
|
|
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');
|
|
}
|
|
|
|
this.renderer.update_time(timestr);
|
|
this.renderer.update_proportion(proportion);
|
|
let goal;
|
|
if (this.block_goal <= 0) {
|
|
goal = "??";
|
|
} else {
|
|
goal = String(this.block_goal);
|
|
}
|
|
this.renderer.update_block(String(this.counter + 1) + '/' + String(goal));
|
|
if (this.is_break){
|
|
this.renderer.update_stage("BREAK");
|
|
this.renderer.set_break_color();
|
|
} else {
|
|
this.renderer.update_stage("FOCUS");
|
|
this.renderer.set_focus_color();
|
|
}
|
|
}
|
|
|
|
tick() {
|
|
// Recursive call run per second to update and change stage
|
|
|
|
if (this.destroying) {
|
|
return
|
|
}
|
|
|
|
if (this.is_break && !this.break_notified && this.counter >= 0) {
|
|
// Notify for break
|
|
// TODO voice alert
|
|
this.renderer.break_alert.play();
|
|
// TODO chat message?
|
|
console.log("Notifying Break.")
|
|
this.break_notified = true;
|
|
} else if (this.count_now > this.counter) {
|
|
this.counter++;
|
|
this.break_notified = false;
|
|
// Notify for focus
|
|
// TODO voice alert
|
|
this.renderer.focus_alert.play();
|
|
// TODO chat message?
|
|
console.log("Notifying Focus.")
|
|
}
|
|
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.stage_element = document.getElementsByClassName("timer-details-stage-text")[0];
|
|
this.block_element = document.getElementsByClassName("timer-details-block")[0];
|
|
|
|
this.break_alert = new Audio('audio/break_alert.wav');
|
|
this.focus_alert = new Audio('audio/focus_alert.wav');
|
|
|
|
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;
|
|
}
|
|
|
|
update_proportion (proportion) {
|
|
var bounds = rectBounds(
|
|
proportion, this.bg_width, this.bg_height
|
|
);
|
|
this.background.style.setProperty(
|
|
'--clip-path',
|
|
"polygon(" + bounds.join(', ') + ")"
|
|
);
|
|
}
|
|
|
|
update_block (blockstr) {
|
|
this.block_element.textContent = blockstr;
|
|
}
|
|
|
|
update_stage (stagestr) {
|
|
this.stage_element.textContent = stagestr;
|
|
}
|
|
|
|
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");
|
|
websocket.send(JSON.stringify({type: "init", channel: "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":
|
|
update_timer(
|
|
new Date(args.start_at),
|
|
args.focus_length,
|
|
args.break_length,
|
|
args.block_goal
|
|
)
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported method requested: ${event.method}.`)
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported event type: ${event.type}.`);
|
|
}
|
|
});
|
|
}
|
|
|
|
function update_timer(start_at, focus_length, break_length, goal) {
|
|
if (timer != null) {
|
|
timer.destroying = true;
|
|
}
|
|
timer = new Timer(
|
|
renderer,
|
|
start_at,
|
|
focus_length,
|
|
break_length,
|
|
goal
|
|
)
|
|
timer.tick();
|
|
}
|