Let a drawing run over as many pages as the note
The ink was stuck on page one while the writing carried on without it, which stopped making sense the moment a note could run to four pages. Every page is exported anyway, so every page can be drawn on. Pages are numbered the way the PDF reader numbers them, and only pages that carry ink are stored, so the keys are the drawn pages. While drawing there is always one blank page past the end: pages otherwise come from how far the writing runs, and to draw on page three you would first have had to write three pages. A drawing that lives entirely on page one is still written in the old single-page shape. Nothing requires that — it means an ordinary note is stored exactly as it always was, and a device running an older build still shows it. The bin clears the page being looked at. Undo and redo work on the drawing as a whole, because one Undo button that means four different things depending on where you scrolled is not a button anyone can use. And a real bug found on the way out: the exporter has written drawings/<note>.png since the interface was put into English, while the importer was still looking for zeichnungen/<note>.png. A drawing exported by this very app came back from its own archive as nothing at all. Both names are read now, and so are the further pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b8c5c42217
commit
228552d453
9 files changed
+671
-136
No files matched your search
+85
-10
@@ -23,11 +23,13 @@ import {
|
||||
const GRID_W = 48,
|
||||
GRID_H = 64;
|
||||
|
||||
/** Rasterises the drawing surface into a GRID_W x GRID_H map of "has ink". */
|
||||
const inkGrid = (page) =>
|
||||
/** Rasterises one page's drawing surface into a GRID_W x GRID_H map of "has ink". */
|
||||
const inkGrid = (page, sheet = 1) =>
|
||||
page.evaluate(
|
||||
([gw, gh]) => {
|
||||
const c = document.querySelector("canvas");
|
||||
([gw, gh, n]) => {
|
||||
const c =
|
||||
document.querySelector(`canvas[data-draw-page="${n}"]`) ||
|
||||
document.querySelector(`[data-drawn-page="${n}"] canvas`);
|
||||
if (!c) return null;
|
||||
const off = document.createElement("canvas");
|
||||
off.width = gw;
|
||||
@@ -39,18 +41,23 @@ const inkGrid = (page) =>
|
||||
for (let i = 0; i < gw * gh; i++) out.push(d[i * 4 + 3] > 40 ? 1 : 0);
|
||||
return out;
|
||||
},
|
||||
[GRID_W, GRID_H]
|
||||
[GRID_W, GRID_H, sheet]
|
||||
);
|
||||
|
||||
const diff = (a, b) => a.reduce((n, v, i) => n + (v === b[i] ? 0 : 1), 0);
|
||||
const ink = (g) => g.reduce((n, v) => n + v, 0);
|
||||
|
||||
async function draw(page) {
|
||||
await page.locator("button").filter({ hasText: "Draw" }).first().click();
|
||||
await page.waitForTimeout(1200);
|
||||
const box = await page.locator("canvas").first().boundingBox();
|
||||
async function draw(page, sheet = 1, strokes = null) {
|
||||
if (sheet === 1) {
|
||||
await page.locator("button").filter({ hasText: "Draw" }).first().click();
|
||||
await page.waitForTimeout(1200);
|
||||
}
|
||||
const canvas = page.locator(`canvas[data-draw-page="${sheet}"]`);
|
||||
await canvas.scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500);
|
||||
const box = await canvas.boundingBox();
|
||||
// Three strokes in different parts of the page, so a shifted origin shows up.
|
||||
const strokes = [
|
||||
strokes ??= [
|
||||
[0.2, 0.2, 0.7, 0.35],
|
||||
[0.25, 0.55, 0.75, 0.5],
|
||||
[0.5, 0.7, 0.55, 0.85],
|
||||
@@ -130,6 +137,74 @@ try {
|
||||
const d = diff(reference, afterReload);
|
||||
check(d / (GRID_W * GRID_H) < 0.02, "the reloaded drawing matches the original", `${d} cells`);
|
||||
}
|
||||
|
||||
/* ── a drawing runs over as many pages as the note has ──
|
||||
* The ink used to be stuck on page one while the writing carried on without
|
||||
* it. Every page is exported anyway, so every page can be drawn on. */
|
||||
await createNote(p);
|
||||
await setTitle(p, "Mehrere Seiten");
|
||||
await p.locator("button").filter({ hasText: "Draw" }).first().click();
|
||||
await p.waitForTimeout(1200);
|
||||
check(
|
||||
(await p.locator('canvas[data-draw-page="2"]').count()) === 1,
|
||||
"there is a blank page past the end to draw on, before anything is written"
|
||||
);
|
||||
|
||||
await draw(p, 1);
|
||||
const first = await inkGrid(p, 1);
|
||||
await draw(p, 2, [
|
||||
[0.3, 0.25, 0.8, 0.4],
|
||||
[0.2, 0.6, 0.6, 0.75],
|
||||
]);
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
const second = await inkGrid(p, 2);
|
||||
check(ink(second) > 0, "a stroke lands on page two", `${ink(second)} ink cells`);
|
||||
check(diff(first, await inkGrid(p, 1)) === 0, "and page one is untouched by it");
|
||||
check(
|
||||
(await p.locator("[data-note-position]").innerText()).includes("of 2"),
|
||||
"drawing on page two makes the note two pages",
|
||||
await p.locator("[data-note-position]").innerText()
|
||||
);
|
||||
check(
|
||||
(await p.locator('canvas[data-draw-page="3"]').count()) === 1,
|
||||
"and there is a blank page after it again"
|
||||
);
|
||||
await p.screenshot({ path: `${OUT}/drawing-pages.png` });
|
||||
|
||||
/* Stored per page, and it survives the trip to the server and back. */
|
||||
const pageDoc = await p.evaluate(async () => {
|
||||
const auth = { Authorization: "Bearer " + localStorage.getItem("inknote_token") };
|
||||
const notes = await (await fetch("/api/notes", { headers: auth })).json();
|
||||
const mine = notes.find((n) => n.title === "Mehrere Seiten");
|
||||
return (await (await fetch(`/api/notes/${mine.id}`, { headers: auth })).json()).drawingStrokes;
|
||||
});
|
||||
check(!!pageDoc?.sheets, "the server stored it as pages, not as one sheet", JSON.stringify(Object.keys(pageDoc || {})));
|
||||
check(
|
||||
(pageDoc?.sheets?.["1"]?.length ?? 0) > 0 && (pageDoc?.sheets?.["2"]?.length ?? 0) > 0,
|
||||
"with strokes on both",
|
||||
`page 1: ${pageDoc?.sheets?.["1"]?.length}, page 2: ${pageDoc?.sheets?.["2"]?.length}`
|
||||
);
|
||||
|
||||
await reopenNote(p, "Mehrere Seiten");
|
||||
await p.waitForTimeout(2000);
|
||||
const reloadedTwo = await inkGrid(p, 2);
|
||||
check(reloadedTwo !== null && ink(reloadedTwo) > 0, "page two's ink is still there after a reload");
|
||||
check(
|
||||
diff(second, reloadedTwo) / (GRID_W * GRID_H) < 0.02,
|
||||
"and it is the same ink",
|
||||
`${diff(second, reloadedTwo)} cells`
|
||||
);
|
||||
|
||||
/* Clearing acts on the page you are looking at, not on the whole drawing. */
|
||||
await p.locator("button").filter({ hasText: "Draw" }).first().click();
|
||||
await p.waitForTimeout(1200);
|
||||
await p.locator('canvas[data-draw-page="1"]').scrollIntoViewIfNeeded();
|
||||
await p.waitForTimeout(900);
|
||||
await p.locator("[data-clear-page]").click();
|
||||
await p.waitForTimeout(2500);
|
||||
check(ink(await inkGrid(p, 1)) === 0, "the bin empties the page being looked at");
|
||||
check(ink(await inkGrid(p, 2)) > 0, "and leaves the other pages alone");
|
||||
} catch (e) {
|
||||
check(false, "suite ran to the end", e.message);
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user