diff --git a/frontend/src/lib/components/ElevationProfile.svelte b/frontend/src/lib/components/ElevationProfile.svelte index 693903d..b2dba20 100644 --- a/frontend/src/lib/components/ElevationProfile.svelte +++ b/frontend/src/lib/components/ElevationProfile.svelte @@ -10,11 +10,18 @@ let { elevation, onhover, + onselect, highlightDistance = null, height = '120px', }: { elevation: { d: number; ele: number }[] onhover?: (distance: number | null) => void + /** + * Klick auf eine Stelle des Profils. Damit lässt sich ein Marker auch + * über die Höhe finden — im steilen Stück, statt die Linie auf der + * Karte abzusuchen. + */ + onselect?: (distance: number) => void highlightDistance?: number | null height?: string } = $props() @@ -82,32 +89,82 @@ } }) - function handleMove(e: MouseEvent) { - if (!elevation.length) return + /** Distanz an der Mausposition; null, wenn es keine Punkte gibt. */ + function distanceAt(e: MouseEvent): number | null { + if (!elevation.length) return null - const rect = (e.currentTarget as SVGElement).getBoundingClientRect() + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() const ratio = (e.clientX - rect.left) / rect.width const idx = Math.round(ratio * (elevation.length - 1)) const clamped = Math.max(0, Math.min(elevation.length - 1, idx)) - onhover?.(elevation[clamped].d) + return elevation[clamped].d + } + + function handleMove(e: MouseEvent) { + const d = distanceAt(e) + if (d !== null) onhover?.(d) + } + + function handleClick(e: MouseEvent) { + if (!onselect) return + + const d = distanceAt(e) + if (d !== null) onselect(d) } function handleLeave() { onhover?.(null) } + + /** + * Tastaturweg zum Setzen: Mit den Pfeiltasten die Stelle wählen, mit + * Enter oder Leertaste bestätigen. Ohne das wäre das Profil ein reines + * Mausziel. + */ + function handleKey(e: KeyboardEvent) { + if (!onselect || !stats) return + + const total = stats.totalD + const step = total / 50 + const current = highlightDistance ?? 0 + + if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { + e.preventDefault() + const next = e.key === 'ArrowRight' ? current + step : current - step + onhover?.(Math.max(0, Math.min(total, next))) + } else if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + if (highlightDistance != null) onselect(highlightDistance) + } + } {#if stats}
{@html safeDescription}
- {/if} -- Es gibt noch keine Flag-Typen. - Jetzt anlegen -
- {/if} - - {#if version.elevation?.length} -Für diesen Trail gibt es noch keine GPX-Datei.
- {#if canEdit} -+ {statusInfo.label} + gemeldet am {formatDate(trail.status_changed)} + {#if trail.status_by} + von {teams.userLabel(trail.status_by)} + {/if} +
+ {/if} + + {#if safeDescription} +{@html safeDescription}
+ {/if}- Noch keine Marker. Klicke dafür auf der Karte auf die Trail-Linie. -
- {:else} -{flag?.label ?? 'Unbekannt'}
- {#if m.note} -{m.note}
- {/if} -{formatDate(m.created)}
-Für diesen Trail gibt es noch keine GPX-Datei.
+ {#if canEdit} +{commentError}
- {/if} ++ Auf die Trail-Linie oder ins Höhenprofil klicken, um dort einen + Marker zu setzen. +
+ {:else} ++ Es gibt noch keine Flag-Typen. + Jetzt anlegen +
+ {/if} - {#if comments.length === 0} -Noch keine Kommentare.
- {:else} -{c.text}
-{formatDate(c.created)}
-Noch keine Version hochgeladen.
- {:else} -{formatDate(v.created)}
- {#if v.id === trail.current} -Noch keine Meldung.
+ {:else} +{flag?.label ?? 'Unbekannt'}
+ {#if m.note} +{m.note}
+ {/if} ++ {formatDate(m.created)} · {markers.authorName(m)} +
+- {(v.distance_m / 1000).toFixed(1)} km · {v.ascent_m} hm - {#if v.note}· {v.note}{/if} -
-{commentError}
+ {/if} - {#if canEdit && v.id !== trail.current} -Noch keine Kommentare.
+ {:else} +{c.text}
+{formatDate(c.created)}
+Noch keine Version hochgeladen.
+ {:else} +{formatDate(v.created)}
+ {#if v.id === trail.current} ++ {(v.distance_m / 1000).toFixed(1)} km · {v.ascent_m} hm + {#if v.note}· {v.note}{/if} +
+