fix(library): the wall counts its columns off the layout, not off the run it drew
A column count read back off the cards the window drew is an input the window feeds itself with: the drawn run is a whole number of rows, and those rows were read back into the count. A wall hidden behind the studio read nothing but zeroes off its own cards and reset itself to the top on the way out. Either way a window was written from a window, pass after pass, until React gave up with #185 and the whole screen went white — there is nothing between that throw and the floor. The count now comes off the grid, which is the same read the settle effect above already scrolls the wall by; a wall with no box is not measured at all, and is measured on the frame it comes back up; the one reset is written off the shelf's own length rather than off the last window; the scroller is not a place the browser may anchor; and a chain of writes past four is cut until something outside moves — a scroll, a resize, or another shelf.
This commit is contained in:
@@ -197,6 +197,12 @@ const WALL_LANES = 2;
|
||||
// What `.adm-grid` leaves between its rows. A spacer has to stand the same
|
||||
// distance past the last row it replaces as the row itself would have.
|
||||
const WALL_GAP = 12;
|
||||
// How many windows the wall may write in a row before it is talking to itself.
|
||||
// A window read off the window before it is a loop, and React answers the fifty
|
||||
// turns of one with #185 and a blank screen; four is more than an honest change
|
||||
// needs — a re-column takes two passes — and the count is handed back by the
|
||||
// first scroll, resize or change of shelf the reader makes.
|
||||
const WALL_CARRIES = 4;
|
||||
// The hours a frame's own clock can read, and what the time filter offers: the
|
||||
// whole day in the 24-hour form a camera writes into EXIF.
|
||||
const HOURS = Array.from({ length: 24 }, (_, i) => i);
|
||||
@@ -3088,32 +3094,60 @@ function useWallWindow(count: number, on: boolean): { ref: RefObject<HTMLDivElem
|
||||
// The window is only ever set through here: a `setWin` the wall already
|
||||
// stands at is a render bought for nothing, and this is measured after every
|
||||
// render — that is the loop (React #185).
|
||||
// And the second guard, for a reader the first one cannot see: a window
|
||||
// written off the window before it is a window that can write itself, and
|
||||
// whatever the shelf does between the two there are only so many turns of
|
||||
// that before React gives up with #185 — a blank screen, this app having no
|
||||
// error boundary between it and the floor. `ponytail:` past the count the
|
||||
// wall stands still, one window behind, until something outside moves — the
|
||||
// next scroll, resize, or count hands the pen straight back.
|
||||
let carries = 0;
|
||||
const show = (next: { from: number; to: number; above: number; below: number }) => {
|
||||
const prev = winRef.current;
|
||||
if (prev.from === next.from && prev.to === next.to && prev.above === next.above && prev.below === next.below) return;
|
||||
if (carries >= WALL_CARRIES) return;
|
||||
carries++;
|
||||
winRef.current = next;
|
||||
setWin(next);
|
||||
};
|
||||
const measure = () => {
|
||||
const cards = el.querySelectorAll<HTMLElement>(':scope > .adm-card');
|
||||
const first = cards[0];
|
||||
let cols = 0;
|
||||
if (first) while (cols < cards.length && cards[cols].offsetTop === first.offsetTop) cols++;
|
||||
// One row, or a wall not yet drawn: open on the first screenful and let the
|
||||
// next pass read the step off what it drew.
|
||||
if (cards.length <= cols) {
|
||||
const to = Math.min(count, 24);
|
||||
show({ from: 0, to, above: 0, below: 0 });
|
||||
// A wall that is not standing has no box to measure: the library is
|
||||
// `display:none` while the studio is up, and a window read off it would be
|
||||
// a window read off nothing — every card reports `offsetTop` 0 and every
|
||||
// scroll 0 — which threw the shelf back to its top every time the studio
|
||||
// handed back to it. It is read on the frame it comes back up, which is a
|
||||
// box change.
|
||||
if (!el.clientHeight) return;
|
||||
// And a window that is nowhere on the shelf stands nowhere: outside a shelf
|
||||
// that shrank under it — another folder, a filter, a shorter roll — or empty
|
||||
// while the shelf is not, which is the first paint, before the catalogue has
|
||||
// landed. This is the one reset, and it is written off the shelf's own
|
||||
// length rather than off the cards the last window drew, which is what made
|
||||
// the reset a window the window fed.
|
||||
const held = winRef.current.to - winRef.current.from;
|
||||
if (held <= 0 || winRef.current.from >= count || winRef.current.to > count) {
|
||||
show({ from: 0, to: Math.min(count, 24), above: 0, below: 0 });
|
||||
return;
|
||||
}
|
||||
// How many columns is the grid's own business, read off its layout and not
|
||||
// counted off the cards the window drew: the drawn run is a whole number of
|
||||
// rows, those rows were read back into the count, and a count read off the
|
||||
// run is a count the window feeds itself with. This is the same read the
|
||||
// settle effect above scrolls the wall by.
|
||||
const cols = Math.max(1, getComputedStyle(el).gridTemplateColumns.split(' ').length);
|
||||
// The run the step below is read off. A run of a single row is no run at
|
||||
// all — that is the last row of a shelf scrolled to its end — and a wall
|
||||
// that has drawn nothing is nothing to read: the state's own first
|
||||
// screenful stands until there is one.
|
||||
const cards = el.querySelectorAll<HTMLElement>(':scope > .adm-card');
|
||||
const rowsDrawn = Math.floor(cards.length / cols);
|
||||
if (rowsDrawn < 2) return;
|
||||
// The step from one row to the next, taken as the drawn run's own average.
|
||||
// The cards do NOT all stand the same height — a caption that wraps makes
|
||||
// its row taller — and `offsetTop` is rounded to whole pixels, so the
|
||||
// smallest step would be a pixel short on every one of thousands of rows.
|
||||
// The average of the rows in hand is the honest guess at the ones that are
|
||||
// not in hand.
|
||||
const rowsDrawn = Math.floor(cards.length / cols);
|
||||
if (rowsDrawn < 2) return;
|
||||
// The step is read off the drawn rows the first time the wall stands at this
|
||||
// many columns, and kept until it stands at another: the average of a run of
|
||||
// rows moves as the run grows, so a step re-read on every pass made the window
|
||||
@@ -3144,13 +3178,16 @@ function useWallWindow(count: number, on: boolean): { ref: RefObject<HTMLDivElem
|
||||
show({ from, to, above, below });
|
||||
};
|
||||
measureRef.current = measure;
|
||||
const onScroll = () => measure();
|
||||
el.addEventListener('scroll', onScroll, { passive: true });
|
||||
const ro = new ResizeObserver(onScroll);
|
||||
const onOutside = () => {
|
||||
carries = 0;
|
||||
measure();
|
||||
};
|
||||
el.addEventListener('scroll', onOutside, { passive: true });
|
||||
const ro = new ResizeObserver(onOutside);
|
||||
ro.observe(el);
|
||||
return () => {
|
||||
measureRef.current = () => {};
|
||||
el.removeEventListener('scroll', onScroll);
|
||||
el.removeEventListener('scroll', onOutside);
|
||||
ro.disconnect();
|
||||
};
|
||||
}, [count, on]);
|
||||
|
||||
@@ -1342,7 +1342,7 @@ button.hint:disabled { cursor: default; }
|
||||
}
|
||||
/* All thumbs: the wall scrolls down inside the stage, and its own scrollbar on
|
||||
the right edge is what reaches the frames below. */
|
||||
.lib-root .adm-grid { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 6px; }
|
||||
.lib-root .adm-grid { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 6px; overflow-anchor: none; }
|
||||
/* What the thumbnail wall is read through: the four selects and the count of what
|
||||
is left. One row above the wall, and it does not scroll with it. */
|
||||
.lib-root .adm-stage > .lib-filters,
|
||||
|
||||
Reference in New Issue
Block a user