feat: add thumbnail previews to admin panel for albums and hero images

- Add /api/admin/thumbnail/[id] proxy endpoint for serving Immich thumbnails
- Show album cover thumbnails in AlbumCard components (subpages + standalone)
- Show actual image thumbnails in AlbumPicker modal instead of emoji placeholders
- Show small thumbnail previews for hero image entries
- Add corresponding CSS styles for all thumbnail elements
This commit is contained in:
ralksta
2026-05-29 17:14:58 +02:00
parent 9b053e1648
commit 4a0b57b932
4 changed files with 125 additions and 2 deletions

View File

@@ -425,6 +425,14 @@
border-radius: 6px;
}
.hero-item-thumb {
width: 32px;
height: 32px;
object-fit: cover;
border-radius: 4px;
background: #222;
}
.hero-uuid {
font-family: monospace;
font-size: 0.75rem;
@@ -455,6 +463,22 @@
align-items: center;
justify-content: space-between;
padding: 0.6rem 0.75rem;
gap: 0.75rem;
}
.album-card-thumb {
flex-shrink: 0;
width: 48px;
height: 48px;
border-radius: 6px;
overflow: hidden;
background: #222;
}
.album-card-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.album-card-info {
@@ -735,6 +759,14 @@
font-size: 1.1rem;
}
.picker-thumb-img {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 6px;
background: #222;
}
.picker-item-info {
flex: 1;
min-width: 0;

View File

@@ -68,7 +68,12 @@ export default function AlbumPicker({ albums, onSelect, onClose, usedAlbumIds }:
>
<div className="picker-item-thumb">
{album.thumbnailAssetId ? (
<div className="picker-thumb-placeholder">📷</div>
<img
className="picker-thumb-img"
src={`/api/admin/thumbnail/${album.thumbnailAssetId}`}
alt=""
loading="lazy"
/>
) : (
<div className="picker-thumb-placeholder">📁</div>
)}

View File

@@ -419,6 +419,11 @@ export default function PageBuilder() {
return found?.assetCount || 0;
}
function getAlbumThumbnailId(id: string): string | null {
const found = immichAlbums.find((a) => a.id === id);
return found?.thumbnailAssetId || null;
}
// ── Render ───────────────────────────────────────────────────
if (loading) {
return (
@@ -467,6 +472,12 @@ export default function PageBuilder() {
)}
{gallery.hero.map((id, i) => (
<div key={i} className="hero-item">
<img
className="hero-item-thumb"
src={`/api/admin/thumbnail/${id}`}
alt=""
loading="lazy"
/>
<span className="hero-uuid" title={id}>
{id.slice(0, 8)}...{id.slice(-4)}
</span>
@@ -499,6 +510,7 @@ export default function PageBuilder() {
album={album}
name={getAlbumName(album.id)}
count={getAlbumCount(album.id)}
thumbnailId={getAlbumThumbnailId(album.id)}
onRemove={() => removeStandaloneAlbum(i)}
onUpdate={(updates) => {
setGallery((g) => {
@@ -624,6 +636,7 @@ export default function PageBuilder() {
album={album}
name={getAlbumName(album.id)}
count={getAlbumCount(album.id)}
thumbnailId={getAlbumThumbnailId(album.id)}
onRemove={() => removeSubpageAlbum(spIndex, aIndex)}
onUpdate={(updates) => {
setGallery((g) => {
@@ -684,6 +697,7 @@ export default function PageBuilder() {
album={album}
name={getAlbumName(album.id)}
count={getAlbumCount(album.id)}
thumbnailId={getAlbumThumbnailId(album.id)}
onRemove={() => removeSectionAlbum(spIndex, secIndex, aIndex)}
onUpdate={(updates) => {
setGallery((g) => {
@@ -767,16 +781,26 @@ interface AlbumCardProps {
album: AlbumEntry;
name: string;
count: number;
thumbnailId: string | null;
onRemove: () => void;
onUpdate: (updates: Partial<AlbumEntry>) => void;
}
function AlbumCard({ album, name, count, onRemove, onUpdate }: AlbumCardProps) {
function AlbumCard({ album, name, count, thumbnailId, onRemove, onUpdate }: AlbumCardProps) {
const [expanded, setExpanded] = useState(false);
return (
<div className="album-card">
<div className="album-card-main">
{thumbnailId && (
<div className="album-card-thumb">
<img
src={`/api/admin/thumbnail/${thumbnailId}`}
alt=""
loading="lazy"
/>
</div>
)}
<div className="album-card-info">
<span className="album-card-name">{album.title || name}</span>
<span className="album-card-count">{count} photos</span>