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,42 @@
|
||||
/** Shared helpers for the photo/video timeline (used by the grid, lightbox,
|
||||
* People and Places views). */
|
||||
import type { FileItem } from '$lib/api/types';
|
||||
|
||||
/** True for video tiles (they get a play badge and client-side frame thumbs). */
|
||||
export function isVideo(p: FileItem): boolean {
|
||||
return (p.mime_type ?? '').startsWith('video/');
|
||||
}
|
||||
|
||||
/**
|
||||
* EXIF-aware capture timestamp in milliseconds. `sort_date`/`created_at` are
|
||||
* stored in seconds; values below ~1e12 are treated as seconds and scaled up.
|
||||
*/
|
||||
export function photoTimestamp(p: FileItem): number {
|
||||
const v = p.sort_date || p.created_at || 0;
|
||||
return v < 1e12 ? v * 1000 : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a minimal {@link FileItem} from just an id — used by People and Places
|
||||
* to open the lightbox by id and let it lazily fetch the rest (name, EXIF).
|
||||
*/
|
||||
export function minimalPhotoItem(id: string): FileItem {
|
||||
return {
|
||||
category: 'image',
|
||||
created_at: 0,
|
||||
icon_class: '',
|
||||
icon_special_class: '',
|
||||
id,
|
||||
mime_type: 'image/jpeg',
|
||||
modified_at: 0,
|
||||
name: '',
|
||||
owner_id: '',
|
||||
folder_id: '',
|
||||
path: '',
|
||||
size: 0,
|
||||
size_formatted: '',
|
||||
sort_date: 0,
|
||||
etag: '',
|
||||
content_hash: ''
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user