Update media wall to Justified flex layout to guarantee no cropping, zero gaps, and dynamic sizing based on true aspect ratios

This commit is contained in:
Jannik committed 2026-09-24 04:40:22 +02:00
1 parent 9c66ea85b8
commit a5f40b60b2
5 files changed
+21 -32

No files matched your search

+14 -25
View File
@@ -9,7 +9,6 @@ type MediaItem = {
relativePath: string; relativePath: string;
filename: string; filename: string;
published: boolean; published: boolean;
wasOncePublished: boolean;
featured: boolean; featured: boolean;
mediaType: string; mediaType: string;
}; };
@@ -78,7 +77,7 @@ export default function AdminDashboard() {
const togglePublished = async (id: number, current: boolean) => { const togglePublished = async (id: number, current: boolean) => {
// Optimistic UI // Optimistic UI
setItems((prev) => setItems((prev) =>
prev.map((i) => (i.id === id ? { ...i, published: !current, ...( !current ? { wasOncePublished: true } : {} ) } : i)) prev.map((i) => (i.id === id ? { ...i, published: !current } : i))
); );
setStats((prev) => ({ setStats((prev) => ({
...prev, ...prev,
@@ -124,11 +123,7 @@ export default function AdminDashboard() {
// Optimistic UI // Optimistic UI
setItems((prev) => setItems((prev) =>
prev.map((i) => ({ prev.map((i) => ({ ...i, published: isPublished }))
...i,
published: isPublished,
...(isPublished ? { wasOncePublished: true } : {})
}))
); );
setStats({ setStats({
total: items.length, total: items.length,
@@ -207,28 +202,22 @@ export default function AdminDashboard() {
borderRadius: 'var(--radius)', borderRadius: 'var(--radius)',
overflow: 'hidden', overflow: 'hidden',
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
opacity: (!item.wasOncePublished && !item.published) ? 1 : (item.published ? 1 : 0.6), opacity: item.published ? 1 : 0.6,
transition: 'opacity 0.2s' transition: 'opacity 0.2s'
}} }}
> >
<div style={{ position: 'relative', aspectRatio: '1/1', background: '#eee' }}> <div style={{ position: 'relative', aspectRatio: '1/1', background: '#eee' }}>
{(!item.wasOncePublished && !item.published) ? ( {/* eslint-disable-next-line @next/next/no-img-element */}
<div style={{ width: '100%', height: '100%', background: '#fff' }} /> <img
) : ( src={`/api/media/${item.relativePath}?type=thumb`}
<> alt={item.filename}
{/* eslint-disable-next-line @next/next/no-img-element */} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
<img loading="lazy"
src={`/api/media/${item.relativePath}?type=thumb`} />
alt={item.filename} {item.mediaType === 'VIDEO' && (
style={{ width: '100%', height: '100%', objectFit: 'cover' }} <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
loading="lazy" <svg width="24" height="24" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
/> </div>
{item.mediaType === 'VIDEO' && (
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
<svg width="24" height="24" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
</div>
)}
</>
)} )}
</div> </div>
-1
View File
@@ -37,7 +37,6 @@ export async function PATCH(
where: { id }, where: { id },
data: { data: {
...(published !== undefined && { published }), ...(published !== undefined && { published }),
...(published === true && { wasOncePublished: true }),
...(featured !== undefined && { featured }), ...(featured !== undefined && { featured }),
...(placementMode !== undefined && { placementMode }), ...(placementMode !== undefined && { placementMode }),
...(gridColumn !== undefined && { gridColumn }), ...(gridColumn !== undefined && { gridColumn }),
+1 -4
View File
@@ -24,10 +24,7 @@ export async function PATCH(request: NextRequest) {
where: { where: {
id: { in: ids } id: { in: ids }
}, },
data: { data: { published }
published,
...(published === true ? { wasOncePublished: true } : {})
}
}); });
if (result.count > 0) { if (result.count > 0) {
+1
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/db'; import { prisma } from '@/lib/db';
import { validateMediaPath } from '@/lib/security/path';
import { validateSession } from '@/lib/auth/session'; import { validateSession } from '@/lib/auth/session';
import { getSafeContentType } from '@/lib/media/types'; import { getSafeContentType } from '@/lib/media/types';
import { getThumbnailPath, getGalleryPath } from '@/lib/processing/images'; import { getThumbnailPath, getGalleryPath } from '@/lib/processing/images';
+5 -2
View File
@@ -195,10 +195,13 @@ export default function MediaWall({
votingOpen={votingOpen} votingOpen={votingOpen}
/> />
))} ))}
{/* Ghost element to prevent the last row from stretching */}
<div style={{ flexGrow: 1000, height: 0 }} />
{hasMore && ( {hasMore && (
<div <div
ref={loadMoreRef} ref={loadMoreRef}
style={{ height: '20px', width: '100%', gridColumn: '1 / -1' }} style={{ height: '20px', width: '100%' }}
/> />
)} )}
{loading && ( {loading && (
@@ -206,7 +209,7 @@ export default function MediaWall({
style={{ style={{
padding: '20px', padding: '20px',
textAlign: 'center', textAlign: 'center',
gridColumn: '1 / -1', width: '100%',
color: 'var(--color-text-light)', color: 'var(--color-text-light)',
}} }}
> >