fix(library): add one-time DB cursor migration to clean legacy Blobs and implement hierarchical Add Folder tree matching

This commit is contained in:
2026-10-05 07:01:11 +07:00
parent 91722861f2
commit b2fc0762ee
3 changed files with 119 additions and 34 deletions
+46 -8
View File
@@ -10,6 +10,7 @@ import {
canBrowseFolders,
clearLibraryCache,
ensurePermission,
findFolderRelationship,
getPhoto,
getPhotoThumbs,
HEAD_BYTES,
@@ -659,16 +660,54 @@ export function Library() {
try {
const folder = await pickFolder();
if (!folder) return;
setFolders((prev) => [...prev.filter((f) => f.name !== folder.name), folder].sort((a, b) => a.name.localeCompare(b.name)));
setReady((prev) => new Set(prev).add(folder.name));
setNode(folder.name);
await scan(folder);
const relInfo = await findFolderRelationship(folder.handle, folders);
if (relInfo.type === 'subfolder') {
const { parent, relPath } = relInfo;
const subNodeKey = `${parent.name}/${relPath}`;
const parts = relPath.split('/');
setExpanded((prev) => {
const next = new Set(prev);
let curr = parent.name;
next.add(curr);
for (let i = 0; i < parts.length; i++) {
curr = `${curr}/${parts[i]}`;
next.add(curr);
}
return next;
});
setDirs((prev) => {
if (prev.some((d) => d.id === subNodeKey)) return prev;
return [...prev, { id: subNodeKey, folder: parent.name, rel: relPath }];
});
setNode(subNodeKey);
await scan(parent, false, relPath + '/');
} else if (relInfo.type === 'parent') {
const { child } = relInfo;
const nextFolders = [...folders.filter((f) => f.name !== child.name), folder].sort((a, b) =>
a.name.localeCompare(b.name)
);
setFolders(nextFolders);
setReady((prev) => new Set(prev).add(folder.name));
setNode(folder.name);
await scan(folder);
} else {
setFolders((prev) =>
[...prev.filter((f) => f.name !== folder.name), folder].sort((a, b) => a.name.localeCompare(b.name))
);
setReady((prev) => new Set(prev).add(folder.name));
setNode(folder.name);
await scan(folder);
}
} catch (err) {
// Closing the picker is a "not now", not a failure worth a line of text.
if (err instanceof DOMException && err.name === 'AbortError') return;
setNote(t('lib.failed'));
}
}, [scan, t]);
}, [folders, scan, t]);
const reconnect = useCallback(
async (folder: LibraryFolder) => {
@@ -1107,7 +1146,7 @@ export function Library() {
same row and ellipsises — the catalogue is the content, this bar
is furniture. */}
<div className="chip-row lib-bar">
<button type="button" className="btn primary" data-key="lib-add" disabled={!!live} onClick={() => void add()}>
<button type="button" className="btn primary" data-key="lib-add" onClick={() => void add()}>
{t('lib.add')}
</button>
{live || scanQueue().length ? (
@@ -1732,7 +1771,6 @@ export function Library() {
type="button"
className="lib-menu-item"
data-key="lib-menu-add"
disabled={!!live}
onClick={() => {
setMenu(null);
void add();