diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index eeffd43..85c0e19 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -620,10 +620,20 @@ function Catalogue() { const shape = progress.dirs.length ? `${progress.dirs.length}:${progress.dirs[progress.dirs.length - 1].id}` : ''; if (shape !== drew) { drew = shape; - // The names of a reading kept to one folder are the folders under that - // one and no others: the column keeps what it has, and the rows it - // already drew stay where they are. - if (!progress.from) setDirs(progress.dirs); + if (progress.dirs.length) { + setDirs((prev) => { + const map = new Map(prev.map((d) => [normPath(d.id).toLowerCase(), d])); + let changed = false; + for (const d of progress.dirs) { + const k = normPath(d.id).toLowerCase(); + if (!map.has(k)) { + map.set(k, d); + changed = true; + } + } + return changed ? [...map.values()] : prev; + }); + } } // 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 @@ -1031,7 +1041,9 @@ function Catalogue() { const { hits, foldersUnder } = baseTreeInfo; const counted = (key: string, hit: number | undefined) => { const normKey = (key || '').replace(/\\/g, '/'); - return Math.max(hit ?? 0, read?.[normKey] ?? read?.[key] ?? 0); + const lowerKey = normKey.toLowerCase(); + const readVal = read?.[normKey] ?? read?.[lowerKey] ?? read?.[key] ?? 0; + return Math.max(hit ?? 0, readVal); }; const out: Node[] = []; for (const folder of folders) { @@ -1110,6 +1122,25 @@ function Catalogue() { return nKey === mOwn || mOwn.startsWith(`${nKey}/`); }; + // Auto-expand parent folders of the active scan path so deep subfolders (levels 4, 5, 6, 7+) + // are visible in the tree while being scanned. + useEffect(() => { + if (!liveMark) return; + const parts = normPath(liveMark.own).split('/'); + if (parts.length <= 1) return; + const toExpand: string[] = []; + for (let i = 1; i < parts.length; i++) { + toExpand.push(parts.slice(0, i).join('/')); + } + setExpanded((prev) => { + const missing = toExpand.filter((k) => !prev.has(k)); + if (!missing.length) return prev; + const next = new Set(prev); + for (const k of missing) next.add(k); + return next; + }); + }, [liveMark?.own]); + // A remembered subfolder that no longer holds a frame falls back to the folder // it sits in, so the screen never opens on a node that is not drawn. useEffect(() => { diff --git a/docker/frontend/src/engine/library.ts b/docker/frontend/src/engine/library.ts index b28e8f8..9f90f02 100644 --- a/docker/frontend/src/engine/library.ts +++ b/docker/frontend/src/engine/library.ts @@ -798,12 +798,14 @@ export async function scanFolder( const relNorm = rel.replace(/\\/g, '/'); const cut = relNorm.lastIndexOf('/'); counts[normRoot] = (counts[normRoot] ?? 0) + n; + counts[normRoot.toLowerCase()] = counts[normRoot]; let path = ''; for (const part of (cut < 0 ? '' : relNorm.slice(0, cut)).split('/')) { if (!part) continue; path = path ? `${path}/${part}` : part; const key = `${normRoot}/${path}`; counts[key] = (counts[key] ?? 0) + n; + counts[key.toLowerCase()] = counts[key]; } }; for (const id of known.keys()) count(id.startsWith(`${normRoot}/`) ? id.slice(normRoot.length + 1) : id); diff --git a/docker/recipescam-images.tar.gz.sha256 b/docker/recipescam-images.tar.gz.sha256 index a343021..ddb342f 100644 --- a/docker/recipescam-images.tar.gz.sha256 +++ b/docker/recipescam-images.tar.gz.sha256 @@ -1 +1 @@ -431247d71c50662a2ee41d5d418c2966fc8a8496ea0a7f0909014916e0accd1a recipescam-images.tar.gz +3e9a0f2173ec2dd5df1d1d6b3925ce16ac637e82746a316cc90cf35a82fb2c14 recipescam-images.tar.gz