diff --git a/frontend/src/components/map/MapView.tsx b/frontend/src/components/map/MapView.tsx
index bbec2dd..a72238e 100644
--- a/frontend/src/components/map/MapView.tsx
+++ b/frontend/src/components/map/MapView.tsx
@@ -1,9 +1,6 @@
import { useEffect, useMemo, useRef } from 'react'
import { useQuery } from '@tanstack/react-query'
-import { MapContainer, TileLayer, Marker, useMap } from 'react-leaflet'
-// react-leaflet-cluster has no own .d.ts that survives strict mode in
-// every project, so we let TS infer from its runtime export.
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+import { MapContainer, TileLayer, CircleMarker, useMap } from 'react-leaflet'
// @ts-ignore — package ships JS with no bundled types
import MarkerClusterGroup from 'react-leaflet-cluster'
import L from 'leaflet'
@@ -18,24 +15,8 @@ interface MapPoint {
taken_at: string | null
}
-/** Build the divIcon used for each photo marker. The HTML is a tiny
- * square thumbnail with a white border + drop shadow so it reads on
- * any tile background. Memoised per-photo so we don't re-create the
- * L.DivIcon on every re-render. */
-function buildPhotoIcon(photoId: string): L.DivIcon {
- const url = photosApi.getThumbnailUrl(photoId, 'small')
- return L.divIcon({
- className: 'photo-map-marker',
- html: `

`,
- iconSize: [56, 56],
- iconAnchor: [28, 28],
- popupAnchor: [0, -28],
- })
-}
-
/** Pans/zooms the map to fit the supplied points the first time they
- * arrive. Subsequent loads (e.g. cache refresh) leave the user's pan
- * alone — they're probably mid-investigation. */
+ * arrive. Subsequent loads leave the user's pan alone. */
function FitBoundsOnce({ points }: { points: MapPoint[] }) {
const map = useMap()
const fittedRef = useRef(false)
@@ -52,25 +33,10 @@ export function MapView() {
const { data: points = [], isLoading, error } = useQuery({
queryKey: ['photos', 'map'],
queryFn: () => photosApi.mapPoints(),
- staleTime: 60 * 1000,
+ staleTime: 5 * 60 * 1000,
})
- // Marker click hands off to the same PreviewView the timeline uses, so
- // the user gets the full preview UI (large image, info panel with
- // location, filmstrip nav between map photos) instead of a bespoke
- // map-only lightbox. We pass the map's own point order as the visible
- // sequence so left/right arrows step through neighboring markers.
const openPreview = usePhotoStore((s) => s.openPreview)
-
- // Stable per-marker icons. Re-created only when the set of point ids
- // changes — the underlying L.DivIcon objects are pure HTML so reusing
- // them is safe across re-renders.
- const iconsById = useMemo(() => {
- const map = new Map()
- for (const p of points) map.set(p.id, buildPhotoIcon(p.id))
- return map
- }, [points])
-
const visibleSequence = useMemo(() => points.map((p) => p.id), [points])
if (isLoading) {
@@ -116,54 +82,33 @@ export function MapView() {
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
-
- {points.map((p) => {
- const icon = iconsById.get(p.id)
- if (!icon) return null
- return (
- openPreview(p.id, visibleSequence)}
- />
- )
- })}
+
+ {points.map((p) => (
+ openPreview(p.id, visibleSequence),
+ }}
+ />
+ ))}
-
-
)
}
-
-function PhotoMarker({
- point,
- icon,
- onClick,
-}: {
- point: MapPoint
- icon: L.DivIcon
- onClick: () => void
-}) {
- return (
-
- )
-}