From 400b2150369ff9cef444d67f2fab4f47a3046d18 Mon Sep 17 00:00:00 2001 From: dtoro Date: Tue, 30 Jun 2026 21:04:56 +0200 Subject: [PATCH] fix(sidebar,map,duplicates): flatten sidebar hierarchy, filter by user basePath, fix map coordinates - Flatten sidebar: remove collapsible Tags and Review sections, place all items at level-0 Notes, tag categories (Labels/Keywords/People/Colors/Ratings) now appear directly in Views Review tabs (Causes/Stacks/Duplicates) and Hidden appear directly in Manage - Filter duplicates by user base path to ensure multi-tenant isolation listDuplicateGroups now accepts optional basePath parameter update review page and sidebar to pass userBasePath() for proper per-user caching - Filter map geo data by user base path using path: query filter map page now only shows geotagged photos from current user's library - Fix map coordinate positioning: PhotoPrism /geo endpoint returns [lat,lng] but GeoJSON and MapLibre expect [lng,lat]. Transform coordinates and bbox on data receive to fix photo placement and zoom behavior Co-Authored-By: Claude Haiku 4.5 --- .../lib/components/layout/LeftSidebar.svelte | 170 ++++-------------- web/src/lib/services/adapters/duplicates.ts | 13 +- web/src/lib/services/photoprism.ts | 18 ++ web/src/routes/map/+page.svelte | 16 +- web/src/routes/review/+page.svelte | 6 +- 5 files changed, 80 insertions(+), 143 deletions(-) diff --git a/web/src/lib/components/layout/LeftSidebar.svelte b/web/src/lib/components/layout/LeftSidebar.svelte index 41801f1..ebeba30 100644 --- a/web/src/lib/components/layout/LeftSidebar.svelte +++ b/web/src/lib/components/layout/LeftSidebar.svelte @@ -96,8 +96,8 @@ // sidebar only observes these — cross-folder is an O(disk) scan, so it // stays enabled:false and the duplicates page populates it on first visit. const stacksQuery = createQuery(() => ({ - queryKey: ['duplicates'], - queryFn: listDuplicateGroups, + queryKey: ['duplicates', userBasePath()], + queryFn: () => listDuplicateGroups(userBasePath()), enabled: isAuthenticated(), staleTime: 60_000 })); @@ -178,45 +178,13 @@ if (browser) localStorage.setItem(ROOT_OPEN_KEY, rootExpanded ? '1' : '0'); } - // Tags-submenu collapse state. Same dedicated-key pattern as `rootExpanded` - // above (keeping it out of `view.metadataSections`, which is reserved for - // the right-sidebar metadata panel). Defaults to collapsed so the sidebar - // doesn't grow on first paint. - const TAGS_OPEN_KEY = 'mule_tags_expanded'; - let tagsExpanded = $state(loadTagsExpanded()); - function loadTagsExpanded(): boolean { - if (!browser) return false; - const raw = localStorage.getItem(TAGS_OPEN_KEY); - return raw === '1'; - } - function toggleTags() { - tagsExpanded = !tagsExpanded; - if (browser) localStorage.setItem(TAGS_OPEN_KEY, tagsExpanded ? '1' : '0'); - } - - // Review-submenu collapse state. Mirrors `tagsExpanded` so the Review - // row in Manage can expose the same set of tabs the /review page shows - // (cause groups + duplicates panels). Defaults to collapsed. - const REVIEW_OPEN_KEY = 'mule_review_expanded'; - let reviewExpanded = $state(loadReviewExpanded()); - function loadReviewExpanded(): boolean { - if (!browser) return false; - return localStorage.getItem(REVIEW_OPEN_KEY) === '1'; - } - function toggleReview() { - reviewExpanded = !reviewExpanded; - if (browser) localStorage.setItem(REVIEW_OPEN_KEY, reviewExpanded ? '1' : '0'); - } - // Cause-tab list is dynamic (only buckets with hits show up on /review), - // so the sidebar mirrors that by reusing the same query. Gated on - // `reviewExpanded` to avoid paying the /photos round-trip for users who - // never expand the section; the queryKey is shared with the /review page - // so visiting that route warms the cache for free. + // so the sidebar mirrors that by reusing the same query. The queryKey is + // shared with the /review page so visiting that route warms the cache for free. const reviewGroupsQuery = createQuery(() => ({ queryKey: ['review-groups'], queryFn: listReviewGroups, - enabled: isAuthenticated() && reviewExpanded, + enabled: isAuthenticated(), staleTime: 30_000 })); @@ -732,65 +700,34 @@ {#each views as v (v.kind === 'section' ? `s:${v.id}` : `r:${v.href}`)} {@render viewRow(v)} {/each} - - - {#if tagsExpanded} - + + {#if true} {@const notesActive = isNotesActive()} Notes - {#each TAG_CATEGORIES as cat (cat)} - {@const active = isTagCategoryActive(cat)} - - {TAG_CATEGORY_LABELS[cat]} - - {/each} {/if} + + {#each TAG_CATEGORIES as cat (cat)} + {@const active = isTagCategoryActive(cat)} + + {TAG_CATEGORY_LABELS[cat]} + + {/each} - - {#if reviewExpanded} - {#each reviewTabs as t (t.id)} - {@const active = isReviewTabActive(t.id)} - - {t.label} - - {/each} - + {t.label} + + {/each} + + {#if true} {@const hiddenActive = isActive('hidden')}