fix: Karte lazy-loaded, formatKm bei 0, Enter im Dialog
- TrailMap: IntersectionObserver für WebGL-Kontexte (max 8-16 gleichzeitig) → Karte erst laden, wenn sichtbar, rootMargin 200px für frühes Laden - Trail-Liste: formatKm zeigt 0 m als "0,0 km" statt "–" - Trail-Anlegen: Enter im Namensfeld triggert save(), saving-Guard hinzugefügt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
60d5891bc9
commit
7bc128601a
2 changed files with 67 additions and 37 deletions
|
|
@ -62,6 +62,9 @@
|
||||||
let highlightMarker: Marker | null = null
|
let highlightMarker: Marker | null = null
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
let cleanupMap: (() => void) | null = null
|
||||||
|
|
||||||
|
function build() {
|
||||||
const instance = new maplibregl.Map({
|
const instance = new maplibregl.Map({
|
||||||
container,
|
container,
|
||||||
style: {
|
style: {
|
||||||
|
|
@ -92,7 +95,7 @@
|
||||||
if (placing) onplace?.({ lat: e.lngLat.lat, lng: e.lngLat.lng })
|
if (placing) onplace?.({ lat: e.lngLat.lat, lng: e.lngLat.lng })
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
cleanupMap = () => {
|
||||||
// Map.remove() räumt Controls auf, aber keine Marker — die tragen
|
// Map.remove() räumt Controls auf, aber keine Marker — die tragen
|
||||||
// eigene move-Listener auf der Karte und werden hier zuerst entfernt.
|
// eigene move-Listener auf der Karte und werden hier zuerst entfernt.
|
||||||
for (const m of markerObjects) m.remove()
|
for (const m of markerObjects) m.remove()
|
||||||
|
|
@ -102,6 +105,26 @@
|
||||||
|
|
||||||
instance.remove()
|
instance.remove()
|
||||||
map = null
|
map = null
|
||||||
|
ready = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browser begrenzen WebGL-Kontexte auf rund 8 bis 16. In einer Liste
|
||||||
|
// mit vielen Trails wären das zu viele — deshalb entsteht die Karte
|
||||||
|
// erst, wenn ihre Kachel in den Sichtbereich scrollt.
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting && !map) build()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin: '200px' },
|
||||||
|
)
|
||||||
|
observer.observe(container)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
cleanupMap?.()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatKm(m: number | undefined) {
|
function formatKm(m: number | undefined) {
|
||||||
if (!m) return '–'
|
// Nur fehlende Werte zeigen "–"; 0 ist eine gültige Länge.
|
||||||
|
if (m == null) return '–'
|
||||||
return `${(m / 1000).toFixed(1)} km`
|
return `${(m / 1000).toFixed(1)} km`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (saving) return
|
||||||
if (!form.name.trim()) {
|
if (!form.name.trim()) {
|
||||||
error = 'Bitte einen Namen angeben.'
|
error = 'Bitte einen Namen angeben.'
|
||||||
return
|
return
|
||||||
|
|
@ -169,7 +171,12 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="trail-name">Name</Label>
|
<Label for="trail-name">Name</Label>
|
||||||
<Input id="trail-name" bind:value={form.name} placeholder="z. B. Steinberg-Abfahrt" />
|
<Input
|
||||||
|
id="trail-name"
|
||||||
|
bind:value={form.name}
|
||||||
|
placeholder="z. B. Steinberg-Abfahrt"
|
||||||
|
onkeydown={(e) => e.key === 'Enter' && save()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="trail-desc">Beschreibung</Label>
|
<Label for="trail-desc">Beschreibung</Label>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue