style: run prettier
This commit is contained in:
@ -55,13 +55,22 @@
|
||||
preferredFuel: "Diesel",
|
||||
});
|
||||
|
||||
const biggerButtonsInDrive = localStore<boolean>("bigger-buttons-in-drive", false);
|
||||
const shouldUseLargeSize = $derived(biggerButtonsInDrive.current ? isDriving() : false);
|
||||
const biggerButtonsInDrive = localStore<boolean>(
|
||||
"bigger-buttons-in-drive",
|
||||
false,
|
||||
);
|
||||
const shouldUseLargeSize = $derived(
|
||||
biggerButtonsInDrive.current ? isDriving() : false,
|
||||
);
|
||||
</script>
|
||||
|
||||
<Drawer.Root bind:open>
|
||||
<Drawer.Trigger
|
||||
class={buttonVariants({ variant: "secondary", class: "w-full", size: shouldUseLargeSize ? "drive" : "default" })}
|
||||
class={buttonVariants({
|
||||
variant: "secondary",
|
||||
class: "w-full",
|
||||
size: shouldUseLargeSize ? "drive" : "default",
|
||||
})}
|
||||
>
|
||||
{@render children()}
|
||||
</Drawer.Trigger>
|
||||
|
||||
@ -178,8 +178,13 @@
|
||||
{/if}
|
||||
|
||||
{#if !hideSearch && !!location.speed != false}
|
||||
<div id="speedometer" style="position: fixed; {mobileView ? `bottom: calc(50px + ${sidebarHeight.current}px + 10px); right: 10px;` : "bottom: 10px; right: 10px;"}">
|
||||
{(location.speed * 3.6 | 0).toFixed(0)}
|
||||
<div
|
||||
id="speedometer"
|
||||
style="position: fixed; {mobileView
|
||||
? `bottom: calc(50px + ${sidebarHeight.current}px + 10px); right: 10px;`
|
||||
: 'bottom: 10px; right: 10px;'}"
|
||||
>
|
||||
{((location.speed * 3.6) | 0).toFixed(0)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if getRoadMetadata().current}
|
||||
@ -187,7 +192,12 @@
|
||||
{#if meta.maxspeed}
|
||||
{@const maxspeed = getSpeed(meta.maxspeed)}
|
||||
{#if maxspeed && maxspeed < 100}
|
||||
<div id="max-speed" style="position: fixed; {mobileView ? `bottom: calc(50px + ${sidebarHeight.current}px + 10px + 2ch + 20px + 10px); right: 10px;` : "bottom: calc(10px + 2ch + 20px + 10px); right: 10px;"}">
|
||||
<div
|
||||
id="max-speed"
|
||||
style="position: fixed; {mobileView
|
||||
? `bottom: calc(50px + ${sidebarHeight.current}px + 10px + 2ch + 20px + 10px); right: 10px;`
|
||||
: 'bottom: calc(10px + 2ch + 20px + 10px); right: 10px;'}"
|
||||
>
|
||||
{meta.maxspeed}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -41,13 +41,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
const biggerButtonsInDrive = localStore<boolean>("bigger-buttons-in-drive", false);
|
||||
const shouldUseLargeSize = $derived(biggerButtonsInDrive.current ? isDriving() : false);
|
||||
const biggerButtonsInDrive = localStore<boolean>(
|
||||
"bigger-buttons-in-drive",
|
||||
false,
|
||||
);
|
||||
const shouldUseLargeSize = $derived(
|
||||
biggerButtonsInDrive.current ? isDriving() : false,
|
||||
);
|
||||
</script>
|
||||
|
||||
<Drawer.Root bind:open>
|
||||
<Drawer.Trigger
|
||||
class={buttonVariants({ variant: "secondary", class: "w-full", size: shouldUseLargeSize ? "drive" : "default" })}
|
||||
class={buttonVariants({
|
||||
variant: "secondary",
|
||||
class: "w-full",
|
||||
size: shouldUseLargeSize ? "drive" : "default",
|
||||
})}
|
||||
>
|
||||
{@const vehicle = selectedVehicle()?.data ?? DefaultVehicle}
|
||||
{@const Icon = getVehicleIcon(vehicle.type)}
|
||||
|
||||
@ -35,13 +35,15 @@ export const location = $state({
|
||||
lastUpdate: null as Date | null,
|
||||
});
|
||||
|
||||
const _isDriving = $derived(location.speed > (7 / 3.6));
|
||||
const _isDriving = $derived(location.speed > 7 / 3.6);
|
||||
|
||||
export function isDriving() {
|
||||
return _isDriving;
|
||||
}
|
||||
|
||||
const roadMetadata: WrappedValue<GeoJSON.GeoJsonProperties> = $state({ current: null });
|
||||
const roadMetadata: WrappedValue<GeoJSON.GeoJsonProperties> = $state({
|
||||
current: null,
|
||||
});
|
||||
|
||||
export function getRoadMetadata() {
|
||||
return roadMetadata;
|
||||
@ -60,8 +62,11 @@ export function watchLocation() {
|
||||
location.available = true;
|
||||
location.heading = pos.coords.heading;
|
||||
location.lastUpdate = new Date();
|
||||
|
||||
getMeta({ lat: location.lat, lon: location.lng }, "transportation").then((meta) => {
|
||||
|
||||
getMeta(
|
||||
{ lat: location.lat, lon: location.lng },
|
||||
"transportation",
|
||||
).then((meta) => {
|
||||
roadMetadata.current = meta;
|
||||
});
|
||||
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Button from "$lib/components/ui/button/button.svelte";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { fetchEvents, type DAVCalendar, type DAVCredentials, type DAVEvent } from "$lib/services/CalDAV";
|
||||
import {
|
||||
fetchEvents,
|
||||
type DAVCalendar,
|
||||
type DAVCredentials,
|
||||
type DAVEvent,
|
||||
} from "$lib/services/CalDAV";
|
||||
import { search } from "$lib/services/Search";
|
||||
import { onMount } from "svelte";
|
||||
import { map, pin } from "../map.svelte";
|
||||
@ -11,13 +16,14 @@
|
||||
|
||||
onMount(async () => {
|
||||
const calendars = localStorage.getItem("calendars");
|
||||
if(!calendars) return;
|
||||
const parsedCalendars: (DAVCalendar & { credentials: DAVCredentials })[] = JSON.parse(calendars);
|
||||
for(const calendar of parsedCalendars) {
|
||||
if (!calendars) return;
|
||||
const parsedCalendars: (DAVCalendar & { credentials: DAVCredentials })[] =
|
||||
JSON.parse(calendars);
|
||||
for (const calendar of parsedCalendars) {
|
||||
const calendarEvents = await fetchEvents(calendar, calendar.credentials);
|
||||
events.push(...calendarEvents);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="events">
|
||||
@ -31,35 +37,40 @@
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<p>
|
||||
<strong>{m["calendar.start"]()}:</strong> {event.start?.toLocaleString("de-DE") || m["calendar.no-start"]()}
|
||||
<strong>{m["calendar.start"]()}:</strong>
|
||||
{event.start?.toLocaleString("de-DE") || m["calendar.no-start"]()}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{m["calendar.end"]()}:</strong> {event.end?.toLocaleString("de-DE") || m["calendar.no-end"]()}
|
||||
<strong>{m["calendar.end"]()}:</strong>
|
||||
{event.end?.toLocaleString("de-DE") || m["calendar.no-end"]()}
|
||||
</p>
|
||||
<p>
|
||||
{#if event.location}
|
||||
<strong>{m["calendar.location"]()}:</strong> {event.location}
|
||||
{:else}
|
||||
<strong>{m["calendar.location"]()}:</strong> {m["calendar.no-location"]()}
|
||||
<strong>{m["calendar.location"]()}:</strong>
|
||||
{m["calendar.no-location"]()}
|
||||
{/if}
|
||||
</p>
|
||||
</Card.Content>
|
||||
<Card.Footer>
|
||||
<Button onclick={async () => {
|
||||
if(!event.location) return;
|
||||
const features = await search(event.location, 0, 0);
|
||||
if(features.length == 0) {
|
||||
return void alert("Can't find location");
|
||||
}
|
||||
const lat = features[0].geometry.coordinates[1];
|
||||
const lng = features[0].geometry.coordinates[0];
|
||||
pin.dropPin(lat, lng);
|
||||
pin.showInfo();
|
||||
map.value?.flyTo({
|
||||
center: [lng, lat],
|
||||
zoom: 19,
|
||||
});
|
||||
}}>{m.open()}</Button>
|
||||
<Button
|
||||
onclick={async () => {
|
||||
if (!event.location) return;
|
||||
const features = await search(event.location, 0, 0);
|
||||
if (features.length == 0) {
|
||||
return void alert("Can't find location");
|
||||
}
|
||||
const lat = features[0].geometry.coordinates[1];
|
||||
const lng = features[0].geometry.coordinates[0];
|
||||
pin.dropPin(lat, lng);
|
||||
pin.showInfo();
|
||||
map.value?.flyTo({
|
||||
center: [lng, lat],
|
||||
zoom: 19,
|
||||
});
|
||||
}}>{m.open()}</Button
|
||||
>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
{/each}
|
||||
|
||||
@ -74,11 +74,13 @@
|
||||
const FROM: WorldLocation =
|
||||
fromLocation == "current"
|
||||
? { lat: location.lat, lon: location.lng }
|
||||
: savedLocations.current.find(s => s.name == fromLocation)
|
||||
: savedLocations.current.find((s) => s.name == fromLocation)
|
||||
? {
|
||||
lat: savedLocations.current.find(s => s.name == fromLocation)!.data.lat,
|
||||
lon: savedLocations.current.find(s => s.name == fromLocation)!.data.lng,
|
||||
}
|
||||
lat: savedLocations.current.find((s) => s.name == fromLocation)!
|
||||
.data.lat,
|
||||
lon: savedLocations.current.find((s) => s.name == fromLocation)!
|
||||
.data.lng,
|
||||
}
|
||||
: {
|
||||
lat: parseFloat(fromLocation.split(",")[0]),
|
||||
lon: parseFloat(fromLocation.split(",")[1]),
|
||||
@ -86,11 +88,13 @@
|
||||
const TO: WorldLocation =
|
||||
toLocation == "current"
|
||||
? { lat: location.lat, lon: location.lng }
|
||||
: savedLocations.current.find(s => s.name == toLocation)
|
||||
: savedLocations.current.find((s) => s.name == toLocation)
|
||||
? {
|
||||
lat: savedLocations.current.find(s => s.name == fromLocation)!.data.lat,
|
||||
lon: savedLocations.current.find(s => s.name == fromLocation)!.data.lng,
|
||||
}
|
||||
lat: savedLocations.current.find((s) => s.name == fromLocation)!
|
||||
.data.lat,
|
||||
lon: savedLocations.current.find((s) => s.name == fromLocation)!
|
||||
.data.lng,
|
||||
}
|
||||
: {
|
||||
lat: parseFloat(toLocation.split(",")[0]),
|
||||
lon: parseFloat(toLocation.split(",")[1]),
|
||||
|
||||
@ -8,4 +8,7 @@
|
||||
{m["sidebar.appearance.header"]()}
|
||||
</SidebarHeader>
|
||||
|
||||
<SettingsToggle text={m["sidebar.appearance.bigger-buttons-in-drive"]()} localStorageKey="bigger-buttons-in-drive" />
|
||||
<SettingsToggle
|
||||
text={m["sidebar.appearance.bigger-buttons-in-drive"]()}
|
||||
localStorageKey="bigger-buttons-in-drive"
|
||||
/>
|
||||
|
||||
@ -6,10 +6,16 @@
|
||||
import * as Drawer from "$lib/components/ui/drawer";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import Input from "$lib/components/ui/input/input.svelte";
|
||||
import { fetchCalendars, findScheme, type AuthScheme, type DAVCalendar, type DAVCredentials } from "$lib/services/CalDAV";
|
||||
import {
|
||||
fetchCalendars,
|
||||
findScheme,
|
||||
type AuthScheme,
|
||||
type DAVCalendar,
|
||||
type DAVCredentials,
|
||||
} from "$lib/services/CalDAV";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let calendars: (DAVCalendar & {credentials: DAVCredentials})[] = $state([]);
|
||||
|
||||
let calendars: (DAVCalendar & { credentials: DAVCredentials })[] = $state([]);
|
||||
|
||||
let calDavDrawerOpen = $state(false);
|
||||
let calDavLoading = $state(false);
|
||||
@ -25,15 +31,19 @@
|
||||
calDavState = m["sidebar.calendar.probing-server"]();
|
||||
calDavScheme = await findScheme(calDavUrl);
|
||||
calDavState = m["sidebar.calendar.discovering-calendars"]();
|
||||
calDavCalendars = await fetchCalendars(calDavUrl, { scheme: calDavScheme, username: calDavUsername, password: calDavPassword });
|
||||
calDavCalendars = await fetchCalendars(calDavUrl, {
|
||||
scheme: calDavScheme,
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
});
|
||||
calDavState = "";
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if(localStorage.getItem("calendars")) {
|
||||
if (localStorage.getItem("calendars")) {
|
||||
calendars = JSON.parse(localStorage.getItem("calendars")!);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<SidebarHeader>
|
||||
@ -41,15 +51,30 @@
|
||||
</SidebarHeader>
|
||||
|
||||
<Drawer.Root bind:open={calDavDrawerOpen}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>{m["sidebar.calendar.connect"]()}</Drawer.Title>
|
||||
</Drawer.Header>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>{m["sidebar.calendar.connect"]()}</Drawer.Title>
|
||||
</Drawer.Header>
|
||||
<div class="p-4 pt-0 flex flex-col gap-2">
|
||||
{#if calDavCalendars}
|
||||
<Input type="url" placeholder="https://my-caldav-server.com/..." disabled={calDavLoading} bind:value={calDavUrl} />
|
||||
<Input type="text" placeholder="Username" disabled={calDavLoading} bind:value={calDavUsername} />
|
||||
<Input type="password" placeholder="Password" disabled={calDavLoading} bind:value={calDavPassword} />
|
||||
<Input
|
||||
type="url"
|
||||
placeholder="https://my-caldav-server.com/..."
|
||||
disabled={calDavLoading}
|
||||
bind:value={calDavUrl}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
disabled={calDavLoading}
|
||||
bind:value={calDavUsername}
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
disabled={calDavLoading}
|
||||
bind:value={calDavPassword}
|
||||
/>
|
||||
{#if calDavState}
|
||||
<span>{calDavState}</span>
|
||||
{/if}
|
||||
@ -59,40 +84,51 @@
|
||||
<ul class="max-h-48 overflow-y-auto">
|
||||
{#each calDavCalendars as calendar (calendar.url)}
|
||||
<li>
|
||||
<Button class="w-full" variant="secondary" onclick={() => {
|
||||
if(localStorage.getItem("calendars")) {
|
||||
const existing = JSON.parse(localStorage.getItem("calendars")!);
|
||||
existing.push({
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme
|
||||
}
|
||||
});
|
||||
localStorage.setItem("calendars", JSON.stringify(existing));
|
||||
} else {
|
||||
localStorage.setItem("calendars", JSON.stringify([{
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme
|
||||
}
|
||||
}]));
|
||||
}
|
||||
calendars.push({
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme
|
||||
<Button
|
||||
class="w-full"
|
||||
variant="secondary"
|
||||
onclick={() => {
|
||||
if (localStorage.getItem("calendars")) {
|
||||
const existing = JSON.parse(
|
||||
localStorage.getItem("calendars")!,
|
||||
);
|
||||
existing.push({
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme,
|
||||
},
|
||||
});
|
||||
localStorage.setItem("calendars", JSON.stringify(existing));
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
"calendars",
|
||||
JSON.stringify([
|
||||
{
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme,
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
});
|
||||
}}>
|
||||
calendars.push({
|
||||
name: calendar.name,
|
||||
url: calendar.url,
|
||||
credentials: {
|
||||
username: calDavUsername,
|
||||
password: calDavPassword,
|
||||
scheme: calDavScheme,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{calendar.name}
|
||||
</Button>
|
||||
</li>
|
||||
@ -100,33 +136,44 @@
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<Drawer.Footer>
|
||||
<Drawer.Footer>
|
||||
{#if calDavCalendars.length === 0}
|
||||
<Button onclick={async () => {
|
||||
calDavLoading = true;
|
||||
await fetchCalDav().catch((e) => {
|
||||
calDavState = e;
|
||||
calDavLoading = false;
|
||||
})
|
||||
}}>{m.submit()}</Button>
|
||||
<Button
|
||||
onclick={async () => {
|
||||
calDavLoading = true;
|
||||
await fetchCalDav().catch((e) => {
|
||||
calDavState = e;
|
||||
calDavLoading = false;
|
||||
});
|
||||
}}>{m.submit()}</Button
|
||||
>
|
||||
{/if}
|
||||
<Drawer.Close>
|
||||
<Drawer.Close>
|
||||
{calDavCalendars.length === 0 ? m.done() : m.cancel()}
|
||||
</Drawer.Close>
|
||||
</Drawer.Footer>
|
||||
</Drawer.Content>
|
||||
</Drawer.Footer>
|
||||
</Drawer.Content>
|
||||
</Drawer.Root>
|
||||
|
||||
{#each calendars as calendar (calendar.url)}
|
||||
<div class="p-2 border rounded mb-2">
|
||||
<h3 class="font-medium">{calendar.name}</h3>
|
||||
<Button variant="destructive" size="sm" class="mt-2" onclick={() => {
|
||||
calendars = calendars.filter(c => c.url !== calendar.url);
|
||||
localStorage.setItem("calendars", JSON.stringify(calendars));
|
||||
}}>{m.delete()}</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
class="mt-2"
|
||||
onclick={() => {
|
||||
calendars = calendars.filter((c) => c.url !== calendar.url);
|
||||
localStorage.setItem("calendars", JSON.stringify(calendars));
|
||||
}}>{m.delete()}</Button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<SettingsButton text={m["sidebar.calendar.connect"]()} icon={CalendarPlusIcon} onclick={() => {
|
||||
calDavDrawerOpen = true;
|
||||
}} />
|
||||
<SettingsButton
|
||||
text={m["sidebar.calendar.connect"]()}
|
||||
icon={CalendarPlusIcon}
|
||||
onclick={() => {
|
||||
calDavDrawerOpen = true;
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import {
|
||||
CalendarSearchIcon,
|
||||
CalendarSearchIcon,
|
||||
CloudUploadIcon,
|
||||
HandIcon,
|
||||
MapIcon,
|
||||
@ -62,20 +62,21 @@
|
||||
onclick={async () => {
|
||||
const url = prompt("URL?");
|
||||
if (!url) return;
|
||||
const scheme = await invoke("dav_find_scheme", { url })
|
||||
.catch((err) => {
|
||||
alert("Error fetching scheme: " + err);
|
||||
});
|
||||
const scheme = await invoke("dav_find_scheme", { url }).catch((err) => {
|
||||
alert("Error fetching scheme: " + err);
|
||||
});
|
||||
alert("Found scheme: " + scheme);
|
||||
const username = prompt("Username?");
|
||||
const password = prompt("Password?");
|
||||
if (!username || !password) return;
|
||||
const credentials = { scheme, username, password };
|
||||
invoke("dav_fetch_calendars", { url, credentials }).then((calendars) => {
|
||||
alert("Fetched calendars: " + JSON.stringify(calendars));
|
||||
}).catch((err) => {
|
||||
alert("Error fetching calendars: " + err);
|
||||
});
|
||||
invoke("dav_fetch_calendars", { url, credentials })
|
||||
.then((calendars) => {
|
||||
alert("Fetched calendars: " + JSON.stringify(calendars));
|
||||
})
|
||||
.catch((err) => {
|
||||
alert("Error fetching calendars: " + err);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SettingsButton
|
||||
@ -88,11 +89,16 @@
|
||||
const password = prompt("Password?");
|
||||
if (!url || !username || !password) return;
|
||||
const credentials = { scheme, username, password };
|
||||
invoke("dav_fetch_events", { calendar: { name: "Calendar", url }, credentials }).then((events) => {
|
||||
alert("Fetched events: " + JSON.stringify(events));
|
||||
}).catch((err) => {
|
||||
alert("Error fetching events: " + err);
|
||||
});
|
||||
invoke("dav_fetch_events", {
|
||||
calendar: { name: "Calendar", url },
|
||||
credentials,
|
||||
})
|
||||
.then((events) => {
|
||||
alert("Fetched events: " + JSON.stringify(events));
|
||||
})
|
||||
.catch((err) => {
|
||||
alert("Error fetching events: " + err);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
view="appearance"
|
||||
/>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h2>{m["sidebar.settings.connections"]()}</h2>
|
||||
<SettingsButton
|
||||
|
||||
@ -11,7 +11,11 @@
|
||||
onchange,
|
||||
disabled,
|
||||
localStorageKey,
|
||||
value = $bindable(localStorageKey ? localStorage.getItem(localStorageKey) === "true" : false),
|
||||
value = $bindable(
|
||||
localStorageKey
|
||||
? localStorage.getItem(localStorageKey) === "true"
|
||||
: false,
|
||||
),
|
||||
}: {
|
||||
icon?: Component<IconProps>;
|
||||
text: string;
|
||||
@ -23,20 +27,26 @@
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
<Label
|
||||
style="width: 100%;"
|
||||
for="settings-toggle"
|
||||
>
|
||||
<Label style="width: 100%;" for="settings-toggle">
|
||||
{#if Icon}
|
||||
<Icon />
|
||||
{/if}
|
||||
{text}
|
||||
</Label>
|
||||
<Switch {disabled} bind:checked={value} onCheckedChange={() => {
|
||||
if (onchange) onchange();
|
||||
if (localStorageKey) {
|
||||
localStorage.setItem(localStorageKey, value ? "true" : "false");
|
||||
eventTarget.dispatchEvent(new CustomEvent("localStorageChanged", { detail: { key: localStorageKey, value } }));
|
||||
}
|
||||
}} id="settings-toggle" />
|
||||
<Switch
|
||||
{disabled}
|
||||
bind:checked={value}
|
||||
onCheckedChange={() => {
|
||||
if (onchange) onchange();
|
||||
if (localStorageKey) {
|
||||
localStorage.setItem(localStorageKey, value ? "true" : "false");
|
||||
eventTarget.dispatchEvent(
|
||||
new CustomEvent("localStorageChanged", {
|
||||
detail: { key: localStorageKey, value },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
id="settings-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -8,8 +8,13 @@
|
||||
} from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
const biggerButtonsInDrive = localStore<boolean>("bigger-buttons-in-drive", false);
|
||||
const shouldUseLargeSize = $derived(biggerButtonsInDrive.current ? isDriving() : false);
|
||||
const biggerButtonsInDrive = localStore<boolean>(
|
||||
"bigger-buttons-in-drive",
|
||||
false,
|
||||
);
|
||||
const shouldUseLargeSize = $derived(
|
||||
biggerButtonsInDrive.current ? isDriving() : false,
|
||||
);
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
@ -32,7 +37,7 @@
|
||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
drive: "h-12 rounded-md px-8 has-[>svg]:px-6"
|
||||
drive: "h-12 rounded-md px-8 has-[>svg]:px-6",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@ -69,7 +74,10 @@
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size: shouldUseLargeSize ? "drive" : size }), className)}
|
||||
class={cn(
|
||||
buttonVariants({ variant, size: shouldUseLargeSize ? "drive" : size }),
|
||||
className,
|
||||
)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
@ -82,7 +90,10 @@
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size: shouldUseLargeSize ? "drive" : size }), className)}
|
||||
class={cn(
|
||||
buttonVariants({ variant, size: shouldUseLargeSize ? "drive" : size }),
|
||||
className,
|
||||
)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="label"
|
||||
class={cn(
|
||||
"flex select-none items-center gap-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -16,14 +16,14 @@
|
||||
data-slot="switch"
|
||||
class={cn(
|
||||
"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 shadow-xs peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
class={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
|
||||
@ -22,18 +22,24 @@ export interface DAVEvent {
|
||||
}
|
||||
|
||||
export async function findScheme(url: string): Promise<AuthScheme> {
|
||||
const scheme = await invoke("dav_find_scheme", { url });
|
||||
const scheme = await invoke("dav_find_scheme", { url });
|
||||
return scheme as AuthScheme;
|
||||
}
|
||||
|
||||
export async function fetchCalendars(url: string, credentials: DAVCredentials): Promise<DAVCalendar[]> {
|
||||
export async function fetchCalendars(
|
||||
url: string,
|
||||
credentials: DAVCredentials,
|
||||
): Promise<DAVCalendar[]> {
|
||||
const calendars = await invoke("dav_fetch_calendars", { url, credentials });
|
||||
return calendars as DAVCalendar[];
|
||||
}
|
||||
|
||||
export async function fetchEvents(calendar: DAVCalendar, credentials: DAVCredentials): Promise<DAVEvent[]> {
|
||||
export async function fetchEvents(
|
||||
calendar: DAVCalendar,
|
||||
credentials: DAVCredentials,
|
||||
): Promise<DAVEvent[]> {
|
||||
const events = await invoke("dav_fetch_events", { calendar, credentials });
|
||||
return (events as DAVEvent[]).map(e => {
|
||||
return (events as DAVEvent[]).map((e) => {
|
||||
if (e.start) e.start = new Date(e.start);
|
||||
if (e.end) e.end = new Date(e.end);
|
||||
return e;
|
||||
|
||||
@ -11,8 +11,8 @@ function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) {
|
||||
// Compute the min distance across all parts
|
||||
return Math.min(
|
||||
...f.geometry.coordinates.map((coords) =>
|
||||
pointToLineDistance(point, lineString(coords))
|
||||
)
|
||||
pointToLineDistance(point, lineString(coords)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Infinity;
|
||||
@ -30,8 +30,11 @@ export async function getMeta(coord: WorldLocation, layer: string) {
|
||||
const feature = layerData.feature(i);
|
||||
features.push(feature.toGeoJSON(zxy.x, zxy.y, zxy.z));
|
||||
}
|
||||
const filtered = features.filter((f) => f.geometry.type === "LineString" || f.geometry.type == "MultiLineString");
|
||||
if(filtered.length === 0) return null;
|
||||
const filtered = features.filter(
|
||||
(f) =>
|
||||
f.geometry.type === "LineString" || f.geometry.type == "MultiLineString",
|
||||
);
|
||||
if (filtered.length === 0) return null;
|
||||
const nearest = filtered.reduce((a, b) => {
|
||||
const distA = getFeatureDistance(a, [coord.lon, coord.lat]);
|
||||
const distB = getFeatureDistance(b, [coord.lon, coord.lat]);
|
||||
@ -44,16 +47,16 @@ const IMPLICIT_SPEEDS: Record<string, number> = {
|
||||
"DE:urban": 50,
|
||||
"DE:rural": 100, // TODO: 80 (hgv weight > 3.5t, or trailer), 60 (weight > 7.5t)
|
||||
"DE:living_street": 7,
|
||||
"DE:bicycle_road": 30
|
||||
}
|
||||
"DE:bicycle_road": 30,
|
||||
};
|
||||
|
||||
export function getSpeed(maxspeed: string): number | null {
|
||||
if(!isNaN(parseInt(maxspeed))) return parseInt(maxspeed);
|
||||
if(maxspeed.endsWith(" mph")) {
|
||||
if (!isNaN(parseInt(maxspeed))) return parseInt(maxspeed);
|
||||
if (maxspeed.endsWith(" mph")) {
|
||||
const val = parseInt(maxspeed.replace(" mph", ""));
|
||||
if(!isNaN(val)) return Math.round(val * 1.60934); // Convert to km/h
|
||||
if (!isNaN(val)) return Math.round(val * 1.60934); // Convert to km/h
|
||||
}
|
||||
if(maxspeed === "walk") return 7; // https://wiki.openstreetmap.org/wiki/Proposed_features/maxspeed_walk
|
||||
if (maxspeed === "walk") return 7; // https://wiki.openstreetmap.org/wiki/Proposed_features/maxspeed_walk
|
||||
return IMPLICIT_SPEEDS[maxspeed as keyof typeof IMPLICIT_SPEEDS] || null;
|
||||
}
|
||||
|
||||
@ -64,11 +67,11 @@ function coordToTile(coord: WorldLocation, zoom: number) {
|
||||
((1 -
|
||||
Math.log(
|
||||
Math.tan((coord.lat * Math.PI) / 180) +
|
||||
1 / Math.cos((coord.lat * Math.PI) / 180)
|
||||
1 / Math.cos((coord.lat * Math.PI) / 180),
|
||||
) /
|
||||
Math.PI) /
|
||||
2) *
|
||||
Math.pow(2, z)
|
||||
Math.pow(2, z),
|
||||
);
|
||||
return { z, x, y };
|
||||
}
|
||||
@ -117,7 +120,7 @@ export async function fetchTile(z: number, x: number, y: number) {
|
||||
const pmtiles = (await hasPMTiles("tiles"))
|
||||
? new PMTiles(new FSSource("tiles"))
|
||||
: new PMTiles("https://trafficcue-tiles.picoscratch.de/germany.pmtiles");
|
||||
const tile = (await pmtiles.getZxy(z, x, y));
|
||||
const tile = await pmtiles.getZxy(z, x, y);
|
||||
if (!tile) {
|
||||
console.log(tile);
|
||||
throw new Error(`Tile not found: z${z} x${x} y${y}`);
|
||||
|
||||
@ -4,15 +4,16 @@ export const eventTarget = new EventTarget();
|
||||
|
||||
export function localStore<T>(key: string, defaultValue: T): WrappedValue<T> {
|
||||
const storedValue = localStorage.getItem(key);
|
||||
const initialValue = storedValue !== null ? JSON.parse(storedValue) : defaultValue;
|
||||
const initialValue =
|
||||
storedValue !== null ? JSON.parse(storedValue) : defaultValue;
|
||||
const state = $state<WrappedValue<T>>({ current: initialValue });
|
||||
eventTarget.addEventListener("localStorageChanged", (event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
console.log("localStorageChanged event received", customEvent.detail);
|
||||
if(customEvent.detail.key === key) {
|
||||
if (customEvent.detail.key === key) {
|
||||
const newValue = customEvent.detail.value as T;
|
||||
console.log(`localStore: ${key} updated from another tab`, newValue);
|
||||
if(JSON.stringify(state.current) === JSON.stringify(newValue)) return;
|
||||
if (JSON.stringify(state.current) === JSON.stringify(newValue)) return;
|
||||
state.current = newValue;
|
||||
}
|
||||
});
|
||||
@ -22,8 +23,12 @@ export function localStore<T>(key: string, defaultValue: T): WrappedValue<T> {
|
||||
},
|
||||
set current(newValue: T) {
|
||||
state.current = newValue;
|
||||
eventTarget.dispatchEvent(new CustomEvent("localStorageChanged", { detail: { key, value: newValue } }));
|
||||
eventTarget.dispatchEvent(
|
||||
new CustomEvent("localStorageChanged", {
|
||||
detail: { key, value: newValue },
|
||||
}),
|
||||
);
|
||||
localStorage.setItem(key, JSON.stringify(newValue));
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user