library: the screen reads the catalogue back when a batch lands, not on a 700ms timer, and a tile keeps the object URL it was given
This commit is contained in:
@@ -13,6 +13,10 @@
|
||||
without Safari's chrome, 'black-translucent' gives it the app's black bars,
|
||||
and 'title' is the name under the icon. -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!-- The same thing under the name it now has: Chrome warns that the `apple-`
|
||||
spelling is deprecated, and iOS Safari still reads only that one, so the
|
||||
installed app carries both. -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="RecipesCam" />
|
||||
|
||||
@@ -153,10 +153,6 @@ export function Library() {
|
||||
// holding has moved on.
|
||||
const live = scanSession();
|
||||
const [, bump] = useReducer((n: number) => n + 1, 0);
|
||||
// When the catalogue was last read back while a scan runs: the refresh reads the
|
||||
// whole catalogue, so it keeps to one every few hundred milliseconds rather than
|
||||
// one per frame.
|
||||
const readAt = useRef(0);
|
||||
const [note, setNote] = useState<string | null>(null);
|
||||
// The folder menu: what a right click opens, and where. No folder means the
|
||||
// right click landed on the empty part of the column, which is the way in for
|
||||
@@ -186,6 +182,13 @@ export function Library() {
|
||||
// one that says the scan is through — is recognisable as an end rather than
|
||||
// as a screen with nothing to do.
|
||||
let was: string | null = null;
|
||||
// How much of the roll the catalogue was last read back at, and the shape the
|
||||
// column was last drawn at: reading the whole catalogue back costs every
|
||||
// thumbnail in it, so it waits for a batch to have landed rather than for a
|
||||
// frame to have been read, and the column is redrawn only when a pass has
|
||||
// walked into something new.
|
||||
let wrote = -1;
|
||||
let drew = '';
|
||||
const watching = () => {
|
||||
bump();
|
||||
const session = scanSession();
|
||||
@@ -202,9 +205,17 @@ export function Library() {
|
||||
was = folder;
|
||||
if (!session) return;
|
||||
const { progress } = session;
|
||||
setDirs(progress.dirs);
|
||||
if (progress.done === 0 || Date.now() - readAt.current > 700) {
|
||||
readAt.current = Date.now();
|
||||
const shape = progress.dirs.length ? `${progress.dirs.length}:${progress.dirs[progress.dirs.length - 1].id}` : '';
|
||||
if (shape !== drew) {
|
||||
drew = shape;
|
||||
setDirs(progress.dirs);
|
||||
}
|
||||
// A window beside the reading is handed the progress of another build, which
|
||||
// may have no such count: the frames it has stored are then read back on a
|
||||
// frame, as they were before there was one.
|
||||
const written = progress.written ?? progress.done;
|
||||
if (written !== wrote) {
|
||||
wrote = written;
|
||||
void reload();
|
||||
}
|
||||
};
|
||||
@@ -1140,13 +1151,38 @@ export function Library() {
|
||||
// the panels paint.
|
||||
function useThumbUrls(photos: LibraryPhoto[]): Record<string, string> {
|
||||
const [urls, setUrls] = useState<Record<string, string>>({});
|
||||
// The URL a row was given, kept for as long as the row is on screen. A scan lands
|
||||
// in batches, and each batch hands the screen the whole list again — so a URL
|
||||
// made afresh per list is a picture the browser decodes afresh per batch:
|
||||
// thousands of bitmaps over a roll, for tiles that have not moved. A row that
|
||||
// leaves the list takes its URL with it.
|
||||
const made = useRef(new Map<string, string>());
|
||||
useEffect(() => {
|
||||
const next: Record<string, string> = {};
|
||||
for (const photo of photos) if (photo.thumb) next[photo.id] = URL.createObjectURL(photo.thumb);
|
||||
for (const photo of photos) {
|
||||
if (!photo.thumb) continue;
|
||||
const had = made.current.get(photo.id);
|
||||
if (had) next[photo.id] = had;
|
||||
else {
|
||||
const url = URL.createObjectURL(photo.thumb);
|
||||
made.current.set(photo.id, url);
|
||||
next[photo.id] = url;
|
||||
}
|
||||
}
|
||||
for (const [id, url] of made.current) {
|
||||
if (id in next) continue;
|
||||
URL.revokeObjectURL(url);
|
||||
made.current.delete(id);
|
||||
}
|
||||
setUrls(next);
|
||||
return () => {
|
||||
for (const url of Object.values(next)) URL.revokeObjectURL(url);
|
||||
};
|
||||
}, [photos]);
|
||||
// The screen going away takes every URL with it.
|
||||
useEffect(
|
||||
() => () => {
|
||||
for (const url of made.current.values()) URL.revokeObjectURL(url);
|
||||
made.current.clear();
|
||||
},
|
||||
[]
|
||||
);
|
||||
return urls;
|
||||
}
|
||||
|
||||
@@ -320,6 +320,12 @@ export interface ScanProgress {
|
||||
total: number;
|
||||
done: number;
|
||||
added: number;
|
||||
// How many frames have reached the catalogue. `done` counts the frames the
|
||||
// reading has got to, which is nearly all of them long before they are stored,
|
||||
// so a screen that read the catalogue back on `done` would read it again every
|
||||
// frame — and reading it back means every thumbnail in it again. This moves a
|
||||
// batch at a time, which is exactly when there is something new to read.
|
||||
written: 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
|
||||
@@ -478,10 +484,13 @@ export async function scanFolder(
|
||||
total: 0,
|
||||
done: 0,
|
||||
added: 0,
|
||||
written: 0,
|
||||
dirs: [],
|
||||
counts: {},
|
||||
};
|
||||
progress.folder = folder.name;
|
||||
// A position written down by an older visit has no count to carry on from.
|
||||
progress.written ??= 0;
|
||||
const found = new Set(progress.dirs.map((d) => d.id));
|
||||
const counts: Record<string, number> = progress.counts;
|
||||
// Count the frame the moment the scan gets to it, before it knows whether the
|
||||
@@ -538,6 +547,7 @@ export async function scanFolder(
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
progress.written += rows.length;
|
||||
write();
|
||||
};
|
||||
// One frame, end to end: its bytes, its tile, its shutter time, and the row the
|
||||
@@ -848,7 +858,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: [], counts: {} },
|
||||
progress: { folder: folder.name, total: 0, done: 0, added: 0, written: 0, dirs: [], counts: {} },
|
||||
stop: false,
|
||||
jump: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user