feat(places): photo map view (MapLibre + PMTiles, Approach A)

Adds a Places tab to the Photos section: a self-hosted MapLibre GL map of
the user's geotagged photos.

- places.js: lazy-loads vendored MapLibre + pmtiles.js on first open; draws
  the server-aggregated clusters (GET /api/photos/geo) as HTML thumbnail
  markers (no glyphs/sprites, no client-side clustering); pan/zoom refetches
  for the new viewport; click a cluster to zoom in, or a single photo to
  open it in the lightbox.
- Optional vector basemap read directly from /basemaps/basemap.pmtiles over
  HTTP Range (label-light Protomaps style, light/dark aware); falls back to
  a themed background when no basemap is present. ODbL attribution shown.
- "Moments | Places" sub-nav wired into the Photos section.
- enable_places now defaults on, since the route + UI are ready.

No new backend serving code: tower-http ServeDir already serves static/
with Range, so the .pmtiles basemap is just a static file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JW6ghFMDtnRYuYNzZhb47M
This commit is contained in:
Claude
2026-06-19 11:17:56 +00:00
parent bb3d73926a
commit 513b6224b4
6 changed files with 479 additions and 3 deletions
+4 -1
View File
@@ -10,6 +10,7 @@ import { batchToolbar } from '../features/files/batchToolbar.js';
import { favorites } from '../features/library/favorites.js';
import { musicView } from '../features/library/music.js';
import { photosView } from '../features/library/photos.js';
import { placesView } from '../features/library/places.js';
import { grants } from '../model/grants.js';
import { favoritesView } from '../views/favorites/favoritesView.js';
import { mySharesView } from '../views/myShares/mySharesView.js';
@@ -225,9 +226,10 @@ function setCurrentSection(section) {
// Reset owner column — sections that need it re-enable it explicitly below.
ui.setOwnerColumnVisible(false);
// Hide photosView when switching to any other section
// Hide photosView (+ the Places sub-view) when switching to any other section
if (section !== 'photos' && photosView) {
photosView.hide();
placesView.unmountTabs();
}
// Hide musicView when switching to any other section
@@ -451,6 +453,7 @@ function switchToPhotosSection() {
if (photosView) {
photosView.show();
}
placesView.mountTabs();
if (batchToolbar) batchToolbar.clear();
}