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:
@@ -85,13 +85,13 @@
|
||||
{#key vf.Hash}
|
||||
<VideoPlayer
|
||||
src={videoUrl(vf.Hash)}
|
||||
poster={thumbUrl(pf.Hash, 'fit_1920')}
|
||||
poster={thumbUrl(pf.Hash, 'fit_1280')}
|
||||
title={photoQuery.data.OriginalName ?? pf.Name ?? ''}
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
<img
|
||||
src={thumbUrl(pf.Hash, 'fit_1920')}
|
||||
src={thumbUrl(pf.Hash, 'fit_1280')}
|
||||
alt={photoQuery.data.OriginalName ?? pf.Name ?? 'Photo'}
|
||||
class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
|
||||
/>
|
||||
|
||||
@@ -811,122 +811,123 @@
|
||||
-->
|
||||
<div class="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<SplitGrid>
|
||||
{#snippet preview()}
|
||||
<InlinePreview uid={selection.focused} order={selection.order} />
|
||||
{/snippet}
|
||||
{#snippet grid()}
|
||||
<main
|
||||
bind:this={scrollRoot}
|
||||
class="flex-1 overflow-y-auto outline-none focus:outline-none"
|
||||
use:gridKeyNav={{ scrollToIndex, onArrow }}
|
||||
use:visibleRange={{
|
||||
onChange: (f, l) => {
|
||||
visFirst = f;
|
||||
visLast = l;
|
||||
},
|
||||
sampleEvery: TILE_SAMPLE,
|
||||
}}
|
||||
>
|
||||
<div class="p-6 pb-24">
|
||||
{#if photosQuery.isPending}
|
||||
<SkeletonGrid />
|
||||
{:else if photosQuery.isError}
|
||||
<p class="text-sm text-destructive">
|
||||
Failed to load photos: {photosQuery.error instanceof Error
|
||||
? photosQuery.error.message
|
||||
: "unknown error"}
|
||||
</p>
|
||||
{:else if photos.length === 0}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{#if filters.section === "archive"}
|
||||
Archive is empty.
|
||||
{:else if filters.section === "review"}
|
||||
Nothing left to review. Photos PhotoPrism's indexer wasn't sure
|
||||
about land here — use Keep to accept them into the timeline or
|
||||
Archive to set them aside.
|
||||
{:else if filters.section === "hidden"}
|
||||
No hidden photos. PhotoPrism auto-hides files it can't index
|
||||
(broken files, very low quality); they only ever show up here.
|
||||
{:else if filters.section === "heap"}
|
||||
This heap has no photos yet. Select some photos and use the bulk
|
||||
bar's "+ Add to heap" button.
|
||||
{#snippet preview()}
|
||||
<InlinePreview uid={selection.focused} order={selection.order} />
|
||||
{/snippet}
|
||||
{#snippet grid()}
|
||||
<main
|
||||
bind:this={scrollRoot}
|
||||
class="flex-1 overflow-y-auto outline-none focus:outline-none"
|
||||
use:gridKeyNav={{ scrollToIndex, onArrow }}
|
||||
use:visibleRange={{
|
||||
onChange: (f, l) => {
|
||||
visFirst = f;
|
||||
visLast = l;
|
||||
},
|
||||
sampleEvery: TILE_SAMPLE,
|
||||
}}
|
||||
>
|
||||
<div class="pr-2 pl-2 pb-2 overflow-x-hidden">
|
||||
{#if photosQuery.isPending}
|
||||
<SkeletonGrid />
|
||||
{:else if photosQuery.isError}
|
||||
<p class="text-sm text-destructive">
|
||||
Failed to load photos: {photosQuery.error instanceof Error
|
||||
? photosQuery.error.message
|
||||
: "unknown error"}
|
||||
</p>
|
||||
{:else if photos.length === 0}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{#if filters.section === "archive"}
|
||||
Archive is empty.
|
||||
{:else if filters.section === "review"}
|
||||
Nothing left to review. Photos PhotoPrism's indexer wasn't
|
||||
sure about land here — use Keep to accept them into the
|
||||
timeline or Archive to set them aside.
|
||||
{:else if filters.section === "hidden"}
|
||||
No hidden photos. PhotoPrism auto-hides files it can't index
|
||||
(broken files, very low quality); they only ever show up here.
|
||||
{:else if filters.section === "heap"}
|
||||
This heap has no photos yet. Select some photos and use the
|
||||
bulk bar's "+ Add to heap" button.
|
||||
{:else}
|
||||
No photos. Index a folder via PhotoPrism's reindex command.
|
||||
{/if}
|
||||
</p>
|
||||
{:else}
|
||||
No photos. Index a folder via PhotoPrism's reindex command.
|
||||
{/if}
|
||||
</p>
|
||||
{:else}
|
||||
<div
|
||||
data-photo-grid
|
||||
use:trackGridCols
|
||||
class="grid gap-2"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax({view.thumbnailSize}px, 1fr));"
|
||||
>
|
||||
{#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
|
||||
<div
|
||||
data-photo-grid
|
||||
use:trackGridCols
|
||||
class="grid gap-2"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax({view.thumbnailSize}px, 1fr));"
|
||||
>
|
||||
{#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.
|
||||
-mx-6 stretches the bar past the wrapper padding so it
|
||||
reads edge-to-edge in the viewport. -->
|
||||
<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"
|
||||
style="grid-column: 1 / -1;"
|
||||
>
|
||||
{row.label}
|
||||
<span
|
||||
class="ml-2 text-[10px] font-normal text-muted-foreground"
|
||||
>
|
||||
{row.count}
|
||||
</span>
|
||||
</h2>
|
||||
{:else}
|
||||
{@const photo = row.photo}
|
||||
{@const i = row.tileIndex}
|
||||
{@const inWindow = i >= renderFirst && i <= renderLast}
|
||||
<!-- Shell: always rendered. Holds grid-cell space + the
|
||||
<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"
|
||||
style="grid-column: 1 / -1;"
|
||||
>
|
||||
{row.label}
|
||||
<span
|
||||
class="ml-2 text-[10px] font-normal text-muted-foreground"
|
||||
>
|
||||
{row.count}
|
||||
</span>
|
||||
</h2>
|
||||
{:else}
|
||||
{@const photo = row.photo}
|
||||
{@const i = row.tileIndex}
|
||||
{@const inWindow = i >= renderFirst && i <= renderLast}
|
||||
<!-- Shell: always rendered. Holds grid-cell space + the
|
||||
stable `data-uid-shell` anchor that gridKeyNav.scrollToIndex
|
||||
can query even when the inner button is windowed out. -->
|
||||
<div
|
||||
data-uid-shell={photo.UID}
|
||||
class="aspect-square"
|
||||
use:tileRegister={i}
|
||||
>
|
||||
{#if inWindow}
|
||||
{@const sel =
|
||||
selectedIds.has(photo.UID) || focusedUid === photo.UID}
|
||||
<PhotoTile
|
||||
{photo}
|
||||
selected={sel}
|
||||
onClick={(e) => onTileClick(e, photo.UID)}
|
||||
onDblclick={(e) => onTileDblclick(e, photo.UID)}
|
||||
/>
|
||||
<div
|
||||
data-uid-shell={photo.UID}
|
||||
class="aspect-square"
|
||||
use:tileRegister={i}
|
||||
>
|
||||
{#if inWindow}
|
||||
{@const sel =
|
||||
selectedIds.has(photo.UID) ||
|
||||
focusedUid === photo.UID}
|
||||
<PhotoTile
|
||||
{photo}
|
||||
selected={sel}
|
||||
onClick={(e) => onTileClick(e, photo.UID)}
|
||||
onDblclick={(e) => onTileDblclick(e, photo.UID)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</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
|
||||
PhotoPrism's preload distance from page/photos.vue. -->
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-4"
|
||||
use:nearBottom={{
|
||||
onHit: () => photosQuery.fetchNextPage(),
|
||||
enabled:
|
||||
photosQuery.hasNextPage && !photosQuery.isFetchingNextPage,
|
||||
root: scrollRoot,
|
||||
}}
|
||||
></div>
|
||||
{#if photosQuery.isFetchingNextPage}
|
||||
<p class="py-3 text-center text-xs text-muted-foreground">
|
||||
Loading more…
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/snippet}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-4"
|
||||
use:nearBottom={{
|
||||
onHit: () => photosQuery.fetchNextPage(),
|
||||
enabled:
|
||||
photosQuery.hasNextPage && !photosQuery.isFetchingNextPage,
|
||||
root: scrollRoot,
|
||||
}}
|
||||
></div>
|
||||
{#if photosQuery.isFetchingNextPage}
|
||||
<p class="py-3 text-center text-xs text-muted-foreground">
|
||||
Loading more…
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/snippet}
|
||||
</SplitGrid>
|
||||
<BulkActionBar />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user