feat(route-builder): SAC-red trail + refit map on image drop
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.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.77.1",
|
||||
"version": "1.77.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -107,6 +107,17 @@ function defaultState(): BuilderState {
|
||||
|
||||
export const builder = $state<BuilderState>(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<typeof setTimeout> | null = null;
|
||||
export function scheduleSave(): void {
|
||||
if (!browser) return;
|
||||
|
||||
Reference in New Issue
Block a user