mirror of
https://github.com/priyanshujain/margin-calendar.git
synced 2026-10-02 11:07:04 +00:00
fix fold wakeup bug
This commit is contained in:
1 parent
9e7d3414d9
commit
c35202674d
5 files changed
+66
-26
No files matched your search
+3
-1
@@ -6,7 +6,9 @@
|
||||
machine expects to find applications: `/Applications` on macOS, or the package manager on Linux.
|
||||
It is the same command whether or not the app is already installed, so it doubles as the update.
|
||||
On macOS it asks a running copy to quit first, because replacing a bundle under a live process
|
||||
leaves it half old and half new. `just uninstall` reverses it and leaves the data directory alone.
|
||||
leaves it half old and half new, and starts the new one once it is in place, so the copy on screen
|
||||
is never older than the copy installed. `just uninstall` reverses it and leaves the data directory
|
||||
alone.
|
||||
|
||||
The local build skips the dmg and builds only the `.app`, since nothing about copying a bundle
|
||||
into place needs a disk image and building one is the slowest part of a mac bundle. That makes a
|
||||
|
||||
@@ -37,7 +37,7 @@ build:
|
||||
*) echo "just: no local build for $(uname -s); macOS and Linux are the desktop targets." >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Build and install, replacing whatever version is already installed.
|
||||
# Build, install over whatever version is already installed, and start the new one.
|
||||
install: build
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
@@ -75,6 +75,7 @@ _install-macos:
|
||||
cp -R "$src" "$dest"
|
||||
version=$(defaults read "$dest/Contents/Info.plist" CFBundleShortVersionString 2> /dev/null || echo "?")
|
||||
echo "Installed $version to $dest"
|
||||
open "$dest"
|
||||
|
||||
_install-linux:
|
||||
#!/usr/bin/env bash
|
||||
|
||||
Generated
+1
-1
@@ -2276,7 +2276,7 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "margin-calendar"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2",
|
||||
|
||||
+14
-23
@@ -1,15 +1,15 @@
|
||||
// The wall clock, at the two granularities anything on the grid cares about: the minute the now
|
||||
// line moves on, and the hour the fit holds open around it.
|
||||
//
|
||||
// Both schedule off the clock rather than off an interval, so a tab that was asleep and a machine
|
||||
// that woke up catch up on the next tick instead of drifting further out every hour. A timer is
|
||||
// still a timer, though: one set before the lid closed can fire long after its wall-clock
|
||||
// deadline, and a hidden window's timers run late or not at all. So the window coming back, as a
|
||||
// focus or a visibility change, is read as a tick of its own.
|
||||
// Both are read off the clock once a minute rather than counted down to. A timer's deadline is
|
||||
// measured in time the machine spent awake, so one set for the top of the hour before the lid
|
||||
// closed is still waiting out the rest of that hour when it opens, however late it is by then.
|
||||
// Reading once a minute puts a minute on how far behind either can be; the window coming back, as
|
||||
// a focus or a visibility change, is read as a tick of its own so it is usually less.
|
||||
//
|
||||
// They are separate hooks because they are separate rerenders. The minute belongs to the one
|
||||
// component that draws the line; putting the fit on that tick would solve the whole axis sixty
|
||||
// times an hour to answer a question that changes once.
|
||||
// component that draws the line. The hour is read as often but only changes once an hour, and a
|
||||
// state set to the value it already holds rerenders nothing.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -22,20 +22,12 @@ function hourStart(ms: number): number {
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
/** The next one. `setMinutes(60)` rolls the date over for us, daylight saving and all. */
|
||||
function nextHourStart(ms: number): number {
|
||||
const d = new Date(ms);
|
||||
d.setMinutes(60, 0, 0);
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
function useTick<T>(read: (now: number) => T, next: (now: number) => number): T {
|
||||
function useTick<T>(read: (now: number) => T): T {
|
||||
const [value, setValue] = useState(() => read(Date.now()));
|
||||
useEffect(() => {
|
||||
let timer = 0;
|
||||
const schedule = () => {
|
||||
const now = Date.now();
|
||||
timer = window.setTimeout(tick, Math.max(0, next(now) - now));
|
||||
timer = window.setTimeout(tick, MINUTE - (Date.now() % MINUTE));
|
||||
};
|
||||
const tick = () => {
|
||||
window.clearTimeout(timer);
|
||||
@@ -53,25 +45,24 @@ function useTick<T>(read: (now: number) => T, next: (now: number) => number): T
|
||||
document.removeEventListener("visibilitychange", woke);
|
||||
window.removeEventListener("focus", woke);
|
||||
};
|
||||
// Both callbacks are module-level functions in every caller, so there is nothing to rebind.
|
||||
}, [read, next]);
|
||||
// `read` is a module-level function in every caller, so there is nothing to rebind.
|
||||
}, [read]);
|
||||
return value;
|
||||
}
|
||||
|
||||
const now = (ms: number) => ms;
|
||||
const nextMinute = (ms: number) => ms + MINUTE - (ms % MINUTE);
|
||||
|
||||
/** Now, refreshed on every wall-clock minute. */
|
||||
export function useMinuteTick(): number {
|
||||
return useTick(now, nextMinute);
|
||||
return useTick(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* The top of the hour it is now, refreshed when the clock turns over into the next one.
|
||||
* The top of the hour it is now, read every minute and changed once an hour.
|
||||
*
|
||||
* Returning the hour rather than the instant is what makes this cheap to depend on: the value is
|
||||
* identical for a whole hour, so a memo keyed on it recomputes once, when the answer changed.
|
||||
*/
|
||||
export function useHourStart(): number {
|
||||
return useTick(hourStart, nextHourStart);
|
||||
return useTick(hourStart);
|
||||
}
|
||||
@@ -280,6 +280,52 @@ test.describe("the hour it is now", () => {
|
||||
expect((await axis(page)).map((entry) => entry.text)).not.toContain("11pm");
|
||||
await expect(page.locator(".grid-now")).toHaveCount(0);
|
||||
});
|
||||
|
||||
// The two below are about an app left open, so the clock is a running one rather than a pinned
|
||||
// instant: `install` lets it be moved, and the strip that reads "8pm to 10pm" only exists once
|
||||
// the hour it holds open has moved from nine to ten.
|
||||
test("moves on with the clock while the app stays open", async ({ page }) => {
|
||||
await page.clock.install({ time: clockAt(21, 58) });
|
||||
await openApp(page);
|
||||
await page.clock.pauseAt(clockAt(21, 59));
|
||||
const before = (await axis(page)).map((entry) => entry.text);
|
||||
expect(before).toContain("9pm");
|
||||
expect(before).not.toContain("10pm");
|
||||
|
||||
await page.clock.runFor(2 * 60_000);
|
||||
|
||||
await expect(page.locator(".grid-strip", { hasText: "8pm to 10pm" })).toHaveCount(1);
|
||||
const after = (await axis(page)).map((entry) => entry.text);
|
||||
expect(after).toContain("10pm");
|
||||
expect(after).not.toContain("9pm");
|
||||
await expect(page.locator(".grid-now")).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("catches up when the clock jumped, as it does after the lid was closed", async ({ page }) => {
|
||||
await page.clock.install({ time: clockAt(21, 30) });
|
||||
await openApp(page);
|
||||
await page.clock.pauseAt(clockAt(21, 31));
|
||||
expect((await axis(page)).map((entry) => entry.text)).toContain("9pm");
|
||||
|
||||
// Asleep, the wall clock moves on and the timers do not: one set for the top of the hour is
|
||||
// still waiting for the rest of its half hour when the machine wakes at five past ten. What
|
||||
// the app gets is one minute of being awake.
|
||||
await page.clock.setSystemTime(clockAt(22, 5));
|
||||
await page.clock.runFor(60_000);
|
||||
|
||||
await expect(page.locator(".grid-strip", { hasText: "8pm to 10pm" })).toHaveCount(1);
|
||||
const hours = (await axis(page)).map((entry) => entry.text);
|
||||
expect(hours).toContain("10pm");
|
||||
expect(hours).not.toContain("9pm");
|
||||
await expect(page.locator(".grid-now")).toHaveCount(1);
|
||||
|
||||
const line = await box(page.locator(".grid-now"));
|
||||
const row = (await gridFit(page)).rowHeight;
|
||||
const ten = (await axis(page)).find((entry) => entry.text === "10pm");
|
||||
const canvas = await box(page.locator(".grid-canvas"));
|
||||
expect(line.top - canvas.top).toBeGreaterThan(ten!.y);
|
||||
expect(line.top - canvas.top).toBeLessThan(ten!.y + row);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("the all-day band", () => {
|
||||
|
||||
Reference in new issue
Block a user