feat(photos): add square medium variant for sharp grid thumbnails

This commit is contained in:
iu
2026-10-11 19:57:00 +03:00
parent 581e479f03
commit f1b3b01bdf
27 changed files with 459 additions and 32 deletions
@@ -28,6 +28,26 @@
onCursorFocus
}: Props = $props();
// gap-2 in the grid template is 0.5rem.
const GAP_PX = 8;
let gridWidth = $state(0);
// Offer every variant the photo actually has; the backend generates the
// medium variant asynchronously, so an incomplete set must still render.
function srcsetFor(photo: Photo): string {
const parts: string[] = [];
if (photo.thumb_url) parts.push(`${photo.thumb_url} 200w`);
if (photo.medium_url) parts.push(`${photo.medium_url} 800w`);
if (photo.preview_url) parts.push(`${photo.preview_url} 1200w`);
return parts.join(', ');
}
const sizes = $derived(
gridWidth > 0
? `${Math.max(1, Math.round((gridWidth - (density.cols - 1) * GAP_PX) / density.cols))}px`
: undefined
);
const isSelected = (id: string) => selectedIds.includes(id);
const isCursor = (i: number) => cursorIndex === i;
const tabIndexFor = (i: number) =>
@@ -47,6 +67,7 @@
{:else}
<ul
class="grid gap-2"
bind:clientWidth={gridWidth}
style="grid-template-columns: repeat({density.cols}, minmax(0, 1fr));"
>
{#each photos as photo, i (photo.id)}
@@ -76,6 +97,8 @@
{#if photo.thumb_url}
<img
src={photo.thumb_url}
srcset={srcsetFor(photo)}
{sizes}
alt={photo.original_filename}
loading="lazy"
decoding="async"
+2
View File
@@ -22,6 +22,7 @@ export interface Album {
description: string;
cover_photo_id?: string | null;
cover_thumb_url?: string;
cover_medium_url?: string;
photos_count?: number;
members_count?: number;
created_at: string;
@@ -64,6 +65,7 @@ export interface Photo {
comments_count: number;
liked_by_me: boolean;
thumb_url?: string;
medium_url?: string;
preview_url?: string;
original_url?: string;
download_url?: string;
+23
View File
@@ -12,6 +12,26 @@
if (auth.isAuthenticated && !albums.loaded) albums.load();
});
// gap-3 in the grid template is 0.75rem.
const GAP_PX = 12;
let gridWidth = $state(0);
// The medium cover URL falls back to the thumbnail until the medium variant
// is generated, so dedupe identical sources.
function coverSrcset(album: Album): string {
const parts: string[] = [];
if (album.cover_thumb_url) parts.push(`${album.cover_thumb_url} 200w`);
if (album.cover_medium_url && album.cover_medium_url !== album.cover_thumb_url)
parts.push(`${album.cover_medium_url} 800w`);
return parts.join(', ');
}
const coverSizes = $derived(
gridWidth > 0
? `${Math.max(1, Math.round((gridWidth - (density.cols - 1) * GAP_PX) / density.cols))}px`
: undefined
);
function onAlbumContextMenu(ev: MouseEvent, album: Album) {
ev.preventDefault();
ev.stopPropagation();
@@ -53,6 +73,7 @@
{/if}
<ul
class="grid gap-3"
bind:clientWidth={gridWidth}
style="grid-template-columns: repeat({density.cols}, minmax(0, 1fr));"
>
{#each albums.list as album (album.id)}
@@ -68,6 +89,8 @@
{#if album.cover_thumb_url}
<img
src={album.cover_thumb_url}
srcset={coverSrcset(album)}
sizes={coverSizes}
alt=""
class="h-full w-full object-cover transition-transform duration-200 group-hover:scale-105"
loading="lazy"