aboutsummaryrefslogtreecommitdiff
path: root/src/lib/stores/timer.svelte.js
diff options
context:
space:
mode:
authorKaran Jayachandra <mail@karanjayachandra.com>2026-08-30 22:43:21 +0200
committerKaran Jayachandra <mail@karanjayachandra.com>2026-08-30 22:43:21 +0200
commitfc8d8ab8d0da95ec79056f59e14a150df6fcea89 (patch)
tree33f8050b2f2920c0f1b884a205a8aa4c81d7687c /src/lib/stores/timer.svelte.js
parentd37ebfda883bcd03b146a85bc3e5a4657d0abd73 (diff)
Moved to sveltemain
Diffstat (limited to 'src/lib/stores/timer.svelte.js')
-rw-r--r--src/lib/stores/timer.svelte.js106
1 files changed, 106 insertions, 0 deletions
diff --git a/src/lib/stores/timer.svelte.js b/src/lib/stores/timer.svelte.js
new file mode 100644
index 0000000..dea1799
--- /dev/null
+++ b/src/lib/stores/timer.svelte.js
@@ -0,0 +1,106 @@
+import { Notyf } from "notyf";
+
+const DEFAULT_MINS = 12;
+const MIN_MINS = 0.5;
+
+const initialMins = Number(sessionStorage.getItem("mins")) || DEFAULT_MINS;
+const initialRunning = Boolean(Number(sessionStorage.getItem("running")));
+const initialDeadline = Number(sessionStorage.getItem("deadline")) || Date.now();
+const initialRemainingMs = initialRunning
+ ? Math.max(initialDeadline - Date.now(), 0)
+ : initialMins * 60 * 1000;
+
+let mins = $state(initialMins);
+let running = $state(initialRunning);
+let deadline = $state(initialDeadline);
+let remainingMs = $state(initialRemainingMs);
+
+let intervalId = null;
+
+function persist() {
+ sessionStorage.setItem("mins", mins);
+ sessionStorage.setItem("running", Number(running));
+ sessionStorage.setItem("deadline", deadline);
+}
+
+function showRoundCompletedToast() {
+ const notyf = new Notyf({
+ duration: 0,
+ position: { x: "center", y: "top" },
+ dismissible: true,
+ });
+ notyf.error("Round completed");
+}
+
+function stopInterval() {
+ if (intervalId !== null) clearInterval(intervalId);
+ intervalId = null;
+}
+
+function tick() {
+ const msToDeadline = deadline - Date.now();
+ if (msToDeadline > 0) {
+ remainingMs = msToDeadline;
+ return;
+ }
+ stopInterval();
+ running = false;
+ remainingMs = mins * 60 * 1000;
+ persist();
+ showRoundCompletedToast();
+}
+
+export function getMins() {
+ return mins;
+}
+
+export function getRunning() {
+ return running;
+}
+
+export function getRemainingMs() {
+ return remainingMs;
+}
+
+export function formatRemaining(ms) {
+ const m = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
+ const s = Math.floor((ms % (1000 * 60)) / 1000);
+ const pad = (n) => (n < 10 ? "0" + n : String(n));
+ return `${pad(m)}:${pad(s)}`;
+}
+
+export function getDisplay() {
+ return formatRemaining(remainingMs);
+}
+
+export function stopTimer() {
+ stopInterval();
+ running = false;
+ remainingMs = mins * 60 * 1000;
+ persist();
+}
+
+export function start(fresh = true) {
+ if (fresh && running) return;
+ if (fresh) {
+ deadline = Date.now() + mins * 60 * 1000;
+ }
+ running = true;
+ remainingMs = Math.max(deadline - Date.now(), 0);
+ persist();
+ stopInterval();
+ intervalId = setInterval(tick, 1000);
+}
+
+export function changeMinutes(delta) {
+ mins = Math.max(mins + delta, MIN_MINS);
+ stopTimer();
+}
+
+// Resume a timer that was still running across a page reload, otherwise
+// settle the display to the resting (not-running) state.
+if (initialRunning) {
+ start(false);
+} else {
+ stopTimer();
+}