web: count a roll as it is read, and open a frame without losing the count

Two holes left by the last change. A frame is opened with `window.location.href`
rather than a link, so the studio was still handed a fresh page and the scan with
it — every route out of the catalogue now goes through `go()`, which pushes the
address instead of reloading while a scan is in flight. And a row counted what was
filed away rather than what had been read, so it sat at zero for the whole of a
first scan: the catalogue's frames only reach IndexedDB in one batch at the end.

`ScanProgress.counts` carries the frames the scan has reached, per row, off the
scan's own bookkeeping, and the column reads it while the scan runs — the row
under the reader's eye moves as the roll is read, and the toolbar's line stays the
whole picture. A re-read counts from the start, which is what it is doing.

The regression check grows the two: a frame opened mid-scan from the catalogue
(of a roll it already holds) has to stay in the page, and the row has to count.
This commit is contained in:
2026-09-28 20:39:51 +07:00
parent 79b0d0db86
commit 1cb2618fd7
5 changed files with 113 additions and 17 deletions
+25 -2
View File
@@ -253,6 +253,13 @@ export interface ScanProgress {
total: number;
done: number;
added: number;
// How many frames the scan has read out of each row of the tree, keyed the way
// the tree spells a row (`folder` for the picked folder, `folder/sub/dir` for
// the rest) and counted the way the rows count: a frame sits on its own row and
// on every row above it. A fresh object every frame, so a screen drawing the
// column sees a change; the frames themselves only reach the catalogue in one
// batch at the end, which would leave every row reading zero until then.
counts: Record<string, number>;
// Every folder the walk has been into so far, a fresh list each time: the
// column draws them while the frames under them are still being read. Not
// filed away — a folder name is worth nothing once the scan it came from ends.
@@ -284,7 +291,22 @@ export async function scanFolder(
// whole scan so a folder that has gone from the disk goes from the column too.
const names: string[] = [];
const found = new Set<string>();
const progress: ScanProgress = { folder: folder.name, total: 0, done: 0, added: 0, dirs: [] };
const progress: ScanProgress = { folder: folder.name, total: 0, done: 0, added: 0, dirs: [], counts: {} };
const counts: Record<string, number> = {};
// Count the frame the moment the scan gets to it, before it knows whether the
// frame is new: the rows say how far the reading has come, not what it kept.
const count = (rel: string) => {
const cut = rel.lastIndexOf('/');
counts[folder.name] = (counts[folder.name] ?? 0) + 1;
let path = '';
for (const part of (cut < 0 ? '' : rel.slice(0, cut)).split('/')) {
if (!part) continue;
path = path ? `${path}/${part}` : part;
const key = `${folder.name}/${path}`;
counts[key] = (counts[key] ?? 0) + 1;
}
progress.counts = { ...counts };
};
// The folders this scan has walked into, kept for the whole of it: the column
// is redrawn from this list, so it only ever grows.
const dirs: LibraryDir[] = [];
@@ -333,6 +355,7 @@ export async function scanFolder(
break;
}
progress.done++;
count(rel);
try {
const file = await handle.getFile();
const id = photoId(folder.name, rel);
@@ -423,7 +446,7 @@ export function startScan(folder: LibraryFolder): Promise<ScanProgress> {
if (live) return Promise.reject(new Error('a scan is already running'));
const session: ScanSession = {
folder: folder.name,
progress: { folder: folder.name, total: 0, done: 0, added: 0, dirs: [] },
progress: { folder: folder.name, total: 0, done: 0, added: 0, dirs: [], counts: {} },
stop: false,
jump: null,
};