fix: load full library + stretch grid to fill row width

Two unrelated bugs surfaced together because the symptom looked
similar ("missing photos in the grid"):

usePhotosQuery only ever fetched page 1 with per_page=500, so any
filter matching more than 500 photos silently truncated. With a
13k-photo library that meant the Timeline only showed ~3.8% of
matches and folders with many descendants looked broken. Walks all
pages now (capped at 200 = 100k photos as a sanity bound), keying
the React Query cache on the full filter set as before.

Timeline grid was leaving an unused horizontal strip on the right.
Two issues in the column math:
  - off-by-one: floor((W - 2P) / (T + G)) double-counts gaps. With N
    columns there are only N-1 inter-cell gaps, so the correct form
    is floor((W - 2P + G) / (T + G)). Reclaims a column whenever the
    remainder almost fits.
  - the floor remainder was discarded instead of distributed back
    into the cells. Treat THUMBNAIL_SIZE as a minimum and stretch
    each cell to (available - (cols-1)*gap) / cols so the row fills
    the container.

Also swap the resize listener for a ResizeObserver on the scroll
container so the grid re-flows when the sidebar collapses (window
resize alone misses that).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-09 11:22:06 +02:00
parent d27ec1af2e
commit 872be4e0cf
2 changed files with 62 additions and 26 deletions

View File

@@ -53,17 +53,33 @@ export function usePhotosQuery() {
// Goes through the shared axios instance so it inherits the
// relative /api/v1 baseURL — same-origin behind the nginx / vite
// proxy, no CORS dance required from another machine.
const response = await api.get<{ photos: Photo[]; total: number }>(
'/photos',
{
//
// The Timeline and grid views virtualize, so we load every match
// up-front rather than paginating in the UI. Backend caps per_page
// at 500, so for libraries / folders with more matches we walk
// pages until we have everything. Capped at 200 pages (= 100k
// photos) as a sanity bound.
const PER_PAGE = 500
const MAX_PAGES = 200
const all: Photo[] = []
for (let page = 1; page <= MAX_PAGES; page++) {
const response = await api.get<{
photos: Photo[]
total: number
pages: number
}>('/photos', {
params: {
page: 1,
per_page: 500,
page,
per_page: PER_PAGE,
...filterParams,
},
}
)
return response.data.photos || []
})
const photos = response.data.photos || []
all.push(...photos)
const totalPages = response.data.pages ?? 1
if (page >= totalPages || photos.length < PER_PAGE) break
}
return all
},
staleTime: 30_000,
})