feat(library): smart subfolder auto-link and instant handle reconnection
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
pickFolder,
|
||||
readPhotoFile,
|
||||
readPhotos,
|
||||
reconnectPhotosForFolders,
|
||||
removeFolder,
|
||||
renameFolder,
|
||||
scanAsked,
|
||||
@@ -713,6 +714,7 @@ export function Library() {
|
||||
async (folder: LibraryFolder) => {
|
||||
if (await ensurePermission(folder.handle)) {
|
||||
setReady((prev) => new Set(prev).add(folder.name));
|
||||
await reconnectPhotosForFolders(folder.handle);
|
||||
await scan(folder);
|
||||
} else {
|
||||
setNote(t('lib.failed'));
|
||||
|
||||
@@ -283,6 +283,86 @@ export async function listFolders(): Promise<LibraryFolder[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function reconnectPhotosForFolders(
|
||||
newFolderHandle: FileSystemDirectoryHandle
|
||||
): Promise<{ reconnectedFolders: number; reconnectedPhotos: number }> {
|
||||
const existingFolders = await listFolders();
|
||||
const allPhotos = (await readPhotos()) ?? [];
|
||||
let reconnectedFolders = 0;
|
||||
let reconnectedPhotos = 0;
|
||||
|
||||
const folderTargets: { name: string; handle: FileSystemDirectoryHandle }[] = [];
|
||||
folderTargets.push({ name: newFolderHandle.name, handle: newFolderHandle });
|
||||
|
||||
const knownFolderNames = new Set([
|
||||
...existingFolders.map((f) => f.name),
|
||||
...allPhotos.map((p) => p.folder),
|
||||
]);
|
||||
|
||||
for (const name of knownFolderNames) {
|
||||
if (folderTargets.some((t) => t.name === name)) continue;
|
||||
try {
|
||||
const sub = await openAt(newFolderHandle, name);
|
||||
if (sub) {
|
||||
folderTargets.push({ name, handle: sub });
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const db = await openDb();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction(FOLDERS, 'readwrite');
|
||||
const store = tx.objectStore(FOLDERS);
|
||||
for (const target of folderTargets) {
|
||||
store.put({ name: target.name, handle: target.handle });
|
||||
reconnectedFolders++;
|
||||
}
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
|
||||
const photosToUpdate: LibraryPhoto[] = [];
|
||||
for (const target of folderTargets) {
|
||||
const matchingPhotos = allPhotos.filter((p) => p.folder === target.name && !p.handle);
|
||||
if (!matchingPhotos.length) continue;
|
||||
|
||||
const dirCache = new Map<string, FileSystemDirectoryHandle>();
|
||||
dirCache.set('', target.handle);
|
||||
|
||||
for (const p of matchingPhotos) {
|
||||
try {
|
||||
let dirHandle = dirCache.get(p.dir);
|
||||
if (!dirHandle) {
|
||||
const opened = await openAt(target.handle, p.dir);
|
||||
if (!opened) continue;
|
||||
dirCache.set(p.dir, opened);
|
||||
dirHandle = opened;
|
||||
}
|
||||
const fileHandle = await dirHandle.getFileHandle(p.name);
|
||||
if (fileHandle) {
|
||||
p.handle = fileHandle;
|
||||
photosToUpdate.push(p);
|
||||
reconnectedPhotos++;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (photosToUpdate.length) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction(PHOTOS, 'readwrite');
|
||||
const store = tx.objectStore(PHOTOS);
|
||||
for (const p of photosToUpdate) {
|
||||
store.put(p);
|
||||
}
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
return { reconnectedFolders, reconnectedPhotos };
|
||||
}
|
||||
|
||||
// The picker's own dialog; `id` makes the browser reopen at the folder this page
|
||||
// was last pointed at, which is what makes a second visit one click.
|
||||
export async function pickFolder(): Promise<LibraryFolder | null> {
|
||||
@@ -292,6 +372,7 @@ export async function pickFolder(): Promise<LibraryFolder | null> {
|
||||
).showDirectoryPicker({ mode: 'readwrite', id: 'recipescam-library' });
|
||||
const folder: LibraryFolder = { name: handle.name, handle };
|
||||
await ask(FOLDERS, 'readwrite', (s) => s.put(folder));
|
||||
await reconnectPhotosForFolders(handle);
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user