mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-05-08 23:15:11 -04:00
feat: add map center and zoom state management with URL synchronization
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
export let mapClass = 'w-full h-full';
|
||||
export let standardControls = true;
|
||||
export let zoom = 2;
|
||||
export let center: [number, number] = [0, 0];
|
||||
export let mapClickEnabled: boolean = true;
|
||||
|
||||
// Basemap
|
||||
@@ -128,12 +129,25 @@
|
||||
mapClick: { lngLat: { lng: number; lat: number } };
|
||||
markerClick: { feature: unknown; markerProps: Record<string, unknown> | null };
|
||||
clusterClick: LayerClickInfo;
|
||||
mapMove: { center: { lng: number; lat: number }; zoom: number };
|
||||
}>();
|
||||
|
||||
function handleMapClick(e: CustomEvent<{ lngLat: { lng: number; lat: number } }>) {
|
||||
dispatch('mapClick', e.detail);
|
||||
}
|
||||
|
||||
function handleMapMove() {
|
||||
if (!map) return;
|
||||
const mapCenter = map.getCenter();
|
||||
const mapZoom = map.getZoom();
|
||||
if (mapCenter && typeof mapZoom === 'number') {
|
||||
dispatch('mapMove', {
|
||||
center: { lng: mapCenter.lng, lat: mapCenter.lat },
|
||||
zoom: mapZoom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setBasemapType(next: string) {
|
||||
basemapType = next;
|
||||
}
|
||||
@@ -261,7 +275,14 @@
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<MapLibre bind:map style={getBasemapUrl(basemapType)} class={mapClass} {standardControls} {zoom}>
|
||||
<MapLibre
|
||||
bind:map
|
||||
style={getBasemapUrl(basemapType)}
|
||||
class={mapClass}
|
||||
{standardControls}
|
||||
{zoom}
|
||||
{center}
|
||||
>
|
||||
{#key styleNonce}
|
||||
{#if effectiveGeoJson && Array.isArray(effectiveGeoJson.features) && effectiveGeoJson.features.length > 0}
|
||||
{#if clusterEnabled}
|
||||
@@ -331,7 +352,9 @@
|
||||
{/key}
|
||||
|
||||
{#if mapClickEnabled}
|
||||
<MapEvents on:click={handleMapClick} />
|
||||
<MapEvents on:click={handleMapClick} on:moveend={handleMapMove} />
|
||||
{:else}
|
||||
<MapEvents on:moveend={handleMapMove} />
|
||||
{/if}
|
||||
<slot name="overlays" {map} />
|
||||
</MapLibre>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import type { ClusterOptions } from 'svelte-maplibre';
|
||||
import { goto } from '$app/navigation';
|
||||
import { getActivityColor } from '$lib';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
// Icons
|
||||
import MapIcon from '~icons/mdi/map';
|
||||
@@ -30,6 +31,11 @@
|
||||
|
||||
let basemapType: string = 'default';
|
||||
|
||||
// Map state from URL params
|
||||
let mapZoom: number = 2;
|
||||
let mapCenter: [number, number] = [0, 0];
|
||||
let updateUrlTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
export let initialLatLng: { lat: number; lng: number } | null = null;
|
||||
|
||||
let visitedRegions: VisitedRegion[] = data.props.visitedRegions;
|
||||
@@ -350,7 +356,40 @@
|
||||
newMarker = null;
|
||||
}
|
||||
|
||||
function updateUrlParams(lat: number, lng: number, zoom: number) {
|
||||
if (updateUrlTimeout) clearTimeout(updateUrlTimeout);
|
||||
updateUrlTimeout = setTimeout(() => {
|
||||
const params = new URLSearchParams($page.url.searchParams);
|
||||
params.set('lat', lat.toFixed(6));
|
||||
params.set('lng', lng.toFixed(6));
|
||||
params.set('zoom', zoom.toFixed(2));
|
||||
goto(`?${params.toString()}`, { replaceState: true, noScroll: true, keepFocus: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleMapMove(e: CustomEvent<{ center: { lng: number; lat: number }; zoom: number }>) {
|
||||
const { center, zoom } = e.detail;
|
||||
updateUrlParams(center.lat, center.lng, zoom);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Initialize from URL params
|
||||
const params = $page.url.searchParams;
|
||||
const lat = params.get('lat');
|
||||
const lng = params.get('lng');
|
||||
const zoom = params.get('zoom');
|
||||
|
||||
if (lat && lng && zoom) {
|
||||
const parsedLat = parseFloat(lat);
|
||||
const parsedLng = parseFloat(lng);
|
||||
const parsedZoom = parseFloat(zoom);
|
||||
|
||||
if (Number.isFinite(parsedLat) && Number.isFinite(parsedLng) && Number.isFinite(parsedZoom)) {
|
||||
mapCenter = [parsedLng, parsedLat];
|
||||
mapZoom = parsedZoom;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
||||
const mql = window.matchMedia('(hover: none), (pointer: coarse)');
|
||||
const update = () => {
|
||||
@@ -359,11 +398,17 @@
|
||||
update();
|
||||
if (typeof mql.addEventListener === 'function') {
|
||||
mql.addEventListener('change', update);
|
||||
return () => mql.removeEventListener('change', update);
|
||||
return () => {
|
||||
mql.removeEventListener('change', update);
|
||||
if (updateUrlTimeout) clearTimeout(updateUrlTimeout);
|
||||
};
|
||||
}
|
||||
// Safari < 14
|
||||
(mql as any).addListener?.(update);
|
||||
return () => (mql as any).removeListener?.(update);
|
||||
return () => {
|
||||
(mql as any).removeListener?.(update);
|
||||
if (updateUrlTimeout) clearTimeout(updateUrlTimeout);
|
||||
};
|
||||
});
|
||||
|
||||
// FullMap handles cluster theme styling + cluster expansion on click.
|
||||
@@ -465,7 +510,10 @@
|
||||
{getMarkerProps}
|
||||
mapClass="w-full h-full min-h-[70vh] rounded-lg"
|
||||
standardControls
|
||||
zoom={mapZoom}
|
||||
center={mapCenter}
|
||||
on:mapClick={addMarker}
|
||||
on:mapMove={handleMapMove}
|
||||
>
|
||||
<svelte:fragment
|
||||
slot="marker"
|
||||
|
||||
Reference in New Issue
Block a user