diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index d6da93e..81ea633 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -1018,9 +1018,15 @@ function Catalogue() { // roll's top level usually holds no file of its own. const shown = useMemo(() => { if (!rootName) return photos; - const dir = node === rootName ? '' : (node ?? '').slice(rootName.length + 1); - const below = dir === '' ? (p: LibraryPhoto) => p.dir !== '' : (p: LibraryPhoto) => p.dir.startsWith(`${dir}/`); - return photos.filter((p) => p.folder === rootName && (p.dir === dir || (deep && below(p)))); + const normRoot = rootName.replace(/\\/g, '/'); + const normNode = (node ?? '').replace(/\\/g, '/'); + const dir = normNode === normRoot ? '' : normNode.slice(normRoot.length + 1); + const below = dir === '' ? (p: LibraryPhoto) => (p.dir ?? '').replace(/\\/g, '/') !== '' : (p: LibraryPhoto) => (p.dir ?? '').replace(/\\/g, '/').startsWith(`${dir}/`); + return photos.filter((p) => { + const pFolder = (p.folder ?? '').replace(/\\/g, '/'); + const pDir = (p.dir ?? '').replace(/\\/g, '/'); + return pFolder === normRoot && (pDir === dir || (deep && below(p))); + }); }, [deep, node, photos, rootName]); // What the thumbnail view draws, out of what the open node holds. A frame's @@ -2311,7 +2317,7 @@ function Catalogue() { return next; }); setTimeout(() => { - const el = document.querySelector(`[data-key="lib-folder-${targetNode}"]`) || document.querySelector(`[data-key="lib-folder-${photo.folder}"]`); + const el = document.querySelector(`[data-key="lib-node-${targetNode}"]`) || document.querySelector(`[data-key="lib-node-${photo.folder}"]`); if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.classList.add('lib-folder-highlight'); diff --git a/docker/frontend/src/engine/library.ts b/docker/frontend/src/engine/library.ts index 4f9e37d..4b4153e 100644 --- a/docker/frontend/src/engine/library.ts +++ b/docker/frontend/src/engine/library.ts @@ -135,8 +135,12 @@ export function isSupportedPhoto(name: string): boolean { return isRawName(name) || SUPPORTED.test(name); } +export function normPath(p: string): string { + return (p || '').replace(/\\/g, '/').replace(/\/+/g, '/').replace(/\/$/, ''); +} + export function photoId(folder: string, name: string): string { - return `${folder}/${name}`; + return `${normPath(folder)}/${normPath(name)}`; } // The picker is Chromium-only (Chrome, Edge, Opera, Brave): Firefox and Safari @@ -949,11 +953,12 @@ export async function scanFolder( const size = preview ? jpegSize(preview) : (raw || heic || isTiff) ? null : jpegSize(bytes); const thumb = src ? await tile(src, size) : null; const taken = (await readCapturedAt(bytes)) ?? file.lastModified; - const cut = rel.lastIndexOf('/'); + const relNorm = rel.replace(/\\/g, '/'); + const cut = relNorm.lastIndexOf('/'); const row: LibraryPhoto = { id, - folder: folder.name, - dir: cut < 0 ? '' : rel.slice(0, cut), + folder: normPath(folder.name), + dir: cut < 0 ? '' : normPath(relNorm.slice(0, cut)), name: handle.name, handle, thumb,