Speed up timeline load with portrait thumbs, bootstrap API, and caching.

Add catalog bootstrap endpoint, portrait thumbnail pipeline, lazy queued timeline images, gzip compression, and 3D texture throttling with code-split VirtualGallery.
This commit is contained in:
Danila Khodjaef
2026-07-06 14:27:29 +03:00
parent bdddadc4d6
commit 99b8559607
117 changed files with 646 additions and 171 deletions
+44 -2
View File
@@ -86,6 +86,48 @@ Curator mutations are recorded in `curator_audit_log` (see [DB_structure.md](DB_
---
## `GET /api/catalog/bootstrap`
**Preferred for timeline first paint.** Returns bounds, eras, movements, and slim artist rows in a single response (replaces the separate `bounds` + `timeline` + `artists?timeline=1` waterfall).
**Query**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `start` | int | bounds `min_year` | Window start year |
| `end` | int | bounds `max_year` | Window end year |
**Response**
```json
{
"bounds": { "min_year": -800, "max_year": 2100 },
"eras": [ ],
"movements": [ ],
"artists": [
{
"id": 1,
"name": "Claude Monet",
"birth_year": 1840,
"death_year": 1926,
"movement_id": 12,
"portrait_path": "portraits/Claude_Monet.jpg",
"portrait_thumb_path": "portraits/thumbs/Claude_Monet_thumb.jpg",
"wikipedia_title": "Claude Monet",
"century": 19,
"movement_name": "Impressionism",
"movement_color": "#6B8E9F"
}
]
}
```
**Caching:** `Cache-Control: public, max-age=300` with `ETag` (304 when catalog row counts unchanged).
The React home page loads this endpoint **once** on mount. Pan and zoom filter movements and portraits **client-side** — no refetch per view change.
---
## `GET /api/bounds`
Returns the overall timeline year range used to initialise the zoomable timeline.
@@ -138,11 +180,11 @@ Artists for timeline portraits and the movement flow diagram.
| `start` | int | Only artists alive after this year |
| `end` | int | Only artists born before this year |
| `movement_id` | int | Filter by movement |
| `timeline` | bool | When `1` or `true`, return a lightweight row set for the home-page timeline (omits `bio_full` and other heavy fields) |
| `timeline` | bool | When `1` or `true`, return a lightweight row set for the home-page timeline (omits `bio_short`, `bio_full`; includes `portrait_thumb_path`) |
**Response** — array of artist objects with joined `movement_name` and `movement_color`.
The React home page loads the timeline catalog **once** on mount via `GET /api/bounds`, `GET /api/timeline?start=…&end=…` (full range), and `GET /api/artists?timeline=1`. Pan and zoom filter movements and portraits **client-side** — no refetch per view change. Rapid pan/zoom is batched with `createViewChangeScheduler()` (one React update per animation frame).
The React home page loads the timeline catalog **once** on mount via `GET /api/catalog/bootstrap` (or legacy: `GET /api/bounds` + `GET /api/timeline` + `GET /api/artists?timeline=1`). Pan and zoom filter movements and portraits **client-side** — no refetch per view change. Rapid pan/zoom is batched with `createViewChangeScheduler()` (one React update per animation frame).
---
+8 -3
View File
@@ -12,8 +12,10 @@ How catalog content, biographies, and artwork files enter the system.
```text
data/images/
├── portraits/ # Artist headshots
── Claude_Monet.jpg
├── portraits/ # Artist headshots (display ~900px wide)
── Claude_Monet.jpg
│ └── thumbs/ # Timeline thumbnails (~256px)
│ └── Claude_Monet_thumb.jpg
└── paintings/
├── Claude_Monet_Water_Lilies.jpg
└── thumbs/
@@ -47,7 +49,8 @@ Promote dev → prod files: `npm run images:sync-to-prod` (after `net use \\192.
| `fetch-missing-images.js` | `npm run fetch-images` | Downloads files for paintings missing on disk |
| `image-fetcher.js` | *(library)* | Wikimedia / museum resolution used by fetch scripts and API |
| `sync-images-to-prod.ps1` / `sync-images-from-prod.ps1` | `npm run images:sync-*` | Robocopy via SMB `\\192.168.10.122\Gallery` |
| `regenerate-thumbnails.js` | `npm run regenerate-thumbnails` | Rebuild thumbs from full images via `sharp` |
| `regenerate-thumbnails.js` | `npm run regenerate-thumbnails` | Rebuild painting thumbs from full images via `sharp` |
| `regenerate-portrait-thumbs.js` | `npm run regenerate-portrait-thumbs` | Rebuild timeline portrait thumbs (~256px) and set `portrait_thumb_path` |
| `audit-painting-images.js` | `npm run audit-painting-images` | Detect thumb/full aspect-ratio mismatches |
| `find-duplicate-paintings.js` | `npm run find-duplicates` | Report exact and near-duplicate catalog rows |
| `migrate-checkup-flags.js` | `npm run migrate:checkup-flags` | Add `checkup_checked` / `checkup_fixed` columns |
@@ -154,6 +157,8 @@ Typical result on a full clone: ~1,000+ paintings linked from ~1,000 on-disk fil
## Artist portraits
Timeline movement flow loads **`portrait_thumb_path`** (~256px JPEG under `portraits/thumbs/{Artist}_thumb.jpg`) when available; biography and 3D exit navigation use full `portrait_path`. After adding portraits, run `npm run regenerate-portrait-thumbs` to backfill thumbs on dev.
`npm run fetch-artist-images` runs `scripts/fetch-artist-images.js`:
1. For each artist, checks `data/images/portraits/{Artist}.jpg` (or other extensions) and sets `portrait_path` when a local file exists.