Mulimage 2.0 #1

Merged
dtoro merged 64 commits from new into main 2026-05-21 22:48:55 +02:00
2 changed files with 56 additions and 8 deletions
Showing only changes of commit aa63d4c11d - Show all commits

View File

@@ -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 @@
</div>
{/if}
<!-- GPS detail (collapsed by default) -->
<details class="rounded border border-border" open={Boolean(photo.Lat || photo.Lng)}>
<!-- GPS detail. Static default (closed); user's expand/collapse
choice persists across photo switches via the view store.
Avoid data-driven defaults here — they make the `open` attr
change between photos, which fires a programmatic `toggle`
event and would silently overwrite the user's preference. -->
<details
class="rounded border border-border"
open={getMetadataSectionOpen('gps', false)}
ontoggle={(e) => setMetadataSection('gps', e.currentTarget.open)}
>
<summary
class="cursor-pointer px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground"
>
@@ -564,10 +573,11 @@
</div>
</details>
<!-- IPTC credits (collapsed unless something set) -->
<!-- IPTC credits. Static default (closed); user's choice persists. -->
<details
class="rounded border border-border"
open={Boolean(subject || artist || copyright || license || notes)}
open={getMetadataSectionOpen('credits', false)}
ontoggle={(e) => setMetadataSection('credits', e.currentTarget.open)}
>
<summary
class="cursor-pointer px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground"
@@ -623,8 +633,12 @@
</div>
</details>
<!-- File (collapsed by default) -->
<details class="rounded border border-border">
<!-- File metadata. Closed by default; persists once opened. -->
<details
class="rounded border border-border"
open={getMetadataSectionOpen('file', false)}
ontoggle={(e) => setMetadataSection('file', e.currentTarget.open)}
>
<summary
class="cursor-pointer px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground"
>

View File

@@ -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<string, boolean>;
}
export const MIN_LEFT_WIDTH = 180;
@@ -62,6 +69,7 @@ export const view = $state<{
thumbnailSize: ThumbnailSize;
leftSidebarWidth: number;
rightSidebarWidth: number;
metadataSections: Record<string, boolean>;
}>({
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();
}