From aa63d4c11dcb41ec1b0ecffdd53088524559fc3e Mon Sep 17 00:00:00 2001 From: dtoro Date: Mon, 18 May 2026 00:03:38 +0200 Subject: [PATCH] feat(sidebar): persist metadata section collapse across photo switches GPS, Credits & notes, and File sections in the right sidebar now read and write their expanded state through the view store and persist it to localStorage. Closed by default; the user's first toggle pins their choice across subsequent photos and reloads. Switched from the previous data-driven defaults ("open if this photo has GPS / IPTC fields") to static defaults: a data-driven default would change between photos, fire a programmatic `toggle` event on the
element, and silently overwrite the user's persisted choice. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/sidebar/RightSidebar.svelte | 26 ++++++++++--- web/src/lib/stores/view.svelte.ts | 38 ++++++++++++++++++- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/web/src/lib/components/sidebar/RightSidebar.svelte b/web/src/lib/components/sidebar/RightSidebar.svelte index 894276a..6f20bce 100644 --- a/web/src/lib/components/sidebar/RightSidebar.svelte +++ b/web/src/lib/components/sidebar/RightSidebar.svelte @@ -38,6 +38,7 @@ import { isAuthenticated } from '$lib/stores/session.svelte'; import { push as pushUndo } from '$lib/stores/undo.svelte'; import { thumbUrl } from '$lib/stores/session.svelte'; + import { getMetadataSectionOpen, setMetadataSection } from '$lib/stores/view.svelte'; import { primaryFile, type PpPhoto } from '$lib/types/photoprism'; interface Props { @@ -522,8 +523,16 @@ {/if} - -
+ +
setMetadataSection('gps', e.currentTarget.open)} + > @@ -564,10 +573,11 @@
- +
setMetadataSection('credits', e.currentTarget.open)} >
- -
+ +
setMetadataSection('file', e.currentTarget.open)} + > diff --git a/web/src/lib/stores/view.svelte.ts b/web/src/lib/stores/view.svelte.ts index 09e52c0..5cde62e 100644 --- a/web/src/lib/stores/view.svelte.ts +++ b/web/src/lib/stores/view.svelte.ts @@ -24,6 +24,13 @@ interface Persisted { thumbnailSize?: ThumbnailSize; leftSidebarWidth?: number; rightSidebarWidth?: number; + /** + * Per-section expanded state for the right-sidebar metadata panel + * (GPS, Credits, File). Keyed by section id; missing entries use a + * static default supplied by the component, so the user's chosen + * collapse state stays put as they navigate between photos. + */ + metadataSections?: Record; } export const MIN_LEFT_WIDTH = 180; @@ -62,6 +69,7 @@ export const view = $state<{ thumbnailSize: ThumbnailSize; leftSidebarWidth: number; rightSidebarWidth: number; + metadataSections: Record; }>({ rightSidebarCollapsed: initial.rightSidebarCollapsed ?? false, leftSidebarCollapsed: initial.leftSidebarCollapsed ?? false, @@ -77,7 +85,11 @@ export const view = $state<{ typeof initial.rightSidebarWidth === 'number' ? initial.rightSidebarWidth : DEFAULT_RIGHT_WIDTH, MIN_RIGHT_WIDTH, MAX_RIGHT_WIDTH - ) + ), + metadataSections: + initial.metadataSections && typeof initial.metadataSections === 'object' + ? { ...initial.metadataSections } + : {} }); function persist(): void { @@ -87,7 +99,8 @@ function persist(): void { leftSidebarCollapsed: view.leftSidebarCollapsed, thumbnailSize: view.thumbnailSize, leftSidebarWidth: view.leftSidebarWidth, - rightSidebarWidth: view.rightSidebarWidth + rightSidebarWidth: view.rightSidebarWidth, + metadataSections: view.metadataSections }; localStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); } @@ -121,3 +134,24 @@ export function toggleLeftSidebar(): void { view.leftSidebarCollapsed = !view.leftSidebarCollapsed; persist(); } + +/** + * Read the persisted expanded state for a right-sidebar metadata + * section, falling back to `defaultOpen` when the user has never + * toggled it. `defaultOpen` should be a *static* value — using a + * per-photo data-driven default would change between photos, fire a + * programmatic `toggle` event, and silently overwrite the user's + * preference. + */ +export function getMetadataSectionOpen(id: string, defaultOpen: boolean): boolean { + const v = view.metadataSections[id]; + return typeof v === 'boolean' ? v : defaultOpen; +} + +/** Record the user's explicit collapse/expand choice for a metadata + * section. Persists immediately so a reload keeps the layout the + * user picked. */ +export function setMetadataSection(id: string, open: boolean): void { + view.metadataSections[id] = open; + persist(); +}