Merge branch 'main' of https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery
This commit is contained in:
@@ -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/
|
||||
@@ -22,43 +24,62 @@ data/images/
|
||||
|
||||
File names are sanitised `{Artist}_{Title}.{ext}`. The image service can rediscover files on disk even when DB paths are empty (`server/image-service.js` → `syncPaintingFromDisk`).
|
||||
|
||||
### Dev vs production image storage
|
||||
|
||||
| Environment | Path on disk | Sync |
|
||||
|-------------|--------------|------|
|
||||
| **Development** | `./data/images/` in repo | Working copy on dev PC |
|
||||
| **Production** | `/mnt/BasePool/Applications/Gallery/data/images` on TrueNAS | SMB `\\192.168.10.122\Gallery\data\images` |
|
||||
|
||||
**One-direction promote:** `npm run devtoprod:images` (dev → prod, skip older). **Refresh dev from prod:** `npm run prodto:dev:images`. **Bidirectional merge** (newer file wins): `npm run harmonize:images` or full `npm run harmonize` — see [harmonize-dev-prod.md](harmonize-dev-prod.md). General sync reference: [environments.md](environments.md).
|
||||
|
||||
## Scripts overview
|
||||
|
||||
| Script | npm command | Role |
|
||||
|--------|-------------|------|
|
||||
| `seed-wikipedia.js` | `npm run seed` | Initial eras, movements, artists, paintings, influences |
|
||||
| `fetch-artist-bios.js` | `npm run fetch-artist-bios` | Wikipedia intros → `bio_short` / `bio_full` |
|
||||
| `seed-wikipedia.js` | `npm run dev:seed` | Initial eras, movements, artists, one flagship painting per artist |
|
||||
| `seed-catalog-data.js` | *(data only)* | Eras, movements, artist metadata consumed by seed |
|
||||
| `sync-image-paths.js` | `npm run dev:sync-image-paths` | Import painting rows from disk; set `image_path` / `thumbnail_path` |
|
||||
| `fetch-artist-images.js` | `npm run dev:fetch-artist-images` | Download or link artist portraits under `data/images/portraits/` |
|
||||
| `fetch-artist-bios.js` | `npm run dev:fetch-artist-bios` | Wikipedia intros → `bio_short` / `bio_full` |
|
||||
| `famous-paintings-data.js` | *(data only)* | Curated list of notable works per artist |
|
||||
| `expand-paintings.js` | `npm run expand-catalog` | Inserts works from data file for thin catalogs |
|
||||
| `art-influences-data.js` | *(data only)* | Curated painting-to-painting influence edges |
|
||||
| `update-influences.js` | `npm run update-influences` | Applies influence graph; creates missing artists/works |
|
||||
| `fetch-missing-images.js` | `npm run fetch-images` | Downloads files for paintings missing on disk |
|
||||
| `expand-paintings.js` | `npm run dev:expand-catalog` | Inserts works from data file for thin catalogs |
|
||||
| `art-influences-data.js` | *(data only)* | Curated influence edges (painting / artist / movement) |
|
||||
| `update-influences.js` | `npm run dev:update-influences` | Applies influence graph; creates missing artists/works |
|
||||
| `fetch-missing-images.js` | `npm run dev:fetch-images` | Downloads files for paintings missing on disk |
|
||||
| `image-fetcher.js` | *(library)* | Wikimedia / museum resolution used by fetch scripts and API |
|
||||
| `regenerate-thumbnails.js` | `npm run regenerate-thumbnails` | Rebuild thumbs from full images via `sharp` |
|
||||
| `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 |
|
||||
| `sync-images-to-prod.ps1` / `sync-images-from-prod.ps1` | `npm run devtoprod:images` / `npm run prodto:dev:images` | Robocopy via SMB `\\192.168.10.122\Gallery` |
|
||||
| `harmonize-db.js` / `harmonize-images.js` | `npm run harmonize:db` / `harmonize:images` | Bidirectional merge by `updated_at` / file mtime |
|
||||
| `harmonize.ps1` | `npm run harmonize` | Orchestrator: backups, optional schema, DB + image merge |
|
||||
| `regenerate-thumbnails.js` | `npm run dev:regenerate-thumbnails` | Rebuild painting thumbs from full images via `sharp` |
|
||||
| `regenerate-portrait-thumbs.js` | `npm run dev:regenerate-portrait-thumbs` | Rebuild timeline portrait thumbs (~256px) and set `portrait_thumb_path` |
|
||||
| `audit-painting-images.js` | `npm run dev:audit-painting-images` | Detect thumb/full aspect-ratio mismatches |
|
||||
| `find-duplicate-paintings.js` | `npm run dev:find-duplicates` | Report exact and near-duplicate catalog rows |
|
||||
| `migrate-checkup-flags.js` | `npm run dev:migrate:checkup-flags` | Add `checkup_checked` / `checkup_fixed` columns |
|
||||
|
||||
## Typical workflow
|
||||
|
||||
```text
|
||||
migrate → seed → fetch-artist-bios → expand-catalog → update-influences → fetch-images (per artist or batch) → build client
|
||||
migrate → seed → sync-image-paths → fetch-artist-images → fetch-artist-bios → expand-catalog → update-influences → fetch-images (per artist or batch) → build client
|
||||
```
|
||||
|
||||
1. **Seed** creates the base catalog (often one flagship painting per modern artist).
|
||||
2. **fetch-artist-bios** fills biography fields for every artist with a `wikipedia_title`.
|
||||
3. **expand-catalog** brings each artist up to at least **6** notable works (configurable via `MIN_PAINTINGS`).
|
||||
4. **fetch-images** downloads artwork files; the 3D gallery needs local files for reliable textures.
|
||||
1. **Seed** creates the base catalog (one flagship painting per artist; ~100 artists).
|
||||
2. **sync-image-paths** imports additional paintings when `data/images/paintings/` already contains files from a full clone (filename pattern `{Artist}_{Title}.jpg`).
|
||||
3. **fetch-artist-images** sets `portrait_path` from local files or Wikipedia.
|
||||
4. **fetch-artist-bios** fills biography fields for every artist with a `wikipedia_title`.
|
||||
5. **expand-catalog** brings each artist up to at least **6** notable works (configurable via `MIN_PAINTINGS`).
|
||||
6. **update-influences** loads the influence graph (*Influenced By* / *Influenced* panels, 3D hall lamps, exit navigation).
|
||||
7. **fetch-images** downloads artwork files still missing on disk; the 3D gallery needs local files for reliable textures.
|
||||
|
||||
## Seeding pipeline
|
||||
|
||||
`npm run seed` runs `scripts/seed-wikipedia.js`, which:
|
||||
`npm run dev:seed` runs `scripts/seed-wikipedia.js`, which:
|
||||
|
||||
1. Inserts **historical eras** and **art movements** (curated date ranges and colours).
|
||||
1. Inserts **historical eras** and **art movements** (curated date ranges and colours from `seed-catalog-data.js`).
|
||||
2. For each curated **artist**:
|
||||
- Creates **artist periods** and **paintings**.
|
||||
- May download portraits and painting images (depending on seed script version).
|
||||
3. Writes **painting_influences** edges from curated scholarship references (mirrored into `painting_influence_sources` when you run `npm run migrate:influence-sources` and `npm run update-influences`).
|
||||
- Creates **artist periods** and one **flagship painting**.
|
||||
- May download portraits and painting images when run with `--fetch-images`.
|
||||
3. Does **not** insert influence edges — run `npm run dev:update-influences` after seed (see [Painting influence graph](#painting-influence-graph)).
|
||||
|
||||
Those influence edges power **3D hall navigation** and painting detail panels via **`painting_influence_sources`** (see `GET /api/artists/:id/navigation` and `GET /api/paintings/:id` in [API.md](API.md)).
|
||||
|
||||
@@ -66,7 +87,7 @@ Artists are grouped by movement and century; the seed list targets at most ~100
|
||||
|
||||
## Artist biographies
|
||||
|
||||
`npm run fetch-artist-bios` reads each artist’s `wikipedia_title`, fetches the English Wikipedia **lead section**, and stores:
|
||||
`npm run dev:fetch-artist-bios` reads each artist’s `wikipedia_title`, fetches the English Wikipedia **lead section**, and stores:
|
||||
|
||||
| Field | Content |
|
||||
|-------|---------|
|
||||
@@ -91,15 +112,15 @@ The bio page (`ArtistBio.tsx`) shows lifespan, movement, summary, full text, and
|
||||
|
||||
## Expanding thin catalogs
|
||||
|
||||
Many seed artists arrive with only one famous painting. `npm run expand-catalog` runs `scripts/expand-paintings.js`, which:
|
||||
Many seed artists arrive with only one famous painting. `npm run dev:expand-catalog` runs `scripts/expand-paintings.js`, which:
|
||||
|
||||
1. Finds artists with fewer than `MIN_PAINTINGS` (default **6**).
|
||||
2. Inserts rows from `scripts/famous-paintings-data.js` that are not already present (normalized title matching skips duplicates).
|
||||
3. Sets `wikipedia_title` on each new painting for image resolution.
|
||||
|
||||
```bash
|
||||
npm run expand-catalog # DB rows only
|
||||
npm run expand-catalog -- --fetch-images # also download images (very slow)
|
||||
npm run dev:expand-catalog # DB rows only
|
||||
npm run dev:expand-catalog -- --fetch-images # also download images (very slow)
|
||||
```
|
||||
|
||||
To add more works, append entries to `famous-paintings-data.js`:
|
||||
@@ -111,13 +132,52 @@ To add more works, append entries to `famous-paintings-data.js`:
|
||||
|
||||
`wikipedia_title` is optional; it defaults to `title`. Use it when the Wikipedia article name differs from the display title.
|
||||
|
||||
Renaissance and medieval masters with large museum catalog dumps (e.g. Raphael, Dürer) are usually above the minimum already; expansion targets Impressionists, modernists, and other artists who had only a single seed painting.
|
||||
Renaissance and medieval masters with large museum catalog dumps (e.g. Raphael, Dürer) are usually above the minimum already when **`sync-image-paths`** has imported files from disk; expansion targets Impressionists, modernists, and other artists who had only a single seed painting.
|
||||
|
||||
## Importing paintings from disk
|
||||
|
||||
When the repository includes a full `data/images/paintings/` tree but the database was seeded fresh (one row per artist), run:
|
||||
|
||||
```bash
|
||||
npm run dev:sync-image-paths
|
||||
```
|
||||
|
||||
`scripts/sync-image-paths.js`:
|
||||
|
||||
1. Scans `data/images/paintings/` for full-size files (not `thumbs/`).
|
||||
2. Matches filenames to artists using the same `{Artist}_{Title}` sanitisation as `server/image-service.js`.
|
||||
3. **Updates** `image_path` / `thumbnail_path` on existing rows when files are found.
|
||||
4. **Inserts** missing painting rows for files not yet in the catalog.
|
||||
|
||||
Flags:
|
||||
|
||||
- `--dry-run` — report counts only, no DB writes.
|
||||
|
||||
Safe to re-run; already-imported works are skipped by normalized title matching.
|
||||
|
||||
Typical result on a full clone: ~1,000+ paintings linked from ~1,000 on-disk files.
|
||||
|
||||
## 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 dev:regenerate-portrait-thumbs` to backfill thumbs on dev.
|
||||
|
||||
`npm run dev: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.
|
||||
2. Otherwise downloads from Wikipedia / search fallbacks via `image-fetcher.js`.
|
||||
|
||||
Flags:
|
||||
|
||||
- `--force` — re-fetch even when `portrait_path` is already set.
|
||||
- `--limit=N` — process only the first N artists needing portraits.
|
||||
|
||||
Run after seed when portrait files exist on disk but the DB still has null `portrait_path` values.
|
||||
|
||||
## Painting influence graph
|
||||
|
||||
Directed influence links are stored in **`painting_influence_sources`**. Each row connects a painting to a **source** of type `painting`, `artist`, or `movement`, with optional period context (e.g. influence during the work’s creation year).
|
||||
|
||||
The legacy **`painting_influences`** table (painting-to-painting only) is still written alongside sources when running `npm run update-influences` — it keeps script compatibility and matches the backfill migration. **The API reads only `painting_influence_sources`**, so each edge appears once in the UI.
|
||||
The legacy **`painting_influences`** table (painting-to-painting only) is still written alongside sources when running `npm run dev:update-influences` — it keeps script compatibility and matches the backfill migration. **The API reads only `painting_influence_sources`**, so each edge appears once in the UI.
|
||||
|
||||
Influence data drives:
|
||||
|
||||
@@ -130,7 +190,7 @@ Influence data drives:
|
||||
Painting-to-painting edges exist in both tables by design. To confirm the database has no stray duplicates and that the API model is clean:
|
||||
|
||||
```bash
|
||||
npm run audit-influence-duplicates
|
||||
npm run dev:audit-influence-duplicates
|
||||
```
|
||||
|
||||
Reports: edges present in both tables, duplicate rows within either table (should be 0), and legacy-only / sources-only mismatches. If *Influenced* ever shows the same successor twice, restart the server after pulling API fixes — responses must not union legacy and sources tables.
|
||||
@@ -138,19 +198,19 @@ Reports: edges present in both tables, duplicate rows within either table (shoul
|
||||
### One-time migration
|
||||
|
||||
```bash
|
||||
npm run migrate:influence-sources # create table + backfill legacy painting edges
|
||||
npm run dev:migrate:influence-sources # create table + backfill legacy painting edges
|
||||
```
|
||||
|
||||
### Curated updates
|
||||
|
||||
`npm run update-influences` runs `scripts/update-influences.js` against `scripts/art-influences-data.js`:
|
||||
`npm run dev:update-influences` runs `scripts/update-influences.js` against `scripts/art-influences-data.js`:
|
||||
|
||||
```bash
|
||||
npm run update-influences # insert curated edges
|
||||
npm run update-influences -- --fetch-images # also download images for newly created works
|
||||
npm run update-influences -- --discover # curated + web discovery pass
|
||||
npm run discover-influences # discovery only (no curated file pass)
|
||||
npm run update-influences -- --discover --limit=20 # cap discovery to N works
|
||||
npm run dev:update-influences # insert curated edges
|
||||
npm run dev:update-influences -- --fetch-images # also download images for newly created works
|
||||
npm run dev:update-influences -- --discover # curated + web discovery pass
|
||||
npm run dev:discover-influences # discovery only (no curated file pass)
|
||||
npm run dev:update-influences -- --discover --limit=20 # cap discovery to N works
|
||||
```
|
||||
|
||||
Each entry defines a later `work` and one or more `influencedBy` sources. Legacy single-object form is still supported:
|
||||
@@ -205,7 +265,7 @@ To add or fix a branch, edit `MOVEMENT_LINEAGE` in that file and rebuild the cli
|
||||
|
||||
Separate from movement lineage layout, `client/src/data/movement-interior-styles.ts` defines a **unique 3D interior** for each seeded art movement (26 styles): wall/floor/ceiling textures, trim colours, window style, and architectural details (columns, coffered ceilings, etc.). Textures are generated procedurally in `client/src/utils/galleryProceduralTextures.ts`.
|
||||
|
||||
Wing layout (up to 55 works per wing, side-wall-only hang, window gap placement) lives in `client/src/utils/movementHallLayout.ts`. To change a movement’s look, edit its entry in `movement-interior-styles.ts` and rebuild the client.
|
||||
Wing layout (up to 55 works per wing, side-wall-only hang, window gap placement) lives in `client/src/utils/movementHallLayout.ts`. Visit order fills the **left wall** (first work at the entrance), then the **right** (last work at the entrance). The same hang applies to artist halls and guided tours. To change a movement’s look, edit its entry in `movement-interior-styles.ts` and rebuild the client.
|
||||
|
||||
## Historical event markers (frontend timeline)
|
||||
|
||||
@@ -216,6 +276,8 @@ Wing layout (up to 55 works per wing, side-wall-only hang, window gap placement)
|
||||
| Storage | TypeScript module in the client — **not** a database table |
|
||||
| Format | `{ id, name, startYear, endYear?, shortLabel? }` — omit `endYear` for a single-year pin |
|
||||
| Interaction | Click a marker to zoom the shared timeline/movement view to that period |
|
||||
| Year axis labels | Dynamic density in `Timeline.tsx` via `chooseTimelineTickInterval()` — fewer labels when zoomed out |
|
||||
| Pan/zoom batching | `createViewChangeScheduler()` in `timelineView.ts` — one React update per animation frame |
|
||||
| Vertical guides | `TimelineEventGuides.tsx` draws faint gold lines (or shaded spans) from the marker row down through the movement flow, aligned to the same year scale |
|
||||
|
||||
Edit `HISTORICAL_EVENTS` and rebuild the client to extend the set.
|
||||
@@ -230,14 +292,14 @@ Short curator-style notes on the painting detail page — separate from the infl
|
||||
| UI | `PaintingAnnotations.tsx` — numbered markers on the image (when `pos_x` / `pos_y` set) plus an “Art history notes” list |
|
||||
| API | Included as `annotations[]` on `GET /api/paintings/:id` |
|
||||
| Curated data | `scripts/painting-annotations-data.js` — artist/title keys matched via `influence-resolver.js` |
|
||||
| Load | `npm run update-painting-annotations` (replaces existing rows per painting by default) |
|
||||
| Wikipedia pass | `npm run update-painting-annotations -- --wikipedia` — one intro sentence per work from `wikipedia_title`; use `--wiki-delay=3000` if rate-limited; `--no-replace` to append without clearing curated rows |
|
||||
| Load | `npm run dev:update-painting-annotations` (replaces existing rows per painting by default) |
|
||||
| Wikipedia pass | `npm run dev:update-painting-annotations -- --wikipedia` — one intro sentence per work from `wikipedia_title`; use `--wiki-delay=3000` if rate-limited; `--no-replace` to append without clearing curated rows |
|
||||
|
||||
Categories include `subject`, `technique`, `context`, and `symbolism`. Sources cite Gombrich, museum catalogs, and Wikipedia as appropriate.
|
||||
|
||||
## Batch image fetch
|
||||
|
||||
`npm run fetch-images` (alias: `npm run search-missing-paintings`) runs `scripts/fetch-missing-images.js`. It searches multiple sources for paintings without local files:
|
||||
`npm run dev:fetch-images` (alias: `npm run dev:search-missing-paintings`) runs `scripts/fetch-missing-images.js`. It searches multiple sources for paintings without local files:
|
||||
|
||||
| Source | Notes |
|
||||
|--------|--------|
|
||||
@@ -255,13 +317,13 @@ Categories include `subject`, `technique`, `context`, and `symbolism`. Sources c
|
||||
| Harvard Art Museums | Optional (`HARVARD_ART_API_KEY` in `.env`) |
|
||||
|
||||
```bash
|
||||
npm run fetch-images # all missing, catalog order (~hours)
|
||||
npm run fetch-images -- --limit=50 # random sample of 50; 10s max per painting
|
||||
npm run fetch-images -- --limit=250 # random sample up to N (caps at current missing count)
|
||||
npm run fetch-images -- --limit=50 --max-wait=120 # slower, more thorough lookup per painting
|
||||
npm run fetch-images -- --artist="Albrecht Dürer" # one artist, catalog order
|
||||
npm run fetch-images -- --discover-only --limit=20 # fix wikipedia_title only
|
||||
npm run fetch-images -- --web-search-only --limit=50 # DuckDuckGo + Commons + multilingual Wikipedia
|
||||
npm run dev:fetch-images # all missing, catalog order (~hours)
|
||||
npm run dev:fetch-images -- --limit=50 # random sample of 50; 10s max per painting
|
||||
npm run dev:fetch-images -- --limit=250 # random sample up to N (caps at current missing count)
|
||||
npm run dev:fetch-images -- --limit=50 --max-wait=120 # slower, more thorough lookup per painting
|
||||
npm run dev:fetch-images -- --artist="Albrecht Dürer" # one artist, catalog order
|
||||
npm run dev:fetch-images -- --discover-only --limit=20 # fix wikipedia_title only
|
||||
npm run dev:fetch-images -- --web-search-only --limit=50 # DuckDuckGo + Commons + multilingual Wikipedia
|
||||
```
|
||||
|
||||
Each run prints **`Missing local files: N`** at startup — that is the current count of catalogued paintings with no full-size or thumbnail file under `data/images/`. A painting counts as present if **either** file exists on disk.
|
||||
@@ -297,7 +359,7 @@ Requests are deduplicated (`inflight` map) and timeout after 15 seconds. On-dema
|
||||
|
||||
## Preload before 3D gallery
|
||||
|
||||
`POST /api/artists/:id/preload-images` runs **local-only** linking — no network. Call this when entering an **artist’s** 3D hall so textures use files already on disk.
|
||||
`POST /api/artists/:id/preload-images` is a **public** route (no curator login). It runs **local-only** linking — no network. The React client calls it automatically when entering an **artist’s** 3D hall so textures use files already on disk.
|
||||
|
||||
**Movement galleries** (`GET /api/movements/:id/gallery`) do not use preload — they load the full painting list from the API and resolve local paths the same way as artist halls. Works without files still show the canvas cover in the frame.
|
||||
|
||||
@@ -317,10 +379,10 @@ These live in `client/public/` (and `client/dist/` after build).
|
||||
|
||||
1. Insert rows into `artists`, `artist_periods`, `paintings` (or extend the seed script).
|
||||
2. Place image files under `data/images/` using the naming convention.
|
||||
3. Run `npm run fetch-artist-bios` for the new artist’s biography.
|
||||
4. Add entries to `famous-paintings-data.js` and run `npm run expand-catalog` if needed.
|
||||
5. Run `npm run fetch-images -- --artist="…"` or rely on preload / on-demand sync.
|
||||
6. Add influence rows via `npm run update-influences` / `art-influences-data.js` (writes both `painting_influence_sources` and legacy painting edges).
|
||||
3. Run `npm run dev:fetch-artist-bios` for the new artist’s biography.
|
||||
4. Add entries to `famous-paintings-data.js` and run `npm run dev:expand-catalog` if needed.
|
||||
5. Run `npm run dev:fetch-images -- --artist="…"` or rely on preload / on-demand sync.
|
||||
6. Add influence rows via `npm run dev:update-influences` / `art-influences-data.js` (writes both `painting_influence_sources` and legacy painting edges).
|
||||
|
||||
## PainterPalette external dataset
|
||||
|
||||
@@ -329,8 +391,8 @@ These live in `client/public/` (and `client/dist/` after build).
|
||||
### One-time setup
|
||||
|
||||
```bash
|
||||
npm run migrate:artist-palette # adds artists.palette_metadata JSONB
|
||||
npm run import-painter-palette # enrich + influence links
|
||||
npm run dev:migrate:artist-palette # adds artists.palette_metadata JSONB
|
||||
npm run dev:import-painter-palette # enrich + influence links
|
||||
```
|
||||
|
||||
### What gets imported
|
||||
@@ -349,10 +411,10 @@ Name matching uses normalized strings plus aliases in `scripts/painter-palette-l
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
npm run analyze-painter-palette # match report
|
||||
npm run import-painter-palette -- --dry-run
|
||||
npm run import-painter-palette -- --metadata-only
|
||||
npm run import-painter-palette -- --influences-only
|
||||
npm run dev:analyze-painter-palette # match report
|
||||
npm run dev:import-painter-palette -- --dry-run
|
||||
npm run dev:import-painter-palette -- --metadata-only
|
||||
npm run dev:import-painter-palette -- --influences-only
|
||||
```
|
||||
|
||||
Re-run `import-painter-palette` after adding gallery artists or updating the CSV; existing palette influence rows are skipped if already present.
|
||||
@@ -362,7 +424,7 @@ Re-run `import-painter-palette` after adding gallery artists or updating the CSV
|
||||
Export the full painting catalog as CSV:
|
||||
|
||||
```bash
|
||||
npm run export-paintings
|
||||
npm run dev:export-paintings
|
||||
```
|
||||
|
||||
Writes **`Output/paintings.csv`** with columns `artist`, `painting`, `year` (sorted by artist, year, title). The `Output/` folder is git-ignored by convention; regenerate after catalog changes.
|
||||
@@ -394,7 +456,7 @@ The catalog can contain the same work more than once — usually from a **double
|
||||
### Find duplicates
|
||||
|
||||
```bash
|
||||
npm run find-duplicates
|
||||
npm run dev:find-duplicates
|
||||
```
|
||||
|
||||
Runs `scripts/find-duplicate-paintings.js`, which reports:
|
||||
@@ -419,9 +481,9 @@ When **Debug mode** is on (home header) or from the **Checkup** page:
|
||||
|
||||
1. **Search** — `GET /api/paintings/:id/debug-image-search` (or `…/debug-portrait-search` for artists) tries Google Custom Search (if `GOOGLE_CSE_API_KEY` + `GOOGLE_CSE_CX` are set in `.env`), Google Arts & Culture, Google Images scrape, then DuckDuckGo (`searchGoogleImagesFirst` / `searchArtistPortraitFirst` in `scripts/image-fetcher.js`).
|
||||
2. **More** — `GET …/debug-image-search/more` or `…/debug-portrait-search/more` returns up to 20 ranked candidates (`searchPaintingImagesMany` / `searchArtistPortraitMany`). The modal shows each thumbnail with **resolution** when the search API provides dimensions; otherwise the client probes via `GET /api/debug/image-proxy`.
|
||||
3. **Fix** — `POST …/fix-image` or `…/fix-portrait` downloads the chosen URL via `downloadImageForFix` → `replacePaintingImageFromUrl` / `replaceArtistPortraitFromUrl` in `server/image-service.js`, regenerates thumbnails with `sharp`, and sets `checkup_fixed` + `checkup_checked`.
|
||||
3. **Fix** — `POST …/fix-image` or `…/fix-portrait` downloads the chosen URL via `downloadImageForFix` → `replacePaintingImageFromUrl` / `replaceArtistPortraitFromUrl` in `server/image-service.js`. The server **always regenerates thumbnails from the saved full image** (`writePaintingThumb` / `writePortraitThumb` via `sharp` — not the search-result thumb URL), updates `thumbnail_path` / `portrait_thumb_path`, and sets `checkup_fixed` + `checkup_checked`.
|
||||
4. **Clear** — `POST …/clear-image` or `…/clear-portrait` deletes local file(s), nulls DB paths, sets both flags. Cleared slots stay empty in the UI (no placeholder; `checkup_fixed` prevents on-demand refetch for paintings).
|
||||
5. **Upload** — `POST …/upload-image` or `…/upload-portrait` accepts a base64-encoded file in JSON (Express body limit **20 MB**; decoded image max **15 MB**), validates with `sharp`, writes to the standard filename under `data/images/`.
|
||||
5. **Upload** — `POST …/upload-image` or `…/upload-portrait` accepts a base64-encoded file in JSON (Express body limit **20 MB**; decoded image max **15 MB**), validates with `sharp`, writes to the standard filename under `data/images/`, and regenerates the matching thumbnail the same way as **Fix it**. The file picker uses a native `<label>` + hidden `<input>` (`DebugUploadButton.tsx`) so the `change` event is reliable on Windows/Chromium.
|
||||
6. **Remove entry** (painting detail only) — `DELETE /api/paintings/:id` via `deletePainting()` in `server/image-service.js`: deletes image files, removes the DB row (cascade on influence/annotation tables), refetches artist/movement gallery data, remounts the 3D hall, and navigates to the next or previous catalog work with no confirmation dialog.
|
||||
|
||||
### Debug panel (painting detail and artist bio)
|
||||
@@ -431,14 +493,16 @@ With debug mode on, `PaintingDetail.tsx` and `ArtistBio.tsx` show a bottom-left
|
||||
| Button | API (paintings / portraits) | Effect |
|
||||
|--------|----------------------------|--------|
|
||||
| **Checked** | `PATCH …/checkup-flags` `{ "checked": true }` | Marks reviewed; gold frame (paintings) or gold portrait border (artists) |
|
||||
| **Fix it** | `POST …/fix-image` / `…/fix-portrait` | Saves top search result to disk, sets both flags, refreshes detail + gallery / timeline |
|
||||
| **More** | `GET …/debug-*-search/more` then fix endpoint | Modal with 20 clickable results (resolution label under each thumb); pick one to replace |
|
||||
| **Fix it** | `POST …/fix-image` / `…/fix-portrait` | Saves top search result to disk, **regenerates thumb from full image**, sets both flags, refreshes detail + gallery / timeline |
|
||||
| **More** | `GET …/debug-*-search/more` then fix endpoint | Modal with 20 clickable results (resolution label under each thumb); pick one to replace (thumb regenerated from downloaded full) |
|
||||
| **Clear** | `POST …/clear-image` / `…/clear-portrait` | Removes file(s), empty frame in UI |
|
||||
| **Upload** | `POST …/upload-image` / `…/upload-portrait` | Local file picker → save like **Fix it** |
|
||||
| **Upload** | `POST …/upload-image` / `…/upload-portrait` | Local file picker (`DebugUploadButton`) → save full image + **auto-generated thumb**; centered **Loading…** overlay; clears preview and blocks search/fix while uploading |
|
||||
| **Remove entry** | `DELETE /api/paintings/:id` | **Paintings only** — permanent delete + gallery refresh + catalog navigation |
|
||||
|
||||
The client passes `searchUrl`, `source`, and `thumbUrl` from search results to improve download reliability. After a fix, clear, upload, or remove, `HomePage` updates the gallery session and appends a revision query on texture URLs so replaced files reload even when the path is unchanged.
|
||||
The client passes `searchUrl`, `source`, and `thumbUrl` from search results to improve download reliability. After a fix, clear, upload, or remove, `HomePage` updates the gallery session. Image URLs include `?v=<mtime_ms>` from API **`image_cache_key`** / **`thumbnail_cache_key`** (file `mtime` on disk) so replaced files reload after a full page refresh even when the relative path is unchanged. In-session counters still bump immediately after a mutation.
|
||||
|
||||
Checkup **Search visible** queues search for filtered rows only (3 concurrent); it does not search the full catalog on load.
|
||||
|
||||
**Curator login required** for all debug/checkup UI and mutating API routes. Guests can browse and enter 3D halls normally; preload remains public. See [API.md — Authentication](API.md#authentication).
|
||||
|
||||
See [API.md](API.md#developer-image-audit) and [basics.md](basics.md#developer-tools-image-audit).
|
||||
|
||||
Reference in New Issue
Block a user