feat(library): read the wall's time by the year and the month, and mint only the frames near the eye
This commit is contained in:
@@ -5,8 +5,13 @@
|
||||
// node holds is a card (the wall is drawn whole — no window over it, so no card
|
||||
// arriving late and no spacer standing in for one), every card sits under the band
|
||||
// of its own day, the last frame is reachable by scrolling with nothing left under
|
||||
// it, and the rail down the wall's right edge — the shelf's own time — reads the
|
||||
// day the eye is in and takes the wall to the day it is pressed for.
|
||||
// it, and the rail down the wall's right edge — the shelf's own time, by the year
|
||||
// and the month and nothing finer — scrolls under the reader's hand, reads the month
|
||||
// the eye is in and takes the wall to the month or the year it is pressed for.
|
||||
//
|
||||
// The wall is also the one screen that reads the whole shelf, so what it mints is
|
||||
// watched here too: a roll of years is a rail of months, and a rail whose every entry
|
||||
// is in the box it is read through.
|
||||
//
|
||||
// It is drawn whole for a reason this check keeps an eye on: the wall used to be a
|
||||
// window over the shelf, and a window measured off the window before it cost a
|
||||
@@ -31,8 +36,8 @@ const APP_PORT = Number(process.env.APP_PORT ?? 5033);
|
||||
const SAMPLE_PORT = Number(process.env.SAMPLE_PORT ?? 5034);
|
||||
const FRAMES = Number(process.env.FRAMES ?? 300);
|
||||
const FOLDER = 'WallRoll';
|
||||
// The second roll: a shelf of one month, which the rail reads by the day rather
|
||||
// than by the month — the other half of what the rail is (`rail` in Library.tsx),
|
||||
// The second roll: a shelf of a single month, which the rail reads with one head and
|
||||
// one month under it — the other half of what the rail is (`rail` in Library.tsx),
|
||||
// and the shape a folder per event has. Its frames are two days of twenty.
|
||||
const SMALL_FOLDER = 'OneDayRoll';
|
||||
const SMALL_EACH = 20;
|
||||
@@ -44,7 +49,11 @@ const SMALL_DAYS = 2;
|
||||
// half of what this check is for.
|
||||
const names = Array.from({ length: FRAMES }, (_, i) => `frame-${String(i + 1).padStart(4, '0')}.jpg`);
|
||||
const DAY_MS = 86400000;
|
||||
const DAY0 = Date.UTC(2026, 0, 1);
|
||||
// The roll is spread over years, a shooting day every forty days or so: a shelf of
|
||||
// months is what the rail is for, and a rail that holds three of them is a rail that
|
||||
// never has to be scrolled — the years are what make it longer than the wall is tall.
|
||||
const DAY_SPREAD = 40;
|
||||
const DAY0 = Date.UTC(2018, 0, 1);
|
||||
// The days the roll was shot on, and they are not equal: a roll is an afternoon
|
||||
// of three frames and a wedding of four hundred, and the wall's own model of its
|
||||
// rows is the thing that has to survive that. A shelf of equal days is a shelf a
|
||||
@@ -73,7 +82,7 @@ const dayIndex = (n) => {
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const dayOfName = (name) => DAY0 + dayIndex(Number((name.match(/\d+/) ?? ['1'])[0]) - 1) * DAY_MS;
|
||||
const dayOfName = (name) => DAY0 + dayIndex(Number((name.match(/\d+/) ?? ['1'])[0]) - 1) * DAY_SPREAD * DAY_MS;
|
||||
|
||||
// The second roll's own names and days: `one-0001` onwards, twenty to a day, over
|
||||
// the fourth and fifth of March 2026 — one month, two days, which is the shelf the
|
||||
@@ -100,7 +109,9 @@ const keyOf = (ts) => {
|
||||
const d = new Date(ts);
|
||||
return `${monthOf(ts)}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
const yearOf = (ts) => String(new Date(ts).getFullYear());
|
||||
const MONTHS = [...new Set(names.map((name) => monthOf(dayOf(name))))];
|
||||
const YEARS = [...new Set(names.map((name) => yearOf(dayOf(name))))];
|
||||
const SMALL_DAY_KEYS = [...new Set(smallNames.map((name) => keyOf(dayOf(name))))];
|
||||
// The days the big roll is cut into, which is what the wall draws bands for.
|
||||
const BIG_DAYS = (() => {
|
||||
@@ -359,7 +370,7 @@ step('every header names the day of the cards under it', days1.bad.length === 0,
|
||||
// What the rail is read for: the entries it holds, the entry the eye is in, and where
|
||||
// a press on one takes the wall. The day at the fold is read off the sections
|
||||
// themselves — the first one crossing the wall's top band, which is the rule the rail
|
||||
// itself reads by — and the entry the wall says it is in is read off the accent.
|
||||
// itself reads by — and the month the wall says it is in is read off the accent.
|
||||
const railOf = () =>
|
||||
page.evaluate(() => {
|
||||
const grid = document.querySelector('.adm-grid');
|
||||
@@ -377,16 +388,23 @@ const railOf = () =>
|
||||
}
|
||||
const on = document.querySelector('.lib-rail-item.on');
|
||||
return {
|
||||
kinds: items.map((b) => b.getAttribute('data-kind') ?? ''),
|
||||
keys: items.map((b) => (b.getAttribute('data-key') ?? '').replace('lib-rail-', '')),
|
||||
// The day a press on the entry sends the wall to, which the entry carries.
|
||||
ats: items.map((b) => b.getAttribute('data-at') ?? ''),
|
||||
labels: items.map((b) => b.textContent.trim()),
|
||||
onKind: on?.getAttribute('data-kind') ?? null,
|
||||
on: on ? (on.getAttribute('data-key') ?? '').replace('lib-rail-', '') : null,
|
||||
fold,
|
||||
scroll: Math.round(rail?.scrollTop ?? 0),
|
||||
tops: items.map((b) => b.offsetTop),
|
||||
heights: items.map((b) => b.offsetHeight),
|
||||
box: rail?.clientHeight ?? 0,
|
||||
whole: rail?.scrollHeight ?? 0,
|
||||
};
|
||||
});
|
||||
const monthsOf = (rail) => rail.keys.filter((_, i) => rail.kinds[i] === 'month');
|
||||
const yearsOf = (rail) => rail.keys.filter((_, i) => rail.kinds[i] === 'year');
|
||||
const stops = [0.15, 0.35, 0.55, 0.75, 0.95];
|
||||
const seenMonths = new Set();
|
||||
let scrolledBad = [];
|
||||
@@ -401,13 +419,13 @@ for (const at of stops) {
|
||||
const audit = await dayAudit();
|
||||
scrolledBad = scrolledBad.concat(audit.bad);
|
||||
const rail = await railOf();
|
||||
// The entry the rail says the eye is in has to own the day at the fold — the month
|
||||
// its key begins with — and the entry has to be inside the rail's own box: a rail
|
||||
// read for the whole shelf and standing on its own top is a rail that says nothing.
|
||||
if (!rail.fold || !rail.on || rail.fold.slice(0, 7) !== rail.on.slice(0, 7)) {
|
||||
railBad.push({ at, fold: rail.fold, on: rail.on });
|
||||
// The month the rail says the eye is in has to own the day at the fold, and the
|
||||
// entry has to be inside the rail's own box: a rail read for the whole shelf and
|
||||
// standing on its own top is a rail that says nothing.
|
||||
if (!rail.fold || !rail.on || rail.onKind !== 'month' || rail.fold.slice(0, 7) !== rail.on.slice(0, 7)) {
|
||||
railBad.push({ at, fold: rail.fold, on: rail.on, kind: rail.onKind });
|
||||
} else {
|
||||
seenMonths.add(rail.on.slice(0, 7));
|
||||
seenMonths.add(rail.on);
|
||||
const i = rail.keys.indexOf(rail.on);
|
||||
const y = (rail.tops[i] ?? 0) - rail.scroll;
|
||||
if (y < -2 || y + (rail.heights[i] ?? 0) > rail.box + 2) railBad.push({ at, on: rail.on, y, box: rail.box });
|
||||
@@ -419,33 +437,72 @@ step(
|
||||
JSON.stringify(scrolledBad.slice(0, 2))
|
||||
);
|
||||
const rail1 = await railOf();
|
||||
const railMonths = [...new Set(rail1.keys.map((k) => k.slice(0, 7)))].sort();
|
||||
step('the rail holds the months of the shelf', railMonths.join() === [...MONTHS].sort().join(), `${rail1.keys.join(' ')}`);
|
||||
const months1 = monthsOf(rail1);
|
||||
// The rail carries the shelf's own order — the reader's sort, months down the wall as
|
||||
// the wall holds them — so what is compared here is the set of months, not the order
|
||||
// twice over.
|
||||
step(
|
||||
'the rail holds the shelf by the year and the month, and by nothing finer',
|
||||
[...months1].sort().join() === [...MONTHS].sort().join() &&
|
||||
[...yearsOf(rail1)].sort().join() === [...YEARS].sort().join() &&
|
||||
rail1.kinds.every((k) => k === 'year' || k === 'month'),
|
||||
`${yearsOf(rail1).length} years, ${months1.length} months of ${MONTHS.length}`
|
||||
);
|
||||
step(
|
||||
'and the entry it reads follows the eye down the shelf',
|
||||
railBad.length === 0 && seenMonths.size === MONTHS.length,
|
||||
railBad.length === 0 && seenMonths.size >= 3,
|
||||
`${seenMonths.size} of ${MONTHS.length} months read, ${JSON.stringify(railBad.slice(0, 2))}`
|
||||
);
|
||||
|
||||
// And the press: the month's own first day is where the wall has to stand, its header
|
||||
// just under the wall's top edge, and the entry pressed is the one read afterwards.
|
||||
const jumped = rail1.keys[1];
|
||||
await page.click(`[data-key="lib-rail-${jumped}"]`);
|
||||
await page.waitForTimeout(700);
|
||||
const landed = await page.evaluate((key) => {
|
||||
const grid = document.querySelector('.adm-grid');
|
||||
const section = grid.querySelector(`[data-key="lib-day-${key}"]`);
|
||||
const box = grid.getBoundingClientRect();
|
||||
const on = document.querySelector('.lib-rail-item.on');
|
||||
return {
|
||||
top: section ? Math.round(section.getBoundingClientRect().top - box.top) : null,
|
||||
on: on ? (on.getAttribute('data-key') ?? '').replace('lib-rail-', '') : null,
|
||||
};
|
||||
}, jumped);
|
||||
step(
|
||||
'a press on a month takes the wall to that day',
|
||||
landed.top !== null && Math.abs(landed.top - 6) <= 6 && landed.on === jumped,
|
||||
`header at ${landed.top}, entry ${landed.on}`
|
||||
'the rail is longer than the wall is tall, and scrolls under the reader',
|
||||
rail1.whole > rail1.box,
|
||||
`${rail1.whole}px of rail in ${rail1.box}px of box`
|
||||
);
|
||||
|
||||
// And the press: the shelf's own first day of the month is where the wall has to
|
||||
// stand, its header just under the wall's top edge, the entry pressed is the month
|
||||
// read afterwards, and the rail brings that entry into its own box.
|
||||
const press = async (key) => {
|
||||
await page.click(`[data-key="lib-rail-${key}"]`);
|
||||
await page.waitForTimeout(700);
|
||||
return page.evaluate((k) => {
|
||||
const grid = document.querySelector('.adm-grid');
|
||||
const rail = document.querySelector('.lib-rail');
|
||||
const button = document.querySelector(`[data-key="lib-rail-${k}"]`);
|
||||
const at = button?.getAttribute('data-at') ?? null;
|
||||
const section = at ? grid.querySelector(`[data-key="lib-day-${at}"]`) : null;
|
||||
const on = document.querySelector('.lib-rail-item.on');
|
||||
const y = button ? button.offsetTop - rail.scrollTop : null;
|
||||
return {
|
||||
at,
|
||||
top: section ? Math.round(section.getBoundingClientRect().top - grid.getBoundingClientRect().top) : null,
|
||||
cards: section ? section.querySelectorAll('.adm-card').length : 0,
|
||||
on: on ? (on.getAttribute('data-key') ?? '').replace('lib-rail-', '') : null,
|
||||
onKind: on?.getAttribute('data-kind') ?? null,
|
||||
seen: y !== null && y >= -2 && y + (button?.offsetHeight ?? 0) <= rail.clientHeight + 2,
|
||||
};
|
||||
}, key);
|
||||
};
|
||||
const jumped = months1[Math.floor(months1.length / 2)];
|
||||
const landed = await press(jumped);
|
||||
step(
|
||||
'a press on a month stands the wall on that month, and the rail on its entry',
|
||||
landed.top !== null &&
|
||||
Math.abs(landed.top - 6) <= 6 &&
|
||||
landed.on === jumped &&
|
||||
landed.onKind === 'month' &&
|
||||
landed.seen,
|
||||
`header at ${landed.top} of ${landed.at}, entry ${landed.on}, in rail ${landed.seen}`
|
||||
);
|
||||
|
||||
// The year is a head and not a month of the shelf: a press on it stands the wall on
|
||||
// the first month the shelf holds of that year, and the eye then reads that month.
|
||||
const yearRow = yearsOf(rail1)[1];
|
||||
const overYear = await press(yearRow);
|
||||
step(
|
||||
'a press on a year stands the wall on the first month it holds',
|
||||
overYear.top !== null && Math.abs(overYear.top - 6) <= 6 && overYear.at?.slice(0, 4) === yearRow && overYear.on === overYear.at?.slice(0, 7),
|
||||
`year ${yearRow}: header at ${overYear.top} of ${overYear.at}, entry ${overYear.on}`
|
||||
);
|
||||
|
||||
// The shelf walked to its own end, a screen at a time — a jump straight to the bottom
|
||||
@@ -483,6 +540,21 @@ const walked = await page.evaluate(async () => {
|
||||
step('the shelf is walked to its own end', walked.reached, `stopped at ${walked.top} of ${walked.room}`);
|
||||
step('and stands on nothing there', walked.gap !== null && walked.gap <= 16, `${walked.gap}px of nothing under the last card`);
|
||||
|
||||
// What the wall mints, read at the far end of the shelf: the tiles are the frames of
|
||||
// the days around the eye, and a shelf walked end to end is not a shelf of tiles held
|
||||
// end to end. A frame the wall holds no tile for draws nothing at all — a folder off
|
||||
// the disk is a file only this page can open — so the count of blob-backed images is
|
||||
// the count of tiles the wall is holding.
|
||||
const held = await page.evaluate(() => ({
|
||||
cards: document.querySelectorAll('.adm-grid .adm-card').length,
|
||||
tiles: document.querySelectorAll('.adm-grid img[src^="blob:"]').length,
|
||||
}));
|
||||
step(
|
||||
'the wall mints the tiles around the eye and not the whole shelf',
|
||||
held.tiles > 0 && held.tiles < held.cards / 2,
|
||||
`${held.tiles} tiles held of ${held.cards} cards`
|
||||
);
|
||||
|
||||
// A wall resized under its own eye: the class of input that cost the screen once, when
|
||||
// the wall measured itself off itself. It re-columns, the rail re-reads, and the reader
|
||||
// is still standing in the library after it.
|
||||
@@ -518,32 +590,17 @@ await page.waitForFunction(
|
||||
await page.click('[data-key="lib-view-grid"]');
|
||||
await page.waitForTimeout(1500);
|
||||
const rail3 = await railOf();
|
||||
const smallMonth = SMALL_DAY_KEYS[0].slice(0, 7);
|
||||
step(
|
||||
'a shelf of one month is read by the day',
|
||||
[...rail3.keys].sort().join() === [...SMALL_DAY_KEYS].sort().join() && rail3.keys.length === SMALL_DAY_KEYS.length,
|
||||
'a shelf of one month is still read by the year and the month',
|
||||
rail3.kinds.join() === 'year,month' && yearsOf(rail3).length === 1 && monthsOf(rail3).join() === smallMonth,
|
||||
`${rail3.labels.join(' ')} / ${rail3.keys.join(' ')}`
|
||||
);
|
||||
const plainDay = SMALL_DAY_KEYS[0];
|
||||
await page.click(`[data-key="lib-rail-${plainDay}"]`);
|
||||
await page.waitForTimeout(700);
|
||||
const small = await page.evaluate((key) => {
|
||||
const grid = document.querySelector('.adm-grid');
|
||||
const section = grid.querySelector(`[data-key="lib-day-${key}"]`);
|
||||
const box = grid.getBoundingClientRect();
|
||||
const on = document.querySelector('.lib-rail-item.on');
|
||||
return {
|
||||
top: section ? Math.round(section.getBoundingClientRect().top - box.top) : null,
|
||||
cards: section ? section.querySelectorAll('.adm-card').length : 0,
|
||||
on: on?.textContent.trim() ?? null,
|
||||
};
|
||||
}, plainDay);
|
||||
const overMonth = await press(smallMonth);
|
||||
step(
|
||||
'a press on a day takes the wall to it, whole',
|
||||
small.top !== null &&
|
||||
Math.abs(small.top - 6) <= 6 &&
|
||||
small.cards === SMALL_EACH &&
|
||||
small.on === String(Number(plainDay.slice(8, 10))),
|
||||
`header at ${small.top}, ${small.cards} cards, entry ${small.on}`
|
||||
'and a press on it stands the wall on that month, whole',
|
||||
overMonth.top !== null && Math.abs(overMonth.top - 6) <= 6 && overMonth.cards === SMALL_EACH && overMonth.on === smallMonth,
|
||||
`header at ${overMonth.top}, ${overMonth.cards} cards, entry ${overMonth.on}`
|
||||
);
|
||||
|
||||
await page.screenshot({ path: process.env.SHOT ?? '/tmp/wall-check.png' }).catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user