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:
1 parent
9c66ea85b8
commit
a5f40b60b2
5 files changed
+21
-32
No files matched your search
+14
-25
@@ -9,7 +9,6 @@ type MediaItem = {
|
||||
relativePath: string;
|
||||
filename: string;
|
||||
published: boolean;
|
||||
wasOncePublished: boolean;
|
||||
featured: boolean;
|
||||
mediaType: string;
|
||||
};
|
||||
@@ -78,7 +77,7 @@ export default function AdminDashboard() {
|
||||
const togglePublished = async (id: number, current: boolean) => {
|
||||
// Optimistic UI
|
||||
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) => ({
|
||||
...prev,
|
||||
@@ -124,11 +123,7 @@ export default function AdminDashboard() {
|
||||
|
||||
// Optimistic UI
|
||||
setItems((prev) =>
|
||||
prev.map((i) => ({
|
||||
...i,
|
||||
published: isPublished,
|
||||
...(isPublished ? { wasOncePublished: true } : {})
|
||||
}))
|
||||
prev.map((i) => ({ ...i, published: isPublished }))
|
||||
);
|
||||
setStats({
|
||||
total: items.length,
|
||||
@@ -207,28 +202,22 @@ export default function AdminDashboard() {
|
||||
borderRadius: 'var(--radius)',
|
||||
overflow: 'hidden',
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative', aspectRatio: '1/1', background: '#eee' }}>
|
||||
{(!item.wasOncePublished && !item.published) ? (
|
||||
<div style={{ width: '100%', height: '100%', background: '#fff' }} />
|
||||
) : (
|
||||
<>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={`/api/media/${item.relativePath}?type=thumb`}
|
||||
alt={item.filename}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={`/api/media/${item.relativePath}?type=thumb`}
|
||||
alt={item.filename}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
{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>
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ export async function PATCH(
|
||||
where: { id },
|
||||
data: {
|
||||
...(published !== undefined && { published }),
|
||||
...(published === true && { wasOncePublished: true }),
|
||||
...(featured !== undefined && { featured }),
|
||||
...(placementMode !== undefined && { placementMode }),
|
||||
...(gridColumn !== undefined && { gridColumn }),
|
||||
|
||||
@@ -24,10 +24,7 @@ export async function PATCH(request: NextRequest) {
|
||||
where: {
|
||||
id: { in: ids }
|
||||
},
|
||||
data: {
|
||||
published,
|
||||
...(published === true ? { wasOncePublished: true } : {})
|
||||
}
|
||||
data: { published }
|
||||
});
|
||||
|
||||
if (result.count > 0) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { validateMediaPath } from '@/lib/security/path';
|
||||
import { validateSession } from '@/lib/auth/session';
|
||||
import { getSafeContentType } from '@/lib/media/types';
|
||||
import { getThumbnailPath, getGalleryPath } from '@/lib/processing/images';
|
||||
|
||||
@@ -195,10 +195,13 @@ export default function MediaWall({
|
||||
votingOpen={votingOpen}
|
||||
/>
|
||||
))}
|
||||
{/* Ghost element to prevent the last row from stretching */}
|
||||
<div style={{ flexGrow: 1000, height: 0 }} />
|
||||
|
||||
{hasMore && (
|
||||
<div
|
||||
ref={loadMoreRef}
|
||||
style={{ height: '20px', width: '100%', gridColumn: '1 / -1' }}
|
||||
style={{ height: '20px', width: '100%' }}
|
||||
/>
|
||||
)}
|
||||
{loading && (
|
||||
@@ -206,7 +209,7 @@ export default function MediaWall({
|
||||
style={{
|
||||
padding: '20px',
|
||||
textAlign: 'center',
|
||||
gridColumn: '1 / -1',
|
||||
width: '100%',
|
||||
color: 'var(--color-text-light)',
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user