fix(store,due): poll the clock so today turns over after sleep
This commit is contained in:
1 parent
b415b17007
commit
e7178c6d3f
3 files changed
+22
-39
No files matched your search
@@ -29,12 +29,6 @@ export function shiftDays(key: string, days: number): string {
|
|||||||
return localKey(date);
|
return localKey(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Milliseconds until the next local midnight, with a little slack past it. */
|
|
||||||
export function untilMidnight(now = new Date()): number {
|
|
||||||
const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
||||||
return Math.max(1000, next.getTime() - now.getTime() + 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function daysFromToday(key: string, from = today()): number {
|
export function daysFromToday(key: string, from = today()): number {
|
||||||
return Math.round((fromKey(key).getTime() - fromKey(from).getTime()) / 86_400_000);
|
return Math.round((fromKey(key).getTime() - fromKey(from).getTime()) / 86_400_000);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-24
@@ -4,7 +4,7 @@ import { isArchivePath, toBoardPath } from "../model/archive";
|
|||||||
import { parseBoard, sphereOf } from "../model/board";
|
import { parseBoard, sphereOf } from "../model/board";
|
||||||
import { anchorDate, isOpen, STATUS_ORDER } from "../model/types";
|
import { anchorDate, isOpen, STATUS_ORDER } from "../model/types";
|
||||||
import type { Board, Status, Task } from "../model/types";
|
import type { Board, Status, Task } from "../model/types";
|
||||||
import { daysFromToday, fromKey, localKey, shiftDays, untilMidnight } from "../lib/due";
|
import { daysFromToday, fromKey, localKey, shiftDays } from "../lib/due";
|
||||||
import { laneKey, parseLaneKey } from "../lib/keys";
|
import { laneKey, parseLaneKey } from "../lib/keys";
|
||||||
import type {
|
import type {
|
||||||
BeaverCalendarSettings,
|
BeaverCalendarSettings,
|
||||||
@@ -197,37 +197,35 @@ export class BoardStore {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Re-reads the wall clock; a no-op while the day has not turned. */
|
/**
|
||||||
|
* Re-reads the wall clock; a no-op while the day has not turned. A cursor
|
||||||
|
* left on the old today, and a month showing it, follow the day over.
|
||||||
|
*/
|
||||||
tick(): void {
|
tick(): void {
|
||||||
const now = localKey(new Date());
|
const now = localKey(new Date());
|
||||||
if (now !== this.today) this.today = now;
|
const was = this.today;
|
||||||
|
if (now === was) return;
|
||||||
|
this.today = now;
|
||||||
|
if (this.cursorDay === was && this.cursorTask === null) this.cursorDay = now;
|
||||||
|
if (this.monthKey.slice(0, 7) === was.slice(0, 7)) this.monthKey = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps `today` honest for as long as the view lives: a timer for the
|
* Keeps `today` honest for as long as the view lives. The clock is polled
|
||||||
* midnight crossing, and a re-read when the window wakes up, since a
|
* rather than timed to midnight: Chromium's timers stop while a Mac sleeps,
|
||||||
* sleeping laptop fires no timers.
|
* so a midnight timeout fires hours late, and a window that stayed focused
|
||||||
|
* through the sleep sends no focus event on waking. Waking still re-reads
|
||||||
|
* at once where the events do come.
|
||||||
*/
|
*/
|
||||||
watchClock(): () => void {
|
watchClock(): () => void {
|
||||||
let timer = 0;
|
const tick = (): void => this.tick();
|
||||||
const arm = (): void => {
|
const timer = window.setInterval(tick, 30_000);
|
||||||
window.clearTimeout(timer);
|
window.addEventListener("focus", tick);
|
||||||
timer = window.setTimeout(() => {
|
document.addEventListener("visibilitychange", tick);
|
||||||
this.tick();
|
|
||||||
arm();
|
|
||||||
}, untilMidnight());
|
|
||||||
};
|
|
||||||
const wake = (): void => {
|
|
||||||
this.tick();
|
|
||||||
arm();
|
|
||||||
};
|
|
||||||
arm();
|
|
||||||
window.addEventListener("focus", wake);
|
|
||||||
document.addEventListener("visibilitychange", wake);
|
|
||||||
return () => {
|
return () => {
|
||||||
window.clearTimeout(timer);
|
window.clearInterval(timer);
|
||||||
window.removeEventListener("focus", wake);
|
window.removeEventListener("focus", tick);
|
||||||
document.removeEventListener("visibilitychange", wake);
|
document.removeEventListener("visibilitychange", tick);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
dueLabel,
|
dueLabel,
|
||||||
dueTone,
|
dueTone,
|
||||||
nextFriday,
|
nextFriday,
|
||||||
untilMidnight,
|
|
||||||
} from "../src/lib/due";
|
} from "../src/lib/due";
|
||||||
|
|
||||||
describe("due helpers read the day they are given", () => {
|
describe("due helpers read the day they are given", () => {
|
||||||
@@ -26,12 +25,4 @@ describe("due helpers read the day they are given", () => {
|
|||||||
expect(nextFriday("2026-08-23")).toBe("2026-08-28");
|
expect(nextFriday("2026-08-23")).toBe("2026-08-28");
|
||||||
expect(nextFriday("2026-08-28")).toBe("2026-09-04");
|
expect(nextFriday("2026-08-28")).toBe("2026-09-04");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("arms the midnight timer for the coming midnight", () => {
|
|
||||||
const late = new Date(2026, 7, 23, 23, 59, 0);
|
|
||||||
expect(untilMidnight(late)).toBeGreaterThan(60_000);
|
|
||||||
expect(untilMidnight(late)).toBeLessThan(62_000);
|
|
||||||
const early = new Date(2026, 7, 23, 0, 0, 1);
|
|
||||||
expect(untilMidnight(early)).toBeGreaterThan(23 * 3_600_000);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user