feat(stores): location stores
Some checks failed
TrafficCue CI / check (push) Failing after 1m29s
TrafficCue CI / build-android (push) Has been cancelled
TrafficCue CI / build (push) Has been cancelled

This commit is contained in:
2025-10-03 15:17:35 +02:00
parent 46bf44a324
commit 6e11b438a2
7 changed files with 144 additions and 163 deletions

View File

@ -8,7 +8,6 @@
routing, routing,
} from "$lib/services/navigation/routing.svelte"; } from "$lib/services/navigation/routing.svelte";
import { location } from "./location.svelte"; import { location } from "./location.svelte";
import { saved } from "$lib/saved.svelte";
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte"; import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
import { import {
getPMTilesURL, getPMTilesURL,
@ -20,6 +19,7 @@
import HazardMarker from "./HazardMarker.svelte"; import HazardMarker from "./HazardMarker.svelte";
import { hazards } from "./hazards.svelte"; import { hazards } from "./hazards.svelte";
import RequiresCapability from "./RequiresCapability.svelte"; import RequiresCapability from "./RequiresCapability.svelte";
import MapLocationMarkers from "./MapLocationMarkers.svelte";
onMount(() => { onMount(() => {
window.addEventListener("resize", map.updateMapPadding); window.addEventListener("resize", map.updateMapPadding);
@ -28,9 +28,6 @@
let locationDot: HTMLDivElement | undefined = $state(); let locationDot: HTMLDivElement | undefined = $state();
let locationAccuracyCircle: HTMLDivElement | undefined = $state(); let locationAccuracyCircle: HTMLDivElement | undefined = $state();
let homeMarker: HTMLImageElement | undefined = $state();
let workMarker: HTMLImageElement | undefined = $state();
let schoolMarker: HTMLImageElement | undefined = $state();
const DEBUG_POINTS = false; // Set to true to show debug points on the map const DEBUG_POINTS = false; // Set to true to show debug points on the map
</script> </script>
@ -137,51 +134,7 @@
/> />
{/if} {/if}
{#if saved.home} <MapLocationMarkers />
<img
src={map.zoom > 9 ? "/img/saved/home.png" : "/img/saved/small.png"}
alt="Home Marker"
bind:this={homeMarker}
style="width: 32px;"
/>
<Marker
lnglat={{
lat: saved.home.lat,
lng: saved.home.lon,
}}
element={homeMarker}
/>
{/if}
{#if saved.school}
<img
src={map.zoom > 9 ? "/img/saved/school.png" : "/img/saved/small.png"}
alt="School Marker"
bind:this={schoolMarker}
style="width: 32px;"
/>
<Marker
lnglat={{
lat: saved.school.lat,
lng: saved.school.lon,
}}
element={schoolMarker}
/>
{/if}
{#if saved.work}
<img
src={map.zoom > 9 ? "/img/saved/work.png" : "/img/saved/small.png"}
alt="Work Marker"
bind:this={workMarker}
style="width: 32px;"
/>
<Marker
lnglat={{
lat: saved.work.lat,
lng: saved.work.lon,
}}
element={workMarker}
/>
{/if}
<RequiresCapability capability="hazards"> <RequiresCapability capability="hazards">
{#each hazards as hazard (hazard.latitude + "-" + hazard.longitude)} {#each hazards as hazard (hazard.latitude + "-" + hazard.longitude)}

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { MAP_ICONS, type Location } from "$lib/saved.svelte";
import { Marker } from "svelte-maplibre-gl";
import { map } from "./map.svelte";
let { store }: { store: Location } = $props();
let marker: HTMLImageElement | undefined = $state();
</script>
<img
src={map.zoom > 9 ? `/img/saved/${MAP_ICONS[store.icon ?? "small.png"]}` : "/img/saved/small.png"}
alt="Work Marker"
bind:this={marker}
style="width: 32px;"
/>
<Marker
lnglat={{
lat: store.lat,
lng: store.lng,
}}
element={marker}
/>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import type { Location } from "$lib/saved.svelte";
import { stores } from "$lib/services/stores.svelte";
import MapLocationMarker from "./MapLocationMarker.svelte";
const locations = stores<Location>("location");
</script>
{#each locations.current as location (location.data.lat + "-" + location.data.lng)}
<MapLocationMarker store={location.data} />
{/each}

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { m } from "$lang/messages";
import Button from "$lib/components/ui/button/button.svelte";
import { circInOut } from "svelte/easing";
import { fly } from "svelte/transition";
import { map, pin } from "../map.svelte";
import { MapPinIcon } from "@lucide/svelte";
import { stores } from "$lib/services/stores.svelte";
import { ICONS, type Location } from "$lib/saved.svelte";
const locations = stores<Location>("location");
function getName(name: string) {
if(name == "home") {
return m["saved.home"]();
} else if(name == "work") {
return m["saved.work"]();
} else if(name == "school") {
return m["saved.school"]();
}
return name;
}
</script>
<div
id="saved"
class="mt-2 mb-2"
in:fly={{ y: 20, duration: 200, easing: circInOut }}
>
{#each locations.current as location (location.data.lat + "-" + location.data.lng)}
<Button
variant="secondary"
class="flex-1"
onclick={() => {
const { lat, lng } = location.data;
pin.dropPin(lat, lng);
pin.showInfo();
map.value?.flyTo({
center: [lng, lat],
zoom: 19,
});
}}>
{@const Icon = ICONS[location.data.icon ?? "pin"] ?? MapPinIcon}
<Icon />
{getName(location.data.name)}
</Button>
{/each}
</div>
<style>
#saved {
display: flex;
gap: 0.5rem;
/* justify-content: space-evenly; */
width: 100%;
max-width: 100%;
}
</style>

View File

@ -22,12 +22,12 @@
import Reviews from "../info/Reviews.svelte"; import Reviews from "../info/Reviews.svelte";
import MapAi from "../info/MapAI.svelte"; import MapAi from "../info/MapAI.svelte";
import RequiresCapability from "../RequiresCapability.svelte"; import RequiresCapability from "../RequiresCapability.svelte";
import { saved, saveLocations } from "$lib/saved.svelte";
import { getDeveloperToggle } from "./settings/developer.svelte"; import { getDeveloperToggle } from "./settings/developer.svelte";
import InternetAccess from "../info/InternetAccess.svelte"; import InternetAccess from "../info/InternetAccess.svelte";
import RestaurantInfo from "../info/RestaurantInfo.svelte"; import RestaurantInfo from "../info/RestaurantInfo.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { updateStore } from "$lib/services/stores.svelte";
// let { feature }: { feature: Feature } = $props(); // let { feature }: { feature: Feature } = $props();
@ -185,12 +185,12 @@
<Button <Button
variant="outline" variant="outline"
onclick={() => { onclick={() => {
// localStorage.setItem( updateStore({ name: "home", type: "location" }, {
// "saved.home", lat,
// JSON.stringify({ lat, lon: lng }), lng,
// ); name: "home",
saved.home = { lat, lon: lng }; icon: "home"
saveLocations(); })
}} }}
> >
<HomeIcon /> <HomeIcon />
@ -199,8 +199,12 @@
<Button <Button
variant="outline" variant="outline"
onclick={() => { onclick={() => {
saved.school = { lat, lon: lng }; updateStore({ name: "school", type: "location" }, {
saveLocations(); lat,
lng,
name: "school",
icon: "school"
})
}} }}
> >
<SchoolIcon /> <SchoolIcon />
@ -209,12 +213,12 @@
<Button <Button
variant="outline" variant="outline"
onclick={() => { onclick={() => {
// localStorage.setItem( updateStore({ name: "work", type: "location" }, {
// "saved.work", lat,
// JSON.stringify({ lat, lon: lng }), lng,
// ); name: "work",
saved.work = { lat, lon: lng }; icon: "work"
saveLocations(); })
}} }}
> >
<BriefcaseIcon /> <BriefcaseIcon />

View File

@ -1,108 +1,21 @@
<script lang="ts"> <script lang="ts">
import { import {
BriefcaseIcon,
DownloadIcon, DownloadIcon,
FuelIcon, FuelIcon,
HomeIcon,
ParkingSquareIcon, ParkingSquareIcon,
SchoolIcon,
} from "@lucide/svelte"; } from "@lucide/svelte";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import { fly } from "svelte/transition";
import { circInOut } from "svelte/easing";
import { map, pin } from "../map.svelte";
import VehicleSelector from "../VehicleSelector.svelte"; import VehicleSelector from "../VehicleSelector.svelte";
import Post from "../Post.svelte"; import Post from "../Post.svelte";
import RequiresCapability from "../RequiresCapability.svelte"; import RequiresCapability from "../RequiresCapability.svelte";
import { saved } from "$lib/saved.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { view } from "../view.svelte"; import { view } from "../view.svelte";
import * as Card from "$lib/components/ui/card"; import * as Card from "$lib/components/ui/card";
import SavedRoutes from "../main/SavedRoutes.svelte"; import SavedRoutes from "../main/SavedRoutes.svelte";
import SavedLocations from "../main/SavedLocations.svelte";
</script> </script>
<div <SavedLocations />
id="saved"
class="mt-2 mb-2"
in:fly={{ y: 20, duration: 200, easing: circInOut }}
>
<Button
variant="secondary"
class="flex-1"
onclick={() => {
const loc = saved.home;
if (!loc) {
alert(m["saved.no-location"]({ name: m["saved.home"]() }));
return;
}
const { lat, lon } = loc;
if (!lat || !lon) {
alert(m["saved.no-location"]({ name: m["saved.home"]() }));
return;
}
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19,
});
}}
>
<HomeIcon />
{m["saved.home"]()}
</Button>
<Button
variant="secondary"
class="flex-1"
onclick={() => {
console.log(saved);
const loc = saved.school;
if (!loc) {
alert(m["saved.no-location"]({ name: m["saved.school"]() }));
return;
}
const { lat, lon } = loc;
if (!lat || !lon) {
alert(m["saved.no-location"]({ name: m["saved.school"]() }));
return;
}
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19,
});
}}
>
<SchoolIcon />
{m["saved.school"]()}
</Button>
<Button
variant="secondary"
class="flex-1"
onclick={() => {
const loc = saved.work;
if (!loc) {
alert(m["saved.no-location"]({ name: m["saved.work"]() }));
return;
}
const { lat, lon } = loc;
if (!lat || !lon) {
alert(m["saved.no-location"]({ name: m["saved.work"]() }));
return;
}
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19,
});
}}
>
<BriefcaseIcon />
{m["saved.work"]()}
</Button>
</div>
<VehicleSelector /> <VehicleSelector />
@ -170,13 +83,3 @@
<Post /> <Post />
</div> </div>
</RequiresCapability> </RequiresCapability>
<style>
#saved {
display: flex;
gap: 0.5rem;
/* justify-content: space-evenly; */
width: 100%;
max-width: 100%;
}
</style>

View File

@ -1,13 +1,24 @@
import type { Component } from "svelte";
import { reverseGeocode } from "./services/Search"; import { reverseGeocode } from "./services/Search";
import { BriefcaseIcon, HomeIcon, MapPinIcon, SchoolIcon, type IconProps } from "@lucide/svelte";
/**
* @deprecated Use stores instead.
*/
export const saved: Record<string, WorldLocation> = $state( export const saved: Record<string, WorldLocation> = $state(
JSON.parse(localStorage.getItem("saved") ?? "{}"), JSON.parse(localStorage.getItem("saved") ?? "{}"),
); );
/**
* @deprecated Use stores instead.
*/
export function saveLocations() { export function saveLocations() {
localStorage.setItem("saved", JSON.stringify(saved)); localStorage.setItem("saved", JSON.stringify(saved));
} }
/**
* @deprecated Use stores instead.
*/
export async function geocode(name: string) { export async function geocode(name: string) {
const loc = saved[name]; const loc = saved[name];
if (!loc) return; if (!loc) return;
@ -18,3 +29,23 @@ export async function geocode(name: string) {
const feature = geocode[0]; const feature = geocode[0];
return `${feature.properties.street}${feature.properties.housenumber ? " " + feature.properties.housenumber : ""}, ${feature.properties.city}`; return `${feature.properties.street}${feature.properties.housenumber ? " " + feature.properties.housenumber : ""}, ${feature.properties.city}`;
} }
export const ICONS: Record<string, Component<IconProps>> = {
home: HomeIcon,
work: BriefcaseIcon,
school: SchoolIcon,
pin: MapPinIcon
};
export const MAP_ICONS: Record<string, string> = {
home: "home.png",
school: "school.png",
work: "work.png"
}; // TODO: add generic pin icon
export interface Location {
lat: number;
lng: number;
name: string;
icon?: string;
}