From da60c6ce9c2941ecf3d3370a6f7b09688c2dd70b Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Mon, 8 Jun 2026 10:48:57 -0700 Subject: [PATCH] feat(maps): persist map view across refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maps page reset to the default US-wide view on every refresh because initialViewState was hardcoded. Save the position and zoom to localStorage on each move-end (key nomad:map-view, matching the existing scale-unit pattern) and restore it at mount: saved view → default. The restore is bounds-checked so a corrupt value falls through to the default. Replaces #815, whose branch had drifted far out of scope (56 files of stale-base/merged-commit noise plus unrelated map-feature WIP). This is just the persist-view improvement, ported cleanly onto current dev. The null-island and coordinate-search parts of #815 targeted URL-param code that never landed on dev, so they don't apply here. --- .../inertia/components/maps/MapComponent.tsx | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/admin/inertia/components/maps/MapComponent.tsx b/admin/inertia/components/maps/MapComponent.tsx index f7a3a8a..aa9d9c0 100644 --- a/admin/inertia/components/maps/MapComponent.tsx +++ b/admin/inertia/components/maps/MapComponent.tsx @@ -28,6 +28,38 @@ type MapComponentProps = { showCoordinatesEnabled: boolean } +const SAVED_MAP_VIEW_KEY = 'nomad:map-view' +const DEFAULT_MAP_VIEW = { longitude: -101, latitude: 40, zoom: 3.5 } + +type SavedMapView = { longitude: number; latitude: number; zoom: number } + +// Restore the last map position/zoom from localStorage so a refresh of /maps doesn't snap back +// to the default US-wide view. Bounds-checked so a corrupt or out-of-range value falls through +// to the default instead of throwing. +const getSavedMapView = (): SavedMapView | null => { + try { + const raw = localStorage.getItem(SAVED_MAP_VIEW_KEY) + if (!raw) return null + const parsed = JSON.parse(raw) + if ( + parsed && + typeof parsed === 'object' && + Number.isFinite(parsed.longitude) && + Number.isFinite(parsed.latitude) && + Number.isFinite(parsed.zoom) && + parsed.latitude >= -90 && + parsed.latitude <= 90 && + parsed.longitude >= -180 && + parsed.longitude <= 180 + ) { + return { longitude: parsed.longitude, latitude: parsed.latitude, zoom: parsed.zoom } + } + } catch { + // ignore — fall through to default + } + return null +} + export default function MapComponent({ isHoveringUI, showCoordinatesEnabled, @@ -47,6 +79,10 @@ export default function MapComponent({ () => (localStorage.getItem('nomad:map-scale-unit') as ScaleUnit) || 'metric' ) + // Resolve the initial view once at mount: saved view → default. Lazy so it isn't recomputed + // on every render. + const [initialViewState] = useState(() => getSavedMapView() ?? DEFAULT_MAP_VIEW) + const [cursorLngLat, setCursorLngLat] = useState<{ lng: number lat: number @@ -171,10 +207,18 @@ export default function MapComponent({ cursor={isDraggingMap ? 'grabbing' : 'crosshair'} mapStyle={`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/maps/styles`} mapLib={maplibregl} - initialViewState={{ - longitude: -101, - latitude: 40, - zoom: 3.5, + initialViewState={initialViewState} + onMoveEnd={(e) => { + // Persist the view so a refresh restores where the user was, not the default. + const { longitude, latitude, zoom } = e.viewState + try { + localStorage.setItem( + SAVED_MAP_VIEW_KEY, + JSON.stringify({ longitude, latitude, zoom }) + ) + } catch { + // ignore persistence failures (private mode, quota) + } }} onMouseDown={() => { setIsDraggingMap(true)