feat(library): RESET SETTING puts a frame back to the tile it came in with
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
remoteUrl,
|
||||
removeFolder,
|
||||
renameFolder,
|
||||
resetEdit,
|
||||
scanAsked,
|
||||
scanBusy,
|
||||
scanQueue,
|
||||
@@ -1277,6 +1278,23 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
go(`/app?lib=${encodeURIComponent(photo.id)}`);
|
||||
}, [t]);
|
||||
|
||||
// Put a frame back: the recipe goes, the graded tile goes with it, and the frame
|
||||
// is drawn again from the negative. Everything this screen holds a copy of goes
|
||||
// too — the URL it is painting and the mark that says the frame was ever touched
|
||||
// — which is what makes the wall draw the tile it is about to be handed.
|
||||
const resetSetting = useCallback(async (photo: LibraryPhoto) => {
|
||||
const blob = await resetEdit(photo).catch(() => null);
|
||||
dropThumbUrl(photo.id);
|
||||
if (blob) cacheObjectUrl(photo.id, blob);
|
||||
setEdited((prev) => {
|
||||
if (!prev.has(photo.id)) return prev;
|
||||
const next = new Set(prev);
|
||||
next.delete(photo.id);
|
||||
return next;
|
||||
});
|
||||
setTilesAt((n) => n + 1);
|
||||
}, []);
|
||||
|
||||
const rootFolder = useMemo(() => {
|
||||
if (!node) return folders[0] ?? null;
|
||||
const normNode = normPath(node);
|
||||
@@ -3163,6 +3181,24 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* The frame's own look, taken back: what the studio filed for it and the
|
||||
tile it drew from it, so the shelf shows the picture the frame came in
|
||||
with — the tile as it was first drawn, and the preview the file's own
|
||||
develop gives. */}
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key="lib-photo-reset"
|
||||
onClick={() => {
|
||||
const photo = photoMenu.photo;
|
||||
setPhotoMenu(null);
|
||||
void resetSetting(photo);
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">↺</span>
|
||||
{t('lib.reset')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
@@ -3378,6 +3414,16 @@ function cacheObjectUrl(id: string, blob: Blob): string {
|
||||
return url;
|
||||
}
|
||||
|
||||
// A tile the shelf is to draw again from the negative — the studio's grading taken
|
||||
// back — is dropped here, the URL with it: the row is keyed on the tile the store
|
||||
// holds, and a copy kept would answer every read after with the graded one.
|
||||
function dropThumbUrl(id: string): void {
|
||||
const held = globalThumbUrls.get(id);
|
||||
if (!held) return;
|
||||
URL.revokeObjectURL(held.url);
|
||||
globalThumbUrls.delete(id);
|
||||
}
|
||||
|
||||
function useThumbUrls(photos: LibraryPhoto[], also?: LibraryPhoto | null, stamp?: number): Record<string, string> {
|
||||
const list = useMemo(() => (also && !photos.some((p) => p.id === also.id) ? [...photos, also] : photos), [photos, also]);
|
||||
|
||||
|
||||
@@ -2249,6 +2249,14 @@ export function makeThumb(photo: LibraryPhoto): Promise<Blob | null> {
|
||||
async function makeOneThumb(photo: LibraryPhoto): Promise<Blob | null> {
|
||||
const kept = await getPhotoThumb(photo.id);
|
||||
if (kept) return kept;
|
||||
return tileFrom(photo);
|
||||
}
|
||||
|
||||
// The tile the frame's own source gives, with neither keeper of a graded tile
|
||||
// asked. The shelf is not the caller: a frame put back wears the picture it was
|
||||
// first drawn with, and both the store and the backup still hold the look that
|
||||
// was just taken back.
|
||||
async function tileFrom(photo: LibraryPhoto): Promise<Blob | null> {
|
||||
// A server's frame has no file to open: its tile is the one Immich drew, asked
|
||||
// for here and kept, so the second look comes out of the store and the sync's
|
||||
// own download is only paid once.
|
||||
@@ -2516,6 +2524,34 @@ export async function saveEdit(id: string, state: StudioState): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Put a frame back to the picture it was first drawn with: the recipe the studio
|
||||
// filed goes, and the tile it graded from the negative goes with it — the store's
|
||||
// copy and the backup's, which is the same look. The tile is made here rather than
|
||||
// left to the shelf to make, because the shelf's way in asks the backup first and
|
||||
// the backup is holding the look just taken back. Written without `tell`: the
|
||||
// reader's own click is what redraws this frame, and the shelf has no business
|
||||
// being told about a rewrite it asked for.
|
||||
export async function resetEdit(photo: LibraryPhoto): Promise<Blob | null> {
|
||||
try {
|
||||
const db = await openDb();
|
||||
const hasThumbs = db.objectStoreNames.contains(THUMBS);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction([EDITS, ...(hasThumbs ? [THUMBS] : [])], 'readwrite');
|
||||
tx.objectStore(EDITS).delete(photo.id);
|
||||
if (hasThumbs) tx.objectStore(THUMBS).delete(photo.id);
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
} catch {
|
||||
// Private mode, or nothing filed for this frame: there is no look to take
|
||||
// back, and the tile below is made all the same.
|
||||
}
|
||||
thumbCache.delete(photo.id);
|
||||
const made = await tileFrom(photo);
|
||||
if (made) await writeThumb(photo.id, made, false);
|
||||
return made;
|
||||
}
|
||||
|
||||
export async function loadEdit(id: string): Promise<LibraryEdit | null> {
|
||||
try {
|
||||
// A row filed before the whole look was kept carries the recipe alone: lift
|
||||
|
||||
@@ -276,6 +276,7 @@ export const en: Dict = {
|
||||
'lib.mergeHdr': 'HDR Merge (Anti-ghosting)…',
|
||||
'lib.copiedPath': 'Copied location to clipboard: {path}',
|
||||
'lib.edited': 'edited',
|
||||
'lib.reset': 'Reset to default',
|
||||
'lib.size': '{mb} MB',
|
||||
'lib.count': '{n} photos',
|
||||
// What the thumbnail view is read through. The score a frame is given is the
|
||||
|
||||
@@ -288,6 +288,7 @@ export const vi = {
|
||||
'lib.mergeHdr': 'Ghép HDR (Chống nhòe)…',
|
||||
'lib.copiedPath': 'Đã sao chép đường dẫn: {path}',
|
||||
'lib.edited': 'đã chỉnh',
|
||||
'lib.reset': 'Đặt lại ảnh về mặc định',
|
||||
'lib.size': '{mb} MB',
|
||||
'lib.count': '{n} ảnh',
|
||||
// Cách đọc bày ảnh: điểm sao là của người xem, nên chữ nói đúng thế — "từ n
|
||||
|
||||
Reference in New Issue
Block a user