diff --git a/messages/de.json b/messages/de.json index 2396022..0001373 100644 --- a/messages/de.json +++ b/messages/de.json @@ -130,5 +130,11 @@ "choose-lang": "Wählen Sie Ihre Sprache", "first-vehicle": "Erstellen wir Ihr erstes Fahrzeug." } - } + }, + "location-selector": { + "current": "Mein Standort", + "searching": "Suchen...", + "no-results": "Keine Orte gefunden." + }, + "unsave": "Löschen" } diff --git a/messages/en.json b/messages/en.json index b10c09c..b44fbed 100644 --- a/messages/en.json +++ b/messages/en.json @@ -132,5 +132,10 @@ "first-vehicle": "Let's create your first vehicle." } }, - "unsave": "Unsave" + "unsave": "Unsave", + "location-selector": { + "current": "Current Location", + "searching": "Searching...", + "no-results": "No locations found." + } } diff --git a/src/lib/components/lnv/LocationSelect.svelte b/src/lib/components/lnv/LocationSelect.svelte index d0e99e5..6bfb12e 100644 --- a/src/lib/components/lnv/LocationSelect.svelte +++ b/src/lib/components/lnv/LocationSelect.svelte @@ -6,35 +6,56 @@ import * as Popover from "$lib/components/ui/popover/index.js"; import { Button } from "$lib/components/ui/button/index.js"; import { cn } from "$lib/utils.js"; + import { m } from "$lang/messages"; + import { BriefcaseIcon, HomeIcon, LocateIcon, MapPinIcon, SchoolIcon } from "@lucide/svelte"; + import { geocode } from "$lib/saved.svelte"; + import { reverseGeocode, search, type Feature } from "$lib/services/Search"; - const frameworks = [ + const locations = [ { - value: "sveltekit", - label: "SvelteKit", + value: "current", + label: m["location-selector.current"](), + icon: LocateIcon }, { - value: "next.js", - label: "Next.js", + value: "home", + label: m["saved.home"](), + subtext: geocode("home"), + icon: HomeIcon }, { - value: "nuxt.js", - label: "Nuxt.js", + value: "school", + label: m["saved.school"](), + subtext: geocode("school"), + icon: SchoolIcon }, { - value: "remix", - label: "Remix", - }, - { - value: "astro", - label: "Astro", - }, + value: "work", + label: m["saved.work"](), + subtext: geocode("work"), + icon: BriefcaseIcon + } ]; let open = $state(false); - let value = $state(""); + let { value = $bindable() } = $props(); let triggerRef = $state(null!); + let searchbarText = $state(""); + let searchText = $derived.by(debounce(() => searchbarText, 300)); + let searching = $state(false); + let searchResults: Feature[] = $state([]); - const selectedValue = $derived(value === "location" ? "My Location" : value); + async function getCoordLabel(value: `${number},${number}`) { + const splitter = value.split(","); + const res = await reverseGeocode({ lat: parseFloat(splitter[0]), lon: parseFloat(splitter[1]) }) + if(res.length == 0) return ""; + const feature = res[0]; + return feature.properties.name; + } + + const selectedValue = $derived( + new Promise(async r => { r(locations.find((f) => f.value === value)?.label || await getCoordLabel(value)) }) + ); // We want to refocus the trigger button when the user selects // an item from the list so users can continue navigating the @@ -45,61 +66,123 @@ triggerRef.focus(); }); } + + function debounce(getter: () => T, delay: number): () => T | undefined { + let value = $state(); + let timer: NodeJS.Timeout; + $effect(() => { + const newValue = getter(); // read here to subscribe to it + clearTimeout(timer); + timer = setTimeout(() => (value = newValue), delay); + return () => clearTimeout(timer); + }); + return () => value; + } + + $effect(() => { + if (!searchText) { + searchResults = []; + return; + } + if (searchText.length > 0) { + searching = true; + search(searchText, 0, 0).then((results) => { + searchResults = results; + searching = false; + }); + } else { + searchResults = []; + } + }); - {#snippet child({ props }: { props: Record })} + {#snippet child({ props })} {/snippet} - - + + - No location found. + + {#if searching} + {m["location-selector.searching"]()} + {:else} + {m["location-selector.no-results"]()} + {/if} + - { - value = "location"; - closeAndFocusTrigger(); - }} - > - - My Location - - - - {#each frameworks as framework (framework.value)} + {#if searchbarText == ""} + {#each locations as location} + { + value = location.value; + closeAndFocusTrigger(); + }} + style="flex-direction: column; align-items: start;" + > +
+ + {location.label} + +
+ {#await location.subtext then subtext} + {#if subtext} + {subtext} + {/if} + {/await} +
+ {/each} + {/if} + + {#each searchResults as result} + {@const resultValue = result.geometry.coordinates[1] + "," + result.geometry.coordinates[0]} { - value = framework.value; + value = resultValue; closeAndFocusTrigger(); }} + style="flex-direction: column; align-items: start;" > - - {framework.label} +
+ + {result.properties.name} + +
+ {result.properties.street}{result.properties.housenumber ? " " + result.properties.housenumber : ""}, {result.properties.city}
{/each}
diff --git a/src/lib/components/lnv/sidebar/RouteSidebar.svelte b/src/lib/components/lnv/sidebar/RouteSidebar.svelte index 739f0d3..3d323f4 100644 --- a/src/lib/components/lnv/sidebar/RouteSidebar.svelte +++ b/src/lib/components/lnv/sidebar/RouteSidebar.svelte @@ -20,6 +20,7 @@ import { location } from "../location.svelte"; import { saved } from "$lib/saved.svelte"; import { m } from "$lang/messages"; + import LocationSelect from "../LocationSelect.svelte"; let { from, @@ -29,7 +30,7 @@ to?: string; } = $props(); - let fromLocation = $state(from || ""); + let fromLocation = $state(from || "current"); let toLocation = $state(to || ""); let routes: Trip[] | null = $state(null); @@ -57,44 +58,37 @@
- +
- +
- - - {@html m["sidebar.route.help"]()} -