diff --git a/apps/mana/apps/web/src/lib/modules/places/ListView.svelte b/apps/mana/apps/web/src/lib/modules/places/ListView.svelte index 1cc3f6bd9..1d6223d9e 100644 --- a/apps/mana/apps/web/src/lib/modules/places/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/places/ListView.svelte @@ -7,7 +7,13 @@ import { useAllPlaces } from './queries'; import { placesStore } from './stores/places.svelte'; import { trackingStore } from './stores/tracking.svelte'; - import { searchAddress, formatAddress, type GeocodingResult } from '$lib/geocoding'; + import { + searchAddress, + reverseGeocode, + formatAddress, + formatLocality, + type GeocodingResult, + } from '$lib/geocoding'; import { Star, MapPin, Plus, PencilSimple, Trash, MagnifyingGlass } from '@mana/shared-icons'; import type { ViewProps } from '$lib/app-registry'; import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui'; @@ -55,6 +61,40 @@ other: 'Sonstiges', }; + // --- Reverse geocode of current tracking position --- + // When tracking is active we have fresh coordinates every few seconds, but + // the GeolocationPosition object is replaced on every update. We debounce + // by ~1.5 s and round to ~10 m precision so we only hit the geocoding + // service when the user has actually moved, not on every micro-jitter. + let currentLocationLabel = $state(null); + let lastReverseKey = ''; + let reverseDebounce: ReturnType | undefined; + + $effect(() => { + const pos = trackingStore.currentPosition; + if (!pos) { + currentLocationLabel = null; + lastReverseKey = ''; + return; + } + + // Round to ~10 m precision (4 decimal places) so we don't re-fetch + // on every tiny coordinate drift while standing still. + const lat = pos.coords.latitude.toFixed(4); + const lon = pos.coords.longitude.toFixed(4); + const key = `${lat},${lon}`; + if (key === lastReverseKey) return; + lastReverseKey = key; + + clearTimeout(reverseDebounce); + reverseDebounce = setTimeout(async () => { + const result = await reverseGeocode(pos.coords.latitude, pos.coords.longitude); + if (result) { + currentLocationLabel = formatLocality(result); + } + }, 1500); + }); + // --- Address autocomplete --- let addressQuery = $state(''); let suggestions = $state([]); @@ -191,12 +231,20 @@ {trackingStore.isTracking ? 'Tracking aktiv' : 'Tracking starten'} {#if trackingStore.currentPosition} - - {formatCoords( - trackingStore.currentPosition.coords.latitude, - trackingStore.currentPosition.coords.longitude - )} - +
+ {#if currentLocationLabel} + + + {currentLocationLabel} + + {/if} + + {formatCoords( + trackingStore.currentPosition.coords.latitude, + trackingStore.currentPosition.coords.longitude + )} + +
{/if} {#if trackingStore.error} @@ -400,6 +448,27 @@ animation: dot-pulse 2s ease-in-out infinite; } + .tracking-location { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.0625rem; + min-width: 0; + } + + .tracking-label { + display: inline-flex; + align-items: center; + gap: 0.1875rem; + font-size: 0.75rem; + color: #0ea5e9; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 220px; + } + .tracking-coords { font-size: 0.6875rem; color: hsl(var(--color-muted-foreground));