feat(preview): inline split-pane preview + sidebar metadata pass
Replace the fullscreen PreviewOverlay with an inline top pane above
each grid surface. SplitGrid + InlinePreview render the focused
photo/video inside the host page; a resizableVertical action drives
the divider and the height persists via the view store. Applied to
the timeline, /tags drill-in, /review cause tabs, /photo/[uid], and
/map. selection.focused is now the single source of truth for both
the inline pane and the right sidebar — preview.svelte store and
PreviewOverlay are removed.
Sidebar: drop the thumb; lead with icon-led filename and folder
rows that match the date/place rhythm. Move dims+size to the top
(below date) and camera/lens/exposure into the collapsible File
section. Read-only spans share the input padding so the text column
aligns across rows. Folder row sits between date and dims+size.
VideoPlayer: stop forcing width/height: 100% so videos honour their
intrinsic aspect ratio inside the pane. Key the player on file hash
in InlinePreview so navigating between videos remounts the element
and autoplay fires again.
Sidebar (LeftSidebar): switch the labels badge to a dedicated
countPhotos('label:*') query so it reports photos with a label
rather than PhotoPrism's category roll-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,7 +126,7 @@
|
||||
}));
|
||||
}
|
||||
|
||||
// One query per badge. Admins with no BasePath skip all of these
|
||||
// One query per badge. Admins with no BasePath skip these
|
||||
// (enabled:false via `wantScoped`) and the configQuery numbers are
|
||||
// used directly — same chrome as before that fix, no extra
|
||||
// round-trips.
|
||||
@@ -134,10 +134,20 @@
|
||||
const reviewCountQuery = scopedCountQuery('review', 'review:true');
|
||||
const hiddenCountQuery = scopedCountQuery('hidden', 'hidden:true');
|
||||
const archivedCountQuery = scopedCountQuery('archived', 'archived:true');
|
||||
const labelsCountQuery = scopedCountQuery('labels', 'all:true label:*');
|
||||
// Labels is special: `configQuery.count.labels` is the number of distinct
|
||||
// label categories (PhotoPrism's roll-up), not the number of photos that
|
||||
// carry a label. The Tags surface wants picture counts everywhere, so we
|
||||
// always run a `countPhotos('label:*')` query regardless of the admin/
|
||||
// BasePath shape and never fall back to the category-count.
|
||||
const labelsCountQuery = createQuery<number>(() => ({
|
||||
queryKey: ['photos', 'scoped-count', 'labels', userBasePath(), isAdminUser],
|
||||
queryFn: () => countPhotos(scoped('all:true label:*')),
|
||||
enabled: isAuthenticated(),
|
||||
staleTime: 60_000
|
||||
}));
|
||||
|
||||
function bucketCount(
|
||||
key: 'favorites' | 'review' | 'hidden' | 'archived' | 'labels',
|
||||
key: 'favorites' | 'review' | 'hidden' | 'archived',
|
||||
query: { data: number | undefined; isPending: boolean }
|
||||
): number | undefined {
|
||||
if (wantScoped) {
|
||||
@@ -148,7 +158,6 @@
|
||||
// (no extra round-trip).
|
||||
const c = configQuery.data?.count;
|
||||
if (!c) return undefined;
|
||||
if (key === 'labels') return c.labels;
|
||||
return c[key];
|
||||
}
|
||||
|
||||
@@ -286,7 +295,9 @@
|
||||
const reviewBadge = $derived(bucketCount('review', reviewCountQuery));
|
||||
const hiddenBadge = $derived(bucketCount('hidden', hiddenCountQuery));
|
||||
const archivedBadge = $derived(bucketCount('archived', archivedCountQuery));
|
||||
const labelsBadge = $derived(bucketCount('labels', labelsCountQuery));
|
||||
const labelsBadge = $derived<number | undefined>(
|
||||
labelsCountQuery.isPending ? undefined : labelsCountQuery.data
|
||||
);
|
||||
|
||||
const createMut = createMutation(() => ({
|
||||
mutationFn: (title: string) => createHeap(title),
|
||||
|
||||
Reference in New Issue
Block a user