From 7bede8cd648c037b8ac6ff8834e3c3a528a14e8e Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Tue, 19 May 2026 21:22:34 +0200 Subject: [PATCH] feat(route-builder): SAC-red trail + refit map on image drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trail polyline now uses the same SAC white-red-white red as the /hikes overview and detail pages so the live preview reads as the final published track. Dropping geolocated images now reframes the edit map to the new bounds via a shared `mapView.fitTick` signal — covers GPX imports too if they ever wire it up. --- package.json | 2 +- .../hikes/route-builder/EditMap.svelte | 38 +++++++++++++++++-- .../hikes/route-builder/ImageDropzone.svelte | 11 +++++- .../route-builder/builderStore.svelte.ts | 11 ++++++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 889e165c..dae8cb4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.77.1", + "version": "1.77.2", "private": true, "type": "module", "scripts": { diff --git a/src/lib/components/hikes/route-builder/EditMap.svelte b/src/lib/components/hikes/route-builder/EditMap.svelte index ca5ce678..9cfb3540 100644 --- a/src/lib/components/hikes/route-builder/EditMap.svelte +++ b/src/lib/components/hikes/route-builder/EditMap.svelte @@ -2,9 +2,11 @@ import type { Attachment } from 'svelte/attachments'; import { builder, + mapView, nextWaypointId, scheduleSave } from './builderStore.svelte'; + import { SAC_TRAIL_COLOR } from '$lib/data/sacColors'; // Single-point Swisstopo elevation lookups are intentionally NOT used — // they returned 0 against WGS-84 inputs in practice, and image waypoints // don't need per-point altitudes anyway. Waypoint altitudes flow from @@ -135,14 +137,14 @@ // Lines: per-pair so each can carry a segIdx for inline insertion. // Snapped + linear segments share the same visual styling — there's // no need to call out the difference, the user picked the mode. - const primary = - getComputedStyle(document.documentElement).getPropertyValue('--color-primary').trim() || - '#5e81ac'; + // SAC white-red-white red — matches /hikes overview + detail-page + // trail colour so the live preview reads as the final published track. + const trackColor = SAC_TRAIL_COLOR.T2; if (builder.routedSegments.length > 0) { builder.routedSegments.forEach((seg, segIdx) => { const latLngs = seg.map((p) => [p[1], p[0]] as [number, number]); const poly = L.polyline(latLngs, { - color: primary, + color: trackColor, weight: 4, opacity: 0.9 }).addTo(lineLayer); @@ -155,6 +157,23 @@ } } + function fitToTrack() { + const points: [number, number][] = []; + for (const w of builder.waypoints) { + if (w.unplaced) continue; + points.push([w.lat, w.lng]); + } + for (const seg of builder.routedSegments) { + for (const p of seg) points.push([p[1], p[0]]); + } + if (points.length === 0) return; + if (points.length === 1) { + map.setView(points[0], 13); + return; + } + map.fitBounds(L.latLngBounds(points), { padding: [40, 40] }); + } + // React to store changes. const stopRoot = $effect.root(() => { $effect(() => { @@ -166,6 +185,17 @@ builder.routedSegments.length; render(); }); + + // External fit-bounds requests (image drops, GPX imports). + // The map's own init-time auto-fit covers first-load; this + // effect handles every subsequent batch insertion. + let lastTick = mapView.fitTick; + $effect(() => { + const tick = mapView.fitTick; + if (tick === lastTick) return; + lastTick = tick; + fitToTrack(); + }); }); // Click on blank map. In normal mode, append a new waypoint at the end. diff --git a/src/lib/components/hikes/route-builder/ImageDropzone.svelte b/src/lib/components/hikes/route-builder/ImageDropzone.svelte index 48a25feb..38774fe5 100644 --- a/src/lib/components/hikes/route-builder/ImageDropzone.svelte +++ b/src/lib/components/hikes/route-builder/ImageDropzone.svelte @@ -3,6 +3,7 @@ builder, insertWaypointChronologically, nextWaypointId, + requestFitBounds, scheduleSave, type Waypoint } from './builderStore.svelte'; @@ -135,15 +136,23 @@ // by snap-to-route enrichment) is the only reliable elevation // source against WGS-84 inputs, and its single-point variant kept // returning 0 even with workaround attempts. + let placedAny = false; for (const p of prepared) { if (!p.ok) continue; - if (p.kind === 'new') insertWaypointChronologically(p.wp); + if (p.kind === 'new') { + insertWaypointChronologically(p.wp); + if (p.hasGps) placedAny = true; + } // Cache the original file so the waypoint table can show a // full-resolution preview this session (for both new + matched // waypoints). Persistence to localStorage keeps only the small // thumbnail. setFullImage(p.id, p.file); } + // Reframe the map to the new track. Only matters when the batch + // added at least one geolocated waypoint — unplaced images don't + // affect bounds, and matched-only drops leave coords unchanged. + if (placedAny) requestFitBounds(); } function onDrop(e: DragEvent) { diff --git a/src/lib/components/hikes/route-builder/builderStore.svelte.ts b/src/lib/components/hikes/route-builder/builderStore.svelte.ts index 17cc6c23..ccf83f22 100644 --- a/src/lib/components/hikes/route-builder/builderStore.svelte.ts +++ b/src/lib/components/hikes/route-builder/builderStore.svelte.ts @@ -107,6 +107,17 @@ function defaultState(): BuilderState { export const builder = $state(loadDraft()); +/** + * UI-only signal for the edit map: bumping `fitTick` asks the map to + * re-run `fitBounds()` on the current track. Used after batch insertions + * (image drops, GPX import) where the user expects the map to reframe to + * show every newly-added waypoint. Not persisted. + */ +export const mapView = $state({ fitTick: 0 }); +export function requestFitBounds(): void { + mapView.fitTick++; +} + let saveTimer: ReturnType | null = null; export function scheduleSave(): void { if (!browser) return;