style: run eslint and prettier
All checks were successful
TrafficCue CI / check (push) Successful in 58s
TrafficCue CI / build (push) Successful in 47s

This commit is contained in:
Cfp
2025-08-11 18:59:04 +02:00
parent 9d5319aee2
commit d7db55cd29
19 changed files with 1006 additions and 936 deletions

View File

@ -1,2 +1,3 @@
android/
dist/
src-tauri/

View File

@ -26,7 +26,7 @@ export default tseslint.config(
],
},
},
[globalIgnores(["./android", "./dist"])],
[globalIgnores(["./android", "./dist", "./src-tauri"])],
{
languageOptions: {
globals: {

View File

@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<link rel="manifest" href="manifest.json" />
<title>TrafficCue</title>
</head>

View File

@ -1,7 +1,6 @@
{
"version": 8,
"sources": {
},
"sources": {},
"sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
"glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
"layers": []

View File

@ -10,7 +10,11 @@
import { location } from "./location.svelte";
import { saved } from "$lib/saved.svelte";
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
import { getPMTilesURL, hasPMTiles, protocol } from "$lib/services/OfflineTiles";
import {
getPMTilesURL,
hasPMTiles,
protocol,
} from "$lib/services/OfflineTiles";
import { layers, worldLayers } from "$lib/mapLayers";
import { PMTilesProtocol } from "svelte-maplibre-gl/pmtiles";
@ -44,31 +48,25 @@
// @ts-expect-error - not typed
window.map = map.value;
// const worldUrl = await getPMTiles("world");
// if(worldUrl) {
map.value!.addSource("ne2_shaded", { // TODO: rename to world
map.value!.addSource("ne2_shaded", {
// TODO: rename to world
type: "vector",
url: await getPMTilesURL("world"),
attribution: "Natural Earth",
// maxzoom: 6
})
});
// @ts-expect-error - not typed correctly
worldLayers.forEach(l => map.value!.addLayer(l));
// }
worldLayers.forEach((l) => map.value!.addLayer(l));
// const url = await getPMTiles("tiles");
// console.log(url)
// if(url) {
map.value!.addSource("openmaptiles", {
type: "vector",
url: await hasPMTiles("tiles") ? await getPMTilesURL("tiles") : "pmtiles://https://trafficcue-tiles.picoscratch.de/germany.pmtiles"
})
url: (await hasPMTiles("tiles"))
? await getPMTilesURL("tiles")
: "pmtiles://https://trafficcue-tiles.picoscratch.de/germany.pmtiles",
});
// @ts-expect-error - not typed correctly
layers.forEach(l => map.value!.addLayer(l));
// }
layers.forEach((l) => map.value!.addLayer(l));
}}
onclick={(e) => {
if (view.current.type == "main" || view.current.type == "info") {

View File

@ -27,8 +27,6 @@
import * as Popover from "../ui/popover";
import { routing } from "$lib/services/navigation/routing.svelte";
import InRouteSidebar from "./sidebar/InRouteSidebar.svelte";
import say from "$lib/services/navigation/TTS";
import { downloadPMTiles } from "$lib/services/OfflineTiles";
import SettingsSidebar from "./sidebar/settings/SettingsSidebar.svelte";
import AboutSidebar from "./sidebar/settings/AboutSidebar.svelte";
import OfflineMapsSidebar from "./sidebar/settings/OfflineMapsSidebar.svelte";
@ -45,7 +43,7 @@
settings: SettingsSidebar,
about: AboutSidebar,
"offline-maps": OfflineMapsSidebar,
"dev-options": DeveloperSidebar
"dev-options": DeveloperSidebar,
};
let isDragging = false;
@ -164,9 +162,11 @@
<UserIcon />
</button>
</RequiresCapability>
<button onclick={() => {
<button
onclick={() => {
view.switch("settings");
}}>
}}
>
<SettingsIcon />
</button>
<!-- <button onclick={() => {

View File

@ -6,18 +6,21 @@
const dev = getDeveloperToggle();
</script>
<SidebarHeader>
About
</SidebarHeader>
<SidebarHeader>About</SidebarHeader>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<h1 style="font-size: 2em; font-weight: bold;" onclick={() => {
<h1
style="font-size: 2em; font-weight: bold;"
onclick={() => {
count--;
if (count == 0) {
dev.current = "true";
}
}}>TrafficCue</h1>
}}
>
TrafficCue
</h1>
<span>Powered by:</span>
<ul>
<li>© OpenStreetMap contributors</li>

View File

@ -10,31 +10,42 @@
const dev = getDeveloperToggle();
</script>
<SidebarHeader>
Developer Settings
</SidebarHeader>
<SidebarHeader>Developer Settings</SidebarHeader>
<div id="sections">
<section>
<h2>Test</h2>
<SettingsButton icon={SpeechIcon} text="Test TTS" onclick={async () => {
await say("Test")
}} />
<SettingsButton icon={MapIcon} disabled={!window.__TAURI__} text="Download tiles from URL{window.__TAURI__ ? "" : " (Unavailable)"}" onclick={async () => {
<SettingsButton
icon={SpeechIcon}
text="Test TTS"
onclick={async () => {
await say("Test");
}}
/>
<SettingsButton
icon={MapIcon}
disabled={!window.__TAURI__}
text="Download tiles from URL{window.__TAURI__ ? '' : ' (Unavailable)'}"
onclick={async () => {
const name = prompt("Name?");
if (!name) return;
const url = prompt("URL?");
if (!url) return;
await downloadPMTiles(url, name);
}} />
}}
/>
</section>
<section>
<h2>Other</h2>
<SettingsButton icon={ToggleLeftIcon} text="Disable Developer Options" onclick={async () => {
<SettingsButton
icon={ToggleLeftIcon}
text="Disable Developer Options"
onclick={async () => {
dev.current = "false";
view.back();
}} />
}}
/>
</section>
</div>

View File

@ -4,12 +4,10 @@
import SettingsButton from "./SettingsButton.svelte";
import SidebarHeader from "../SidebarHeader.svelte";
let progresses: {[key: string]: number} = $state({});
let progresses: Record<string, number> = $state({});
</script>
<SidebarHeader>
Offline Maps
</SidebarHeader>
<SidebarHeader>Offline Maps</SidebarHeader>
{#await getRemoteList()}
<p>Loading...</p>
@ -24,13 +22,23 @@
{/if}
{#each list as item, _index (item.file)}
<SettingsButton disabled={!window.__TAURI__} icon={DownloadCloudIcon} text={item.name} progress={progresses[item.file] ?? -1} onclick={async () => {
await downloadPMTiles("https://trafficcue-tiles.picoscratch.de/" + item.file, item.name.includes("World") ? "world": "tiles", (progress, total) => {
<SettingsButton
disabled={!window.__TAURI__}
icon={DownloadCloudIcon}
text={item.name}
progress={progresses[item.file] ?? -1}
onclick={async () => {
await downloadPMTiles(
"https://trafficcue-tiles.picoscratch.de/" + item.file,
item.name.includes("World") ? "world" : "tiles",
(progress, total) => {
progresses[item.file] = (progress / total) * 100;
});
},
);
alert(`Downloaded ${item.name}`);
location.reload();
}} />
}}
/>
{/each}
</div>
{/await}

View File

@ -6,16 +6,31 @@
import Progress from "$lib/components/ui/progress/progress.svelte";
const {
icon: Icon, text, view: viewName, onclick, disabled, progress
icon: Icon,
text,
view: viewName,
onclick,
disabled,
progress,
}: {
icon: Component<IconProps>, text: string, view?: string, onclick?: () => void, disabled?: boolean, progress?: number
icon: Component<IconProps>;
text: string;
view?: string;
onclick?: () => void;
disabled?: boolean;
progress?: number;
} = $props();
</script>
<Button variant="secondary" style="width: 100%; height: 40px;" {disabled} onclick={() => {
if(viewName) view.switch(viewName)
<Button
variant="secondary"
style="width: 100%; height: 40px;"
{disabled}
onclick={() => {
if (viewName) view.switch(viewName);
if (onclick) onclick();
}}>
}}
>
<Icon />
{text}
{#if progress == -1}

View File

@ -1,5 +1,11 @@
<script>
import { CodeIcon, InfoIcon, LanguagesIcon, MapIcon, PaintbrushIcon } from "@lucide/svelte";
import {
CodeIcon,
InfoIcon,
LanguagesIcon,
MapIcon,
PaintbrushIcon,
} from "@lucide/svelte";
import SidebarHeader from "../SidebarHeader.svelte";
import SettingsButton from "./SettingsButton.svelte";
import { getDeveloperToggle } from "./developer.svelte";
@ -7,9 +13,7 @@
const dev = getDeveloperToggle();
</script>
<SidebarHeader>
Settings
</SidebarHeader>
<SidebarHeader>Settings</SidebarHeader>
<div id="sections">
<section>
@ -26,7 +30,11 @@
<section>
<h2>About</h2>
{#if dev.current == "true"}
<SettingsButton icon={CodeIcon} text="Developer Settings" view="dev-options" />
<SettingsButton
icon={CodeIcon}
text="Developer Settings"
view="dev-options"
/>
{/if}
<SettingsButton icon={InfoIcon} text="About" view="about" />
</section>

View File

@ -1,7 +1,7 @@
export function getDeveloperToggle() {
const value = localStorage.getItem("developer")
const value = localStorage.getItem("developer");
const state = $state({
current: value == null ? "false" : value
current: value == null ? "false" : value,
});
$effect(() => {

View File

@ -14,7 +14,10 @@
<ProgressPrimitive.Root
bind:ref
data-slot="progress"
class={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
class={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className,
)}
{value}
{max}
{...restProps}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,20 @@
import { appDataDir, join } from "@tauri-apps/api/path";
import { BaseDirectory, exists, mkdir, open, remove, SeekMode } from "@tauri-apps/plugin-fs";
import {
BaseDirectory,
exists,
mkdir,
open,
remove,
SeekMode,
} from "@tauri-apps/plugin-fs";
import { download } from "@tauri-apps/plugin-upload";
import { PMTiles, TileType, type Source } from "pmtiles";
export async function downloadPMTiles(url: string, name: string, onprogress?: (progress: number, total: number) => any): Promise<void> {
export async function downloadPMTiles(
url: string,
name: string,
onprogress?: (progress: number, total: number) => void,
): Promise<void> {
// if(!window.__TAURI__) {
// throw new Error("Tauri environment is not available.");
// }
@ -12,7 +23,7 @@ export async function downloadPMTiles(url: string, name: string, onprogress?: (p
const baseDir = BaseDirectory.AppData;
const appDataDirPath = await appDataDir();
if(!await exists(appDataDirPath)) {
if (!(await exists(appDataDirPath))) {
await mkdir(appDataDirPath, { recursive: true });
}
@ -33,15 +44,21 @@ export async function downloadPMTiles(url: string, name: string, onprogress?: (p
let totalProgress = 0;
await download(url, path, ({ progress, total }) => {
totalProgress += progress;
console.log(`Download progress: ${Math.round((totalProgress / total) * 100)}% (${totalProgress}\tof ${total} bytes)`);
console.log(
`Download progress: ${Math.round((totalProgress / total) * 100)}% (${totalProgress}\tof ${total} bytes)`,
);
if (onprogress) onprogress(totalProgress, total);
});
console.log(`Download completed: ${path}`);
}
export async function getRemoteList(): Promise<{ name: string, file: string }[]> {
return await fetch("https://trafficcue-tiles.picoscratch.de/index.json").then(res => res.json());
export async function getRemoteList(): Promise<
{ name: string; file: string }[]
> {
return await fetch("https://trafficcue-tiles.picoscratch.de/index.json").then(
(res) => res.json(),
);
}
export async function hasPMTiles(name: string): Promise<boolean> {
@ -53,7 +70,7 @@ export async function hasPMTiles(name: string): Promise<boolean> {
const baseDir = BaseDirectory.AppData;
const appDataDirPath = await appDataDir();
if(!await exists(appDataDirPath)) {
if (!(await exists(appDataDirPath))) {
return false;
}
@ -69,14 +86,14 @@ export async function getPMTilesURL(name: string) {
const baseDir = BaseDirectory.AppData;
const appDataDirPath = await appDataDir();
if(!await exists(appDataDirPath)) {
if (!(await exists(appDataDirPath))) {
return `pmtiles://https://trafficcue-tiles.picoscratch.de/${name}.pmtiles`;
// throw new Error("App data directory does not exist.");
}
const filePath = await join(appDataDirPath, filename);
if(!await exists(filePath, { baseDir })) {
if (!(await exists(filePath, { baseDir }))) {
return `pmtiles://https://trafficcue-tiles.picoscratch.de/${name}.pmtiles`;
// throw new Error(`PMTiles file not found: ${filePath}`);
}
@ -84,8 +101,15 @@ export async function getPMTilesURL(name: string) {
return `tiles://${name}`;
}
async function readBytes(name: string, offset: number, length: number): Promise<Uint8Array> {
const file = await open(name + ".pmtiles", { read: true, baseDir: BaseDirectory.AppData });
async function readBytes(
name: string,
offset: number,
length: number,
): Promise<Uint8Array> {
const file = await open(name + ".pmtiles", {
read: true,
baseDir: BaseDirectory.AppData,
});
const buffer = new Uint8Array(length);
await file.seek(offset, SeekMode.Start);
await file.read(buffer);
@ -100,14 +124,20 @@ export class FSSource implements Source {
this.name = name;
}
async getBytes(offset: number, length: number, _signal?: AbortSignal, _etag?: string) { // TODO: abort signal
async getBytes(
offset: number,
length: number,
_signal?: AbortSignal,
_etag?: string,
) {
// TODO: abort signal
const data = await readBytes(this.name, offset, length);
return {
data: data.buffer as ArrayBuffer,
etag: undefined,
cacheControl: undefined,
expires: undefined
}
expires: undefined,
};
}
getKey = () => this.name;
@ -121,12 +151,13 @@ interface RequestParameters {
type?: "string" | "json" | "arrayBuffer" | "image";
credentials?: "same-origin" | "include";
collectResourceTiming?: boolean;
};
}
export class Protocol {
/** @hidden */
tiles: Map<string, PMTiles>;
metadata: boolean; errorOnMissingTile: boolean;
metadata: boolean;
errorOnMissingTile: boolean;
/**
* Initialize the MapLibre PMTiles protocol.
@ -163,7 +194,7 @@ export class Protocol {
/** @hidden */
tilev4 = async (
params: RequestParameters,
abortController: AbortController
abortController: AbortController,
) => {
if (params.type === "json") {
const pmtilesUrl = params.url.substr(8);
@ -183,7 +214,7 @@ export class Protocol {
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
console.error(
`Bounds of PMTiles archive ${h.minLon},${h.minLat},${h.maxLon},${h.maxLat} are not valid.`
`Bounds of PMTiles archive ${h.minLon},${h.minLat},${h.maxLon},${h.maxLat} are not valid.`,
);
}

View File

@ -60,7 +60,12 @@ out center tags;`,
}).then((res) => res.json() as Promise<OverpassResult>);
}
export async function fetchNearbyPOI(lat: number, lon: number, tags: string[], radius: number) {
export async function fetchNearbyPOI(
lat: number,
lon: number,
tags: string[],
radius: number,
) {
return await fetch(OVERPASS_SERVER, {
method: "POST",
body: `[out:json];

View File

@ -23,7 +23,7 @@ export default async function say(text: string) {
duck();
if (tts !== "web") {
try {
await invoke("plugin:tts|speak", { text })
await invoke("plugin:tts|speak", { text });
} catch (e) {
console.error("Error speaking text", e);
alert(e);

View File

@ -12,22 +12,23 @@ export default defineConfig({
port: 5173,
strictPort: true,
host: host || false,
hmr: host ? {
hmr: host
? {
protocol: "ws",
host,
port: 1421
} : undefined,
watch: {
ignored: ["**/src-tauri/**"]
port: 1421,
}
: undefined,
watch: {
ignored: ["**/src-tauri/**"],
},
},
envPrefix: ["VITE_", "TAURI_ENV_*"],
build: {
target: process.env.TAURI_ENV_PLATFORM == "windows"
? "chrome105"
: "safari13",
target:
process.env.TAURI_ENV_PLATFORM == "windows" ? "chrome105" : "safari13",
minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
sourcemap: !!process.env.TAURI_ENV_DEBUG
sourcemap: !!process.env.TAURI_ENV_DEBUG,
},
resolve: {
alias: {