library: stand a frame up in the catalogue, wall, strip and studio

The library stage carried no way to correct a frame shot on its side, and
nothing kept the correction. A quarter turn now rides on the catalogue row
(`rot`, beside `star`, carried over by a rescan), the two chips under the
preview step it, and the tile, the strip thumbnail and the stage all read it
through one bake so a frame is never stood on its side in one place and
upright in another. The studio opens a frame at that angle and a turn made
there is filed straight back onto the row, so either end of the turn is
what the frame comes up at next time.
This commit is contained in:
2026-10-06 17:22:56 +07:00
parent 651e0ad09a
commit 2e06e5ab5d
5 changed files with 159 additions and 4 deletions
+19
View File
@@ -102,6 +102,11 @@ export interface LibraryPhoto {
// it one. It belongs to the catalogue and not to the disk — nothing about the
// file changes — so a rescan carries it over, the way it carries `addedAt`.
star?: number;
// The quarter turn the reader gave the frame so it stands the way they saw
// it, 0 or absent for a frame they have not turned. Like the score it belongs
// to the catalogue rather than to the disk — nothing about the file changes —
// and a rescan carries it over; the studio opens a frame at this angle.
rot?: 0 | 90 | 180 | 270;
addedAt: number;
}
@@ -913,6 +918,7 @@ export async function scanFolder(
size: file.size,
mtime: file.lastModified,
star: seen?.star,
rot: seen?.rot,
addedAt: seen?.addedAt ?? Date.now(),
};
batch.push(row);
@@ -1560,6 +1566,19 @@ export async function setStar(id: string, star: number): Promise<void> {
}
}
// The quarter turn a frame stands at, in the same read-and-put the score uses:
// the catalogue's own, carried over by a rescan, and what the studio opens the
// frame at.
export async function setRotation(id: string, rot: 0 | 90 | 180 | 270): Promise<void> {
const photo = await getPhoto(id);
if (!photo) return;
try {
await ask(PHOTOS, 'readwrite', (s) => s.put({ ...photo, rot }));
} catch {
// Private mode: the turn lives no longer than the visit.
}
}
// The frame itself, at last: nothing is read from the disk until this runs, so
// the catalogue costs thumbnails and no more.
export async function readPhotoFile(photo: LibraryPhoto): Promise<File> {