Add editable countdown timer with deadline-based timing
Co-authored-by: Shelley <shelley@exe.dev>
This commit is contained in:
parent
d3ed55847a
commit
9c993beda8
6 changed files with 440 additions and 0 deletions
64
site/countdown/timer.mjs
Normal file
64
site/countdown/timer.mjs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// BigInt keeps entered integers exact, even for very large countdowns.
|
||||
export function integer(value) {
|
||||
return /^\d+$/.test(String(value)) ? BigInt(value) : 0n;
|
||||
}
|
||||
|
||||
export function splitSeconds(seconds) {
|
||||
return {
|
||||
minutes: String(seconds / 60n),
|
||||
seconds: String(seconds % 60n).padStart(2, '0'),
|
||||
};
|
||||
}
|
||||
|
||||
export class Countdown {
|
||||
constructor(now = Date.now) {
|
||||
this.now = now;
|
||||
this.state = 'idle';
|
||||
this.finished = false;
|
||||
this.duration = 0n;
|
||||
this.remaining = 0n;
|
||||
this.deadline = 0n;
|
||||
}
|
||||
|
||||
edit(minutes, seconds) {
|
||||
if (this.state === 'running') return;
|
||||
this.duration = (integer(minutes) * 60n + integer(seconds)) * 1000n;
|
||||
this.remaining = this.duration;
|
||||
this.state = 'idle';
|
||||
this.finished = false;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.state !== 'running') return;
|
||||
const delta = this.deadline - BigInt(this.now());
|
||||
this.remaining = delta > 0n ? delta : 0n;
|
||||
if (this.remaining === 0n) {
|
||||
this.state = 'idle';
|
||||
this.finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.state === 'running') {
|
||||
this.update();
|
||||
if (!this.finished) this.state = 'paused';
|
||||
return;
|
||||
}
|
||||
if (this.finished) this.remaining = this.duration;
|
||||
this.finished = false;
|
||||
this.deadline = BigInt(this.now()) + this.remaining;
|
||||
this.state = 'running';
|
||||
this.update();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.remaining = this.duration;
|
||||
this.state = 'idle';
|
||||
this.finished = false;
|
||||
}
|
||||
|
||||
display() {
|
||||
// Round up so the timer only displays zero once its deadline is reached.
|
||||
return splitSeconds((this.remaining + 999n) / 1000n);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue