diff --git a/frontend/src/components/Gallery.jsx b/frontend/src/components/Gallery.jsx index 0118138..0f9ba4c 100644 --- a/frontend/src/components/Gallery.jsx +++ b/frontend/src/components/Gallery.jsx @@ -1,42 +1,86 @@ +import { useEffect, useRef, useState } from 'react' import { imgUrl } from '../api' -// Justified gallery: rows are built with flexbox, each tile's flex-grow is -// proportional to its aspect ratio so rows fill the container edge to edge. -// When `selected`/`onToggleSelect` are provided, tiles get a select checkmark; -// selection state lives in the parent so it survives lightbox open/close. +// True justified layout: pack photos greedily into rows at their real aspect +// ratios, then scale each row's height so it fills the container width +// exactly. No cropping, no stretch, and the last row simply renders at the +// target height instead of being padded by a spacer. +function layoutRows(photos, containerWidth, targetHeight, gap) { + const rows = [] + let row = [] + let arSum = 0 + let index = 0 + for (const photo of photos) { + const ar = photo.width && photo.height ? photo.width / photo.height : 1.5 + row.push({ photo, ar, index: index++ }) + arSum += ar + const gaps = (row.length - 1) * gap + if (arSum * targetHeight + gaps >= containerWidth) { + rows.push({ items: row, height: (containerWidth - gaps) / arSum }) + row = [] + arSum = 0 + } + } + if (row.length > 0) { + const gaps = (row.length - 1) * gap + rows.push({ + items: row, + height: Math.min(targetHeight, (containerWidth - gaps) / arSum), + }) + } + return rows +} + export default function Gallery({ photos, onOpen, overlay, selected, onToggleSelect }) { + const containerRef = useRef(null) + const [width, setWidth] = useState(0) + + useEffect(() => { + const el = containerRef.current + if (!el) return + const observer = new ResizeObserver((entries) => setWidth(entries[0].contentRect.width)) + observer.observe(el) + return () => observer.disconnect() + }, []) + if (photos.length === 0) return null + const gap = 6 + const targetHeight = width < 700 ? 170 : 240 + const rows = width > 0 ? layoutRows(photos, width, targetHeight, gap) : [] const selecting = selected && selected.size > 0 + return ( -