feat(frontend): port Places, People & photo tabs to the Svelte app
Bring the Photos/People/Places UI that main added (in the legacy vanilla frontend) into the SvelteKit rewrite, wired to the now-merged backend (/api/photos/geo, /api/people/*). Photos page (routes/photos/+page.svelte): - Moments | Places | People sub-tabs (the People tab appears only when the faces feature is enabled, via a /api/people capability probe), mirroring the vanilla photos sub-nav. - Square ↔ justified layout toggle. Justified uses a Flickr-style row-packer over the width/height the photos list endpoint returns (PhotoItem), falling back to 1:1 when dimensions are missing. New components: - PhotoLightbox.svelte — the lightbox extracted from the photos page into a reusable component (items + bindable index, onDelete callback) so the grid, People and Places all share one implementation (no duplication). - PlacesMap.svelte — MapLibre GL map with server-clustered markers; the vector basemap is optional (probed at /basemaps/basemap.pmtiles, themed fallback otherwise). Cluster click zooms in or opens the lightbox. - PeopleView.svelte — identity-cluster grid → per-person photo grid, with rename via the in-app prompt dialog. Supporting: - api/endpoints/people.ts (+ peopleEnabled probe); photos.ts gains fetchPhotosGeo + GeoCluster + PhotoItem; fileThumbnailUrl takes a size. - lib/vendor/maplibre.ts — minimal typings + lazy loader for the vendored MapLibre GL + pmtiles globals (kept any-free for ESLint). - utils/media.ts — shared isVideo / photoTimestamp / minimalPhotoItem. - Vendored maplibre-gl 5.24.0 + pmtiles 4.4.1 under static/vendors and an optional static/basemaps dir, matching the PR's vendored-asset pattern. - New photos.tab_*/layout_*/map_* + people.* keys in en.json. Verified: npm run check (svelte-check + eslint + stylelint + prettier), npm run test:unit (36 pass), and npm run build all green.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# The vector basemap is large (tens of MB) and operator-provided — never
|
||||
# commit it to the repo. Drop a Protomaps `.pmtiles` here as `basemap.pmtiles`
|
||||
# and the existing static file server (tower-http ServeDir, Range-capable)
|
||||
# will serve it to the Places map. See README.md.
|
||||
*.pmtiles
|
||||
@@ -0,0 +1,33 @@
|
||||
# Places basemap (optional)
|
||||
|
||||
The **Places** photo map renders your geotagged photos as clusters. It works
|
||||
out of the box **without** a basemap (clusters on a plain background). To get a
|
||||
real street/terrain backdrop, drop a self-hosted vector basemap here — no
|
||||
third-party tile API, fully offline.
|
||||
|
||||
## How it works (Approach "A")
|
||||
|
||||
OxiCloud already serves `static/` through `tower-http`'s `ServeDir`, which
|
||||
honours **HTTP Range** requests. A [PMTiles](https://docs.protomaps.com/pmtiles/)
|
||||
basemap is a *single file* read directly by the browser via Range — so the
|
||||
basemap is just a static file the app already knows how to serve. No extra
|
||||
backend, no tile server, no API keys.
|
||||
|
||||
## Enabling it
|
||||
|
||||
1. Get a Protomaps `.pmtiles` basemap (vector, ODbL OpenStreetMap data):
|
||||
- Whole planet z0–15 (~120 GB) or a smaller global `z0-6` (~60 MB), or
|
||||
- A **regional extract** (recommended — only the area you need, a few MB):
|
||||
```sh
|
||||
# one-time, downloads only your bounding box from the remote planet
|
||||
pmtiles extract https://build.protomaps.com/<DATE>.pmtiles basemap.pmtiles \
|
||||
--bbox=<west>,<south>,<east>,<north>
|
||||
```
|
||||
See https://docs.protomaps.com/basemaps/downloads
|
||||
2. Place it here as **`static/basemaps/basemap.pmtiles`** (this path is
|
||||
git-ignored on purpose — see `.gitignore`).
|
||||
3. Reload the Places view. The map will pick it up automatically.
|
||||
|
||||
The bundled style is **label-light** (water / land / roads / buildings, no
|
||||
text) so it needs no glyph/sprite assets. Attribution “© OpenStreetMap”
|
||||
(ODbL) is shown automatically when a basemap is present.
|
||||
@@ -88,7 +88,22 @@
|
||||
"full_resolution": "Full resolution",
|
||||
"group_by": "Group by",
|
||||
"trash_partial": "{{ok}} of {{total}} moved to trash.",
|
||||
"trashed": "{{n}} moved to trash."
|
||||
"trashed": "{{n}} moved to trash.",
|
||||
"layout_square": "Grid",
|
||||
"layout_justified": "Justified",
|
||||
"tab_moments": "Moments",
|
||||
"tab_places": "Places",
|
||||
"tab_people": "People",
|
||||
"map_loading": "Loading map…",
|
||||
"map_error": "Could not load the map"
|
||||
},
|
||||
"people": {
|
||||
"unnamed": "Unnamed",
|
||||
"empty": "No people yet",
|
||||
"disabled": "Face recognition is disabled",
|
||||
"rename_title": "Name this person",
|
||||
"name_label": "Name",
|
||||
"back": "Back"
|
||||
},
|
||||
"music": {
|
||||
"create_playlist": "Create Playlist",
|
||||
|
||||
+1
File diff suppressed because one or more lines are too long
+59
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user