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:
59
web/src/lib/components/preview/SplitGrid.svelte
Normal file
59
web/src/lib/components/preview/SplitGrid.svelte
Normal file
@@ -0,0 +1,59 @@
|
||||
<!--
|
||||
Vertical split layout: a top pane (preview) sized by
|
||||
`view.previewPaneHeight`, a draggable divider, and a flex-1 bottom
|
||||
pane (the grid). Mirrors the horizontal sidebar resize pattern.
|
||||
|
||||
Usage:
|
||||
<SplitGrid>
|
||||
{#snippet preview()}
|
||||
<InlinePreview uid={selection.focused} order={selection.order} />
|
||||
{/snippet}
|
||||
{#snippet grid()}
|
||||
<main>…</main>
|
||||
{/snippet}
|
||||
</SplitGrid>
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { resizableVertical } from '$lib/actions/resizableVertical';
|
||||
import { setPreviewPaneHeight, view } from '$lib/stores/view.svelte';
|
||||
|
||||
interface Props {
|
||||
preview: Snippet;
|
||||
grid: Snippet;
|
||||
}
|
||||
let { preview, grid }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col">
|
||||
<!-- Top: preview pane. Fixed pixel height controlled by the divider;
|
||||
overflow-hidden so videos / images can't push the divider off-screen. -->
|
||||
<div
|
||||
class="relative shrink-0 overflow-hidden border-b border-border"
|
||||
style="height: {view.previewPaneHeight}px;"
|
||||
>
|
||||
{@render preview()}
|
||||
<!-- Divider sits on the bottom edge of the preview pane.
|
||||
`edge: 'bottom'` matches the convention: positive dy = pane grows. -->
|
||||
<div
|
||||
class="group absolute -bottom-1.5 left-0 z-20 h-3 w-full cursor-row-resize"
|
||||
use:resizableVertical={{
|
||||
edge: 'bottom',
|
||||
getHeight: () => view.previewPaneHeight,
|
||||
setHeight: setPreviewPaneHeight
|
||||
}}
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Resize preview pane"
|
||||
>
|
||||
<div
|
||||
class="mt-1 h-0.5 w-full bg-transparent transition-colors group-hover:bg-primary/40"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom: grid container; the caller's snippet handles its own scroll. -->
|
||||
<div class="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
{@render grid()}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user