web: come back to the frame that was raised, and zoom it by the wheel

Two things the preview owed a reader.

The frame raised in the strip outlived nothing: leaving for the studio and
coming back dropped the choice and the node opened on its first negative
instead. It is now remembered beside the folder and the column width — the
same one-line store, read on the way in, written on the way out — and a
frame that is gone from the catalogue falls through to the first, the way
a node that is gone falls back to its folder.

A wheel over the frame now magnifies it. The listener goes on the element
rather than through onWheel, because React's own wheel is passive and this
one has to hold the page still while it zooms; the origin is the point
under the pointer, which is what keeps a reader's aim on the thing being
looked at, and the step comes from how far the wheel turned so a notched
mouse and a trackpad travel the same. Six times is where it stops — the
thumbnail behind the preview has no more pixels to give — and a frame that
has just come up is fitted again.

library-check: 35 steps, the two new ones raising a nested frame, reloading
on it, and ticking the wheel up and back down.

Co-authored-by: PenguinHarness <noreply@penguin.local>
This commit is contained in:
2026-09-28 21:43:36 +07:00
parent 067b32131e
commit 134489a5ec
2 changed files with 90 additions and 5 deletions
+31
View File
@@ -432,6 +432,37 @@ const where = await page.$eval('[data-key^="lib-node-"][aria-current="true"]', (
step('the column drags and is the same width next time', dragged >= 160 && reopened === dragged, `${dragged}px, then ${reopened}px`);
step('the screen reopens on the folder it was left on', where === 'lib-node-CheckRoll/2026', where);
// The frame that was raised is remembered too: a reader who comes back finds the
// negative they were looking at, not the first one the node happens to hold. The
// JPEG is the one with a picture to show, so it is the one that gets raised.
const jpgId = indexed.thumbs.find((t) => t.id.endsWith(`/${JPG}`) && t.id.includes('/2026/04/')).id;
await page.click(`[data-key="lib-thumb-${jpgId}"]`);
await page.reload();
await page.waitForSelector(thumbs, { timeout: 120_000 });
const raised = await page.$eval('.adm-thumb.on', (e) => e.dataset.key);
step('the screen reopens on the frame that was raised', raised === `lib-thumb-${jpgId}`, raised);
// A wheel tick over the frame magnifies it about the point under the pointer —
// the one gesture that makes a negative worth the whole window. The page under it
// holds still, and ticking back down fits the frame again.
const shotBox = await (await page.$('[data-key="lib-stage-img"]')).boundingBox();
await page.mouse.move(shotBox.x + shotBox.width / 3, shotBox.y + shotBox.height / 3);
await page.mouse.wheel(0, -240);
await page.waitForTimeout(150);
const magnified = await page.$eval('[data-key="lib-stage-img"]', (el) => ({
transform: getComputedStyle(el).transform,
origin: getComputedStyle(el).transformOrigin,
}));
await page.mouse.wheel(0, 600);
await page.waitForTimeout(150);
const fitted = await page.$eval('[data-key="lib-stage-img"]', (el) => getComputedStyle(el).transform);
const scale = Number((magnified.transform.match(/matrix\(([\d.]+)/) ?? [])[1] ?? 1);
step(
'a wheel tick over the frame zooms it, and back down fits it again',
scale > 1 && scale <= 6 && fitted === 'none',
`${magnified.transform} about ${magnified.origin} → ${fitted}`
);
// One tap on a thumbnail raises the frame, and the button under it hands it to
// the studio. The RAW is the harder half — it has to be read back off the
// (stand-in) handle and developed — so it is the one that gets opened.