feat: initial support for saving routes
Some checks failed
TrafficCue CI / check (push) Failing after 32s
TrafficCue CI / build (push) Failing after 32s

This commit is contained in:
Cfp
2025-08-19 12:19:08 +02:00
parent babfb526de
commit 64ed0c8204
4 changed files with 86 additions and 11 deletions

View File

@ -11,7 +11,8 @@
"home": "Home", "home": "Home",
"school": "School", "school": "School",
"work": "Work", "work": "Work",
"no-location": "No {name} location saved." "no-location": "No {name} location saved.",
"saved-routes": "Saved Routes"
}, },
"location": { "location": {
"unlock": "Unlock Location", "unlock": "Unlock Location",
@ -130,5 +131,6 @@
"choose-lang": "Choose your language", "choose-lang": "Choose your language",
"first-vehicle": "Let's create your first vehicle." "first-vehicle": "Let's create your first vehicle."
} }
} },
"unsave": "Unsave"
} }

View File

@ -9,6 +9,8 @@
import RequiresCapability from "../RequiresCapability.svelte"; import RequiresCapability from "../RequiresCapability.svelte";
import { saved } from "$lib/saved.svelte"; import { saved } from "$lib/saved.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { getSaved } from "$lib/services/lnv";
import { view } from "../view.svelte";
</script> </script>
<div <div
@ -96,6 +98,22 @@
<VehicleSelector /> <VehicleSelector />
{#await getSaved() then saved}
{#if saved.length != 0}
<div>
<h2 style="margin: 5px; margin-left: 0; font-size: 1.2em;">Saved Routes</h2>
<div style="display: flex; flex-direction: column; gap: 10px;">
{#each saved as save}
<Button variant="secondary" onclick={() => {
view.switch("trip", { route: JSON.parse(save.data) })
}}>{save.name}</Button>
{/each}
</div>
</div>
{/if}
{/await}
<RequiresCapability capability="post"> <RequiresCapability capability="post">
<div> <div>
<h2>In your area</h2> <h2>In your area</h2>

View File

@ -10,6 +10,8 @@
import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte"; import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte";
import { map } from "../map.svelte"; import { map } from "../map.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { deleteSaved, isSaved, putSaved } from "$lib/services/lnv";
import { view } from "../view.svelte";
let { let {
route, route,
@ -43,10 +45,21 @@
<RouteIcon /> <RouteIcon />
{m["sidebar.trip.start"]()} {m["sidebar.trip.start"]()}
</Button> </Button>
<Button variant="secondary" disabled> {#await isSaved($state.snapshot(route)) then saved}
<SaveIcon /> <Button variant="secondary" onclick={async () => {
{m["sidebar.trip.save"]()} if(saved) {
</Button> await deleteSaved(saved);
view.back();
} else {
const name = prompt("Trip name?");
if(!name) return;
await putSaved(name, route);
}
}}>
<SaveIcon />
{saved ? m.unsave() : m["sidebar.trip.save"]()}
</Button>
{/await}
<Button variant="secondary" disabled> <Button variant="secondary" disabled>
<SendIcon /> <SendIcon />
{m["sidebar.trip.send"]()} {m["sidebar.trip.send"]()}

View File

@ -80,12 +80,23 @@ export async function refreshToken() {
localStorage.setItem("lnv-refresh", data.refresh_token); localStorage.setItem("lnv-refresh", data.refresh_token);
} }
export async function authFetch(url: string, params: RequestInit): ReturnType<typeof fetch> { export async function authFetch(url: string, params?: RequestInit): ReturnType<typeof fetch> {
let res = await fetch(url, params); let res = await fetch(url, {
if(res.status == 401) { headers: {
await refreshToken(); "Authorization": "Bearer " + localStorage.getItem("lnv-token")
},
...params
});
if(res.status != 401) {
return res;
} }
res = await fetch(url, params); await refreshToken();
res = await fetch(url, {
headers: {
"Authorization": "Bearer " + localStorage.getItem("lnv-token")
},
...params
});
if(res.status == 401) { if(res.status == 401) {
console.error("Server is misconfigured."); console.error("Server is misconfigured.");
} }
@ -160,3 +171,34 @@ export async function ai(query: string, location?: WorldLocation) {
} }
return await res.text(); return await res.text();
} }
export function getSaved(): Promise<{ name: string; data: string; }[]> {
return authFetch(LNV_SERVER + "/saved").then(res => res.json());
}
export function putSaved(name: string, data: object) {
return authFetch(LNV_SERVER + "/saved", {
method: "PUT",
body: JSON.stringify({
name,
data: JSON.stringify(data)
})
}).then(res => res.json());
}
export function deleteSaved(name: string) {
return authFetch(LNV_SERVER + "/saved", {
method: "DELETE",
body: JSON.stringify({
name
})
}).then(res => res.text());
}
export async function isSaved(data: Trip) {
const res = await getSaved();
console.log(res, data);
const filtered = res.filter(s => JSON.parse(s.data).legs[0].shape == data.legs[0].shape);
if(filtered.length == 0) return false;
return filtered[0].name;
}