style: run eslint and prettier
Some checks failed
TrafficCue CI / check (push) Failing after 39s
TrafficCue CI / build (push) Failing after 38s

This commit is contained in:
2025-08-30 13:13:31 +02:00
parent b5804bc9e0
commit 8af4a623b3
10 changed files with 121 additions and 80 deletions

View File

@ -7,7 +7,13 @@
import { Button } from "$lib/components/ui/button/index.js"; import { Button } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { BriefcaseIcon, HomeIcon, LocateIcon, MapPinIcon, SchoolIcon } from "@lucide/svelte"; import {
BriefcaseIcon,
HomeIcon,
LocateIcon,
MapPinIcon,
SchoolIcon,
} from "@lucide/svelte";
import { geocode } from "$lib/saved.svelte"; import { geocode } from "$lib/saved.svelte";
import { reverseGeocode, search, type Feature } from "$lib/services/Search"; import { reverseGeocode, search, type Feature } from "$lib/services/Search";
@ -15,26 +21,26 @@
{ {
value: "current", value: "current",
label: m["location-selector.current"](), label: m["location-selector.current"](),
icon: LocateIcon icon: LocateIcon,
}, },
{ {
value: "home", value: "home",
label: m["saved.home"](), label: m["saved.home"](),
subtext: geocode("home"), subtext: geocode("home"),
icon: HomeIcon icon: HomeIcon,
}, },
{ {
value: "school", value: "school",
label: m["saved.school"](), label: m["saved.school"](),
subtext: geocode("school"), subtext: geocode("school"),
icon: SchoolIcon icon: SchoolIcon,
}, },
{ {
value: "work", value: "work",
label: m["saved.work"](), label: m["saved.work"](),
subtext: geocode("work"), subtext: geocode("work"),
icon: BriefcaseIcon icon: BriefcaseIcon,
} },
]; ];
let open = $state(false); let open = $state(false);
@ -47,14 +53,22 @@
async function getCoordLabel(value: `${number},${number}`) { async function getCoordLabel(value: `${number},${number}`) {
const splitter = value.split(","); const splitter = value.split(",");
const res = await reverseGeocode({ lat: parseFloat(splitter[0]), lon: parseFloat(splitter[1]) }) const res = await reverseGeocode({
if(res.length == 0) return "<unknown>"; lat: parseFloat(splitter[0]),
lon: parseFloat(splitter[1]),
});
if (res.length == 0) return "<unknown>";
const feature = res[0]; const feature = res[0];
return feature.properties.name; return feature.properties.name;
} }
const selectedValue = $derived( const selectedValue = $derived(
new Promise(async r => { r(locations.find((f) => f.value === value)?.label || await getCoordLabel(value)) }) new Promise((r) => {
r(
locations.find((f) => f.value === value)?.label ||
getCoordLabel(value).then((v) => r(v)),
);
}),
); );
// We want to refocus the trigger button when the user selects // We want to refocus the trigger button when the user selects
@ -126,7 +140,7 @@
</Command.Empty> </Command.Empty>
<Command.Group> <Command.Group>
{#if searchbarText == ""} {#if searchbarText == ""}
{#each locations as location} {#each locations as location, _index (location.value)}
<Command.Item <Command.Item
value={location.value} value={location.value}
onSelect={() => { onSelect={() => {
@ -135,17 +149,15 @@
}} }}
style="flex-direction: column; align-items: start;" style="flex-direction: column; align-items: start;"
> >
<div style="display: flex; align-items: center; gap: 5px; width: 100%;"> <div
<location.icon style="display: flex; align-items: center; gap: 5px; width: 100%;"
class={cn( >
"mr-2 size-4" <location.icon class={cn("mr-2 size-4")} />
)}
/>
{location.label} {location.label}
<CheckIcon <CheckIcon
class={cn( class={cn(
"mr-2 size-4 ml-auto", "mr-2 size-4 ml-auto",
value !== location.value && "text-transparent" value !== location.value && "text-transparent",
)} )}
/> />
</div> </div>
@ -158,8 +170,11 @@
{/each} {/each}
{/if} {/if}
{#each searchResults as result} {#each searchResults as result, _index (result.properties.osm_id)}
{@const resultValue = result.geometry.coordinates[1] + "," + result.geometry.coordinates[0]} {@const resultValue =
result.geometry.coordinates[1] +
"," +
result.geometry.coordinates[0]}
<Command.Item <Command.Item
value={resultValue} value={resultValue}
onSelect={() => { onSelect={() => {
@ -168,21 +183,23 @@
}} }}
style="flex-direction: column; align-items: start;" style="flex-direction: column; align-items: start;"
> >
<div style="display: flex; align-items: center; gap: 5px; width: 100%;"> <div
<MapPinIcon style="display: flex; align-items: center; gap: 5px; width: 100%;"
class={cn( >
"mr-2 size-4" <MapPinIcon class={cn("mr-2 size-4")} />
)}
/>
{result.properties.name} {result.properties.name}
<CheckIcon <CheckIcon
class={cn( class={cn(
"mr-2 size-4 ml-auto", "mr-2 size-4 ml-auto",
value !== resultValue && "text-transparent" value !== resultValue && "text-transparent",
)} )}
/> />
</div> </div>
<span>{result.properties.street}{result.properties.housenumber ? " " + result.properties.housenumber : ""}, {result.properties.city}</span> <span
>{result.properties.street}{result.properties.housenumber
? " " + result.properties.housenumber
: ""}, {result.properties.city}</span
>
</Command.Item> </Command.Item>
{/each} {/each}
</Command.Group> </Command.Group>

View File

@ -48,7 +48,10 @@
let CurrentView: Component = $state(LoadingSidebar); let CurrentView: Component = $state(LoadingSidebar);
const modules = import.meta.glob("./sidebar/**/*.svelte"); const modules = import.meta.glob("./sidebar/**/*.svelte");
$effect(() => { $effect(() => {
const path = modules[`./sidebar/${views[view.current.type] || "InvalidSidebar"}.svelte`]; const path =
modules[
`./sidebar/${views[view.current.type] || "InvalidSidebar"}.svelte`
];
if (!path) { if (!path) {
// Invalid view // Invalid view
import("./sidebar/InvalidSidebar.svelte").then((m) => { import("./sidebar/InvalidSidebar.svelte").then((m) => {

View File

@ -1 +1 @@
Loading... Loading...

View File

@ -102,13 +102,18 @@
{#await getSaved() then saved} {#await getSaved() then saved}
{#if saved.length != 0} {#if saved.length != 0}
<div> <div>
<h2 style="margin: 5px; margin-left: 0; font-size: 1.2em;">Saved Routes</h2> <h2 style="margin: 5px; margin-left: 0; font-size: 1.2em;">
Saved Routes
</h2>
<div style="display: flex; flex-direction: column; gap: 10px;"> <div style="display: flex; flex-direction: column; gap: 10px;">
{#each saved as save} {#each saved as save, _index (save.name)}
<Button variant="secondary" onclick={() => { <Button
view.switch("trip", { route: JSON.parse(save.data) }) variant="secondary"
}}>{save.name}</Button> onclick={() => {
view.switch("trip", { route: JSON.parse(save.data) });
}}>{save.name}</Button
>
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { CircleArrowDown, CircleDotIcon, StarIcon } from "@lucide/svelte"; import { CircleArrowDown, CircleDotIcon, StarIcon } from "@lucide/svelte";
import Input from "$lib/components/ui/input/input.svelte";
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { createValhallaRequest } from "$lib/vehicles/ValhallaVehicles"; import { createValhallaRequest } from "$lib/vehicles/ValhallaVehicles";

View File

@ -48,16 +48,19 @@
</Button> </Button>
<RequiresCapability capability="saved-routes"> <RequiresCapability capability="saved-routes">
{#await isSaved($state.snapshot(route)) then saved} {#await isSaved($state.snapshot(route)) then saved}
<Button variant="secondary" onclick={async () => { <Button
if(saved) { variant="secondary"
await deleteSaved(saved); onclick={async () => {
view.back(); if (saved) {
} else { await deleteSaved(saved);
const name = prompt("Trip name?"); view.back();
if(!name) return; } else {
await putSaved(name, route); const name = prompt("Trip name?");
} if (!name) return;
}}> await putSaved(name, route);
}
}}
>
<SaveIcon /> <SaveIcon />
{saved ? m.unsave() : m["sidebar.trip.save"]()} {saved ? m.unsave() : m["sidebar.trip.save"]()}
</Button> </Button>

View File

@ -70,9 +70,11 @@
</Avatar.Root> </Avatar.Root>
{user.name || user.preferred_username} {user.name || user.preferred_username}
</SidebarHeader> </SidebarHeader>
<button onclick={() => { <button
refreshToken(); onclick={() => {
}}>refresh</button> refreshToken();
}}>refresh</button
>
<pre>{user.sub}</pre> <pre>{user.sub}</pre>
{JSON.stringify(user, null, 2)} {JSON.stringify(user, null, 2)}
{/if} {/if}

View File

@ -10,11 +10,11 @@ export function saveLocations() {
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;
const geocode = await reverseGeocode(loc); const geocode = await reverseGeocode(loc);
if(geocode.length == 0) { if (geocode.length == 0) {
return; return;
} }
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}`;
} }

View File

@ -1,7 +1,14 @@
import { LNV_SERVER } from "./hosts"; import { LNV_SERVER } from "./hosts";
import type { OIDCUser } from "./oidc"; import type { OIDCUser } from "./oidc";
export type Capabilities = ("auth" | "reviews" | "ai" | "fuel" | "post" | "saved-routes")[]; export type Capabilities = (
| "auth"
| "reviews"
| "ai"
| "fuel"
| "post"
| "saved-routes"
)[];
export let capabilities: Capabilities = []; export let capabilities: Capabilities = [];
export let oidcConfig: { export let oidcConfig: {
AUTH_URL: string; AUTH_URL: string;
@ -57,22 +64,22 @@ export async function uploadID() {
const res = await fetch(LNV_SERVER + "/user", { const res = await fetch(LNV_SERVER + "/user", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
token: localStorage.getItem("lnv-id") token: localStorage.getItem("lnv-id"),
}) }),
}); });
if(!res.ok) { if (!res.ok) {
alert("Failed to upload user data."); alert("Failed to upload user data.");
} }
} }
export async function refreshToken() { export async function refreshToken() {
const config = await getOIDCConfig(); const config = await getOIDCConfig();
if(!config) throw new Error("Server does not support OIDC."); if (!config) throw new Error("Server does not support OIDC.");
const refresh_token = localStorage.getItem("lnv-refresh"); const refresh_token = localStorage.getItem("lnv-refresh");
if(!refresh_token) throw new Error("No refresh token.") if (!refresh_token) throw new Error("No refresh token.");
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append("grant_type", "refresh_token"); params.append("grant_type", "refresh_token");
params.append("refresh_token", refresh_token); params.append("refresh_token", refresh_token);
@ -80,13 +87,13 @@ export async function refreshToken() {
const res = await fetch(config.TOKEN_URL, { const res = await fetch(config.TOKEN_URL, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded",
}, },
body: params body: params,
}); });
const data = (await res.json()) as OIDCUser; const data = (await res.json()) as OIDCUser;
if(!res.ok) { if (!res.ok) {
console.error("Refreshing token: " + res.status + " " + res.statusText) console.error("Refreshing token: " + res.status + " " + res.statusText);
console.error(data); console.error(data);
} }
console.log(data); console.log(data);
@ -96,24 +103,27 @@ export async function refreshToken() {
await uploadID(); await uploadID();
} }
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, { let res = await fetch(url, {
headers: { headers: {
"Authorization": "Bearer " + localStorage.getItem("lnv-token") Authorization: "Bearer " + localStorage.getItem("lnv-token"),
}, },
...params ...params,
}); });
if(res.status != 401) { if (res.status != 401) {
return res; return res;
} }
await refreshToken(); await refreshToken();
res = await fetch(url, { res = await fetch(url, {
headers: { headers: {
"Authorization": "Bearer " + localStorage.getItem("lnv-token") Authorization: "Bearer " + localStorage.getItem("lnv-token"),
}, },
...params ...params,
}); });
if(res.status == 401) { if (res.status == 401) {
console.error("Server is misconfigured."); console.error("Server is misconfigured.");
} }
return res; return res;
@ -188,8 +198,8 @@ export async function ai(query: string, location?: WorldLocation) {
return await res.text(); return await res.text();
} }
export function getSaved(): Promise<{ name: string; data: string; }[]> { export function getSaved(): Promise<{ name: string; data: string }[]> {
return authFetch(LNV_SERVER + "/saved").then(res => res.json()); return authFetch(LNV_SERVER + "/saved").then((res) => res.json());
} }
export function putSaved(name: string, data: object) { export function putSaved(name: string, data: object) {
@ -197,24 +207,26 @@ export function putSaved(name: string, data: object) {
method: "PUT", method: "PUT",
body: JSON.stringify({ body: JSON.stringify({
name, name,
data: JSON.stringify(data) data: JSON.stringify(data),
}) }),
}).then(res => res.json()); }).then((res) => res.json());
} }
export function deleteSaved(name: string) { export function deleteSaved(name: string) {
return authFetch(LNV_SERVER + "/saved", { return authFetch(LNV_SERVER + "/saved", {
method: "DELETE", method: "DELETE",
body: JSON.stringify({ body: JSON.stringify({
name name,
}) }),
}).then(res => res.text()); }).then((res) => res.text());
} }
export async function isSaved(data: Trip) { export async function isSaved(data: Trip) {
const res = await getSaved(); const res = await getSaved();
console.log(res, data); console.log(res, data);
const filtered = res.filter(s => JSON.parse(s.data).legs[0].shape == data.legs[0].shape); const filtered = res.filter(
if(filtered.length == 0) return false; (s) => JSON.parse(s.data).legs[0].shape == data.legs[0].shape,
);
if (filtered.length == 0) return false;
return filtered[0].name; return filtered[0].name;
} }

View File

@ -60,7 +60,7 @@ async function sha256(input: string | undefined): Promise<ArrayBuffer> {
return await window.crypto.subtle.digest("SHA-256", data); return await window.crypto.subtle.digest("SHA-256", data);
} }
export type OIDCUser = { export interface OIDCUser {
access_token: string; access_token: string;
token_type: string; token_type: string;
refresh_token: string; refresh_token: string;