preview: serve fit_1280 + tighten timeline grid padding

InlinePreview was requesting fit_1920 for both the still image and
the video poster — roughly 3× the pixel count of what the pane
actually needs. Drop to fit_1280: still sharp inside the inline
pane (which the user resizes around 300–500px tall in practice)
while cutting payload by ~⅔.

Timeline: pull the grid wrapper padding in from `p-6 pb-24` to
`pr-2 pl-2 pb-2 overflow-x-hidden` now that the SplitGrid preview
pane sits above the grid — the old generous padding existed to
breathe under a full-screen modal that no longer renders inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 23:25:10 +02:00
parent 24c449f475
commit c783f129cc
2 changed files with 109 additions and 108 deletions

View File

@@ -85,13 +85,13 @@
{#key vf.Hash} {#key vf.Hash}
<VideoPlayer <VideoPlayer
src={videoUrl(vf.Hash)} src={videoUrl(vf.Hash)}
poster={thumbUrl(pf.Hash, 'fit_1920')} poster={thumbUrl(pf.Hash, 'fit_1280')}
title={photoQuery.data.OriginalName ?? pf.Name ?? ''} title={photoQuery.data.OriginalName ?? pf.Name ?? ''}
/> />
{/key} {/key}
{:else} {:else}
<img <img
src={thumbUrl(pf.Hash, 'fit_1920')} src={thumbUrl(pf.Hash, 'fit_1280')}
alt={photoQuery.data.OriginalName ?? pf.Name ?? 'Photo'} alt={photoQuery.data.OriginalName ?? pf.Name ?? 'Photo'}
class="max-h-full max-w-full rounded-md object-contain shadow-2xl" class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
/> />

View File

@@ -811,122 +811,123 @@
--> -->
<div class="flex min-w-0 flex-1 flex-col overflow-hidden"> <div class="flex min-w-0 flex-1 flex-col overflow-hidden">
<SplitGrid> <SplitGrid>
{#snippet preview()} {#snippet preview()}
<InlinePreview uid={selection.focused} order={selection.order} /> <InlinePreview uid={selection.focused} order={selection.order} />
{/snippet} {/snippet}
{#snippet grid()} {#snippet grid()}
<main <main
bind:this={scrollRoot} bind:this={scrollRoot}
class="flex-1 overflow-y-auto outline-none focus:outline-none" class="flex-1 overflow-y-auto outline-none focus:outline-none"
use:gridKeyNav={{ scrollToIndex, onArrow }} use:gridKeyNav={{ scrollToIndex, onArrow }}
use:visibleRange={{ use:visibleRange={{
onChange: (f, l) => { onChange: (f, l) => {
visFirst = f; visFirst = f;
visLast = l; visLast = l;
}, },
sampleEvery: TILE_SAMPLE, sampleEvery: TILE_SAMPLE,
}} }}
> >
<div class="p-6 pb-24"> <div class="pr-2 pl-2 pb-2 overflow-x-hidden">
{#if photosQuery.isPending} {#if photosQuery.isPending}
<SkeletonGrid /> <SkeletonGrid />
{:else if photosQuery.isError} {:else if photosQuery.isError}
<p class="text-sm text-destructive"> <p class="text-sm text-destructive">
Failed to load photos: {photosQuery.error instanceof Error Failed to load photos: {photosQuery.error instanceof Error
? photosQuery.error.message ? photosQuery.error.message
: "unknown error"} : "unknown error"}
</p> </p>
{:else if photos.length === 0} {:else if photos.length === 0}
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground">
{#if filters.section === "archive"} {#if filters.section === "archive"}
Archive is empty. Archive is empty.
{:else if filters.section === "review"} {:else if filters.section === "review"}
Nothing left to review. Photos PhotoPrism's indexer wasn't sure Nothing left to review. Photos PhotoPrism's indexer wasn't
about land here — use Keep to accept them into the timeline or sure about land here — use Keep to accept them into the
Archive to set them aside. timeline or Archive to set them aside.
{:else if filters.section === "hidden"} {:else if filters.section === "hidden"}
No hidden photos. PhotoPrism auto-hides files it can't index No hidden photos. PhotoPrism auto-hides files it can't index
(broken files, very low quality); they only ever show up here. (broken files, very low quality); they only ever show up here.
{:else if filters.section === "heap"} {:else if filters.section === "heap"}
This heap has no photos yet. Select some photos and use the bulk This heap has no photos yet. Select some photos and use the
bar's " Add to heap" button. bulk bar's " Add to heap" button.
{:else}
No photos. Index a folder via PhotoPrism's reindex command.
{/if}
</p>
{:else} {:else}
No photos. Index a folder via PhotoPrism's reindex command. <div
{/if} data-photo-grid
</p> use:trackGridCols
{:else} class="grid gap-2"
<div style="grid-template-columns: repeat(auto-fill, minmax({view.thumbnailSize}px, 1fr));"
data-photo-grid >
use:trackGridCols {#each rows as row (row.kind === "header" ? `h:${row.key}` : `t:${row.photo.UID}`)}
class="grid gap-2" {#if row.kind === "header"}
style="grid-template-columns: repeat(auto-fill, minmax({view.thumbnailSize}px, 1fr));" <!-- col-span-full + position:sticky pins the month label to
>
{#each rows as row (row.kind === "header" ? `h:${row.key}` : `t:${row.photo.UID}`)}
{#if row.kind === "header"}
<!-- col-span-full + position:sticky pins the month label to
the top of the scrolling main as the user passes through. the top of the scrolling main as the user passes through.
-mx-6 stretches the bar past the wrapper padding so it -mx-6 stretches the bar past the wrapper padding so it
reads edge-to-edge in the viewport. --> reads edge-to-edge in the viewport. -->
<h2 <h2
class="sticky top-0 z-10 -mx-6 border-b border-border bg-background/95 px-6 py-2 text-xs font-semibold uppercase tracking-wide text-foreground/80 backdrop-blur" class="sticky top-0 z-10 -mx-6 border-b border-border bg-background/95 px-6 py-2 text-xs font-semibold uppercase tracking-wide text-foreground/80 backdrop-blur"
style="grid-column: 1 / -1;" style="grid-column: 1 / -1;"
> >
{row.label} {row.label}
<span <span
class="ml-2 text-[10px] font-normal text-muted-foreground" class="ml-2 text-[10px] font-normal text-muted-foreground"
> >
{row.count} {row.count}
</span> </span>
</h2> </h2>
{:else} {:else}
{@const photo = row.photo} {@const photo = row.photo}
{@const i = row.tileIndex} {@const i = row.tileIndex}
{@const inWindow = i >= renderFirst && i <= renderLast} {@const inWindow = i >= renderFirst && i <= renderLast}
<!-- Shell: always rendered. Holds grid-cell space + the <!-- Shell: always rendered. Holds grid-cell space + the
stable `data-uid-shell` anchor that gridKeyNav.scrollToIndex stable `data-uid-shell` anchor that gridKeyNav.scrollToIndex
can query even when the inner button is windowed out. --> can query even when the inner button is windowed out. -->
<div <div
data-uid-shell={photo.UID} data-uid-shell={photo.UID}
class="aspect-square" class="aspect-square"
use:tileRegister={i} use:tileRegister={i}
> >
{#if inWindow} {#if inWindow}
{@const sel = {@const sel =
selectedIds.has(photo.UID) || focusedUid === photo.UID} selectedIds.has(photo.UID) ||
<PhotoTile focusedUid === photo.UID}
{photo} <PhotoTile
selected={sel} {photo}
onClick={(e) => onTileClick(e, photo.UID)} selected={sel}
onDblclick={(e) => onTileDblclick(e, photo.UID)} onClick={(e) => onTileClick(e, photo.UID)}
/> onDblclick={(e) => onTileDblclick(e, photo.UID)}
/>
{/if}
</div>
{/if} {/if}
</div> {/each}
{/if} </div>
{/each}
</div>
<!-- Sentinel: when this div nears the viewport we kick the next <!-- Sentinel: when this div nears the viewport we kick the next
page. The 4-viewport rootMargin (handled by the action) is page. The 4-viewport rootMargin (handled by the action) is
PhotoPrism's preload distance from page/photos.vue. --> PhotoPrism's preload distance from page/photos.vue. -->
<div <div
aria-hidden="true" aria-hidden="true"
class="h-4" class="h-4"
use:nearBottom={{ use:nearBottom={{
onHit: () => photosQuery.fetchNextPage(), onHit: () => photosQuery.fetchNextPage(),
enabled: enabled:
photosQuery.hasNextPage && !photosQuery.isFetchingNextPage, photosQuery.hasNextPage && !photosQuery.isFetchingNextPage,
root: scrollRoot, root: scrollRoot,
}} }}
></div> ></div>
{#if photosQuery.isFetchingNextPage} {#if photosQuery.isFetchingNextPage}
<p class="py-3 text-center text-xs text-muted-foreground"> <p class="py-3 text-center text-xs text-muted-foreground">
Loading more Loading more
</p> </p>
{/if} {/if}
{/if} {/if}
</div> </div>
</main> </main>
{/snippet} {/snippet}
</SplitGrid> </SplitGrid>
<BulkActionBar /> <BulkActionBar />
</div> </div>