style: add eslint and prettier
Some checks failed
TrafficCue CI / check (push) Successful in 26s
TrafficCue CI / build (push) Has been cancelled

This commit is contained in:
Cfp
2025-06-22 17:53:32 +02:00
parent 16c0f0c399
commit f2348873fd
100 changed files with 5110 additions and 7344 deletions

View File

@ -10,7 +10,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: bun i run: bun i
- run: bun run check - run: bun run check
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -23,4 +23,4 @@ jobs:
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: dist name: dist
path: dist/ path: dist/

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ dist-ssr
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/settings.json
.idea .idea
.DS_Store .DS_Store
*.suo *.suo

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
android/
dist/

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"useTabs": true,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -1,3 +1,3 @@
{ {
"recommendations": ["svelte.svelte-vscode"] "recommendations": ["svelte.svelte-vscode"]
} }

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"eslint.validate": ["javascript", "typescript", "svelte"]
}

View File

@ -10,7 +10,7 @@
**TrafficCue** is a FOSS navigation app built for vehicles that max out at 45 km/h, so mopeds, microcars, LEVs, and the like (but not limited to them!). **TrafficCue** is a FOSS navigation app built for vehicles that max out at 45 km/h, so mopeds, microcars, LEVs, and the like (but not limited to them!).
Mainstream navigation often routes these vehicles onto roads they're not legally allowed to use. **We dont.** Mainstream navigation often routes these vehicles onto roads they're not legally allowed to use. **We dont.**
Built with **Svelte + Vite**, powered by **Capacitor** for mobile, and focused on *respectful routing*, **TrafficCue** fills the gap. Built with **Svelte + Vite**, powered by **Capacitor** for mobile, and focused on _respectful routing_, **TrafficCue** fills the gap.
--- ---

259
bun.lock
View File

@ -7,8 +7,13 @@
"@capacitor/android": "^7.3.0", "@capacitor/android": "^7.3.0",
"@capacitor/cli": "^7.3.0", "@capacitor/cli": "^7.3.0",
"@capacitor/core": "^7.3.0", "@capacitor/core": "^7.3.0",
"@eslint/js": "^9.29.0",
"eslint": "^9.29.0",
"eslint-plugin-svelte": "^3.9.3",
"globals": "^16.2.0",
"opening_hours": "^3.8.0", "opening_hours": "^3.8.0",
"svelte-maplibre-gl": "^0.1.8", "svelte-maplibre-gl": "^0.1.8",
"typescript-eslint": "^8.34.1",
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.8.1", "@internationalized/date": "^3.8.1",
@ -19,13 +24,15 @@
"@types/node": "^22.15.24", "@types/node": "^22.15.24",
"bits-ui": "^2.7.0", "bits-ui": "^2.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"svelte": "^5.28.1", "eslint-config-prettier": "^10.1.5",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.34.7",
"svelte-check": "^4.1.6", "svelte-check": "^4.1.6",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"tw-animate-css": "^1.3.2", "tw-animate-css": "^1.3.2",
"typescript": "~5.8.3", "typescript": "^5.8.3",
"vaul-svelte": "^1.0.0-next.7", "vaul-svelte": "^1.0.0-next.7",
"vite": "^6.3.5", "vite": "^6.3.5",
}, },
@ -98,12 +105,38 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
"@eslint/config-array": ["@eslint/config-array@0.20.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.3", "", {}, "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg=="],
"@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"@eslint/js": ["@eslint/js@9.29.0", "", {}, "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.2", "", { "dependencies": { "@eslint/core": "^0.15.0", "levn": "^0.4.1" } }, "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg=="],
"@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="], "@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@internationalized/date": ["@internationalized/date@3.8.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA=="], "@internationalized/date": ["@internationalized/date@3.8.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA=="],
"@ionic/cli-framework-output": ["@ionic/cli-framework-output@2.2.8", "", { "dependencies": { "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g=="], "@ionic/cli-framework-output": ["@ionic/cli-framework-output@2.2.8", "", { "dependencies": { "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g=="],
@ -184,6 +217,12 @@
"@math.gl/web-mercator": ["@math.gl/web-mercator@4.1.0", "", { "dependencies": { "@math.gl/core": "4.1.0" } }, "sha512-HZo3vO5GCMkXJThxRJ5/QYUYRr3XumfT8CzNNCwoJfinxy5NtKUd7dusNTXn7yJ40UoB8FMIwkVwNlqaiRZZAw=="], "@math.gl/web-mercator": ["@math.gl/web-mercator@4.1.0", "", { "dependencies": { "@math.gl/core": "4.1.0" } }, "sha512-HZo3vO5GCMkXJThxRJ5/QYUYRr3XumfT8CzNNCwoJfinxy5NtKUd7dusNTXn7yJ40UoB8FMIwkVwNlqaiRZZAw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@probe.gl/env": ["@probe.gl/env@4.1.0", "", {}, "sha512-5ac2Jm2K72VCs4eSMsM7ykVRrV47w32xOGMvcgqn8vQdEMF9PRXyBGYEV9YbqRKWNKpNKmQJVi4AHM/fkCxs9w=="], "@probe.gl/env": ["@probe.gl/env@4.1.0", "", {}, "sha512-5ac2Jm2K72VCs4eSMsM7ykVRrV47w32xOGMvcgqn8vQdEMF9PRXyBGYEV9YbqRKWNKpNKmQJVi4AHM/fkCxs9w=="],
"@probe.gl/log": ["@probe.gl/log@4.1.0", "", { "dependencies": { "@probe.gl/env": "4.1.0" } }, "sha512-r4gRReNY6f+OZEMgfWEXrAE2qJEt8rX0HsDJQXUBMoc+5H47bdB7f/5HBHAmapK8UydwPKL9wCDoS22rJ0yq7Q=="], "@probe.gl/log": ["@probe.gl/log@4.1.0", "", { "dependencies": { "@probe.gl/env": "4.1.0" } }, "sha512-r4gRReNY6f+OZEMgfWEXrAE2qJEt8rX0HsDJQXUBMoc+5H47bdB7f/5HBHAmapK8UydwPKL9wCDoS22rJ0yq7Q=="],
@ -278,6 +317,8 @@
"@types/geojson-vt": ["@types/geojson-vt@3.2.5", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g=="], "@types/geojson-vt": ["@types/geojson-vt@3.2.5", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/mapbox__point-geometry": ["@types/mapbox__point-geometry@0.1.4", "", {}, "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA=="], "@types/mapbox__point-geometry": ["@types/mapbox__point-geometry@0.1.4", "", {}, "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA=="],
"@types/mapbox__vector-tile": ["@types/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="], "@types/mapbox__vector-tile": ["@types/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="],
@ -292,14 +333,40 @@
"@types/supercluster": ["@types/supercluster@7.1.3", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA=="], "@types/supercluster": ["@types/supercluster@7.1.3", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.34.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/type-utils": "8.34.1", "@typescript-eslint/utils": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.34.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.34.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1" } }, "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.34.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/utils": "8.34.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.34.1", "", {}, "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/typescript-estree": "8.34.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw=="],
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.10", "", {}, "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="], "@xmldom/xmldom": ["@xmldom/xmldom@0.8.10", "", {}, "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
@ -318,10 +385,16 @@
"bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="], "bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
@ -334,12 +407,18 @@
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
@ -360,9 +439,41 @@
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="], "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ=="],
"eslint-config-prettier": ["eslint-config-prettier@10.1.5", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw=="],
"eslint-plugin-svelte": ["eslint-plugin-svelte@3.9.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.37.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.2.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-PlcyK80sqAZ43IITeZkgl3zPFWJytx/Joup9iKGqIOsXM2m3pWfPbWuXPr5PN3loXFEypqTY/JyZwNqlSpSvRw=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="], "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
@ -370,6 +481,16 @@
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], "fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
@ -386,16 +507,30 @@
"glob": ["glob@11.0.2", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ=="], "glob": ["glob@11.0.2", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"global-prefix": ["global-prefix@4.0.0", "", { "dependencies": { "ini": "^4.1.3", "kind-of": "^6.0.3", "which": "^4.0.0" } }, "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA=="], "global-prefix": ["global-prefix@4.0.0", "", { "dependencies": { "ini": "^4.1.3", "kind-of": "^6.0.3", "which": "^4.0.0" } }, "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA=="],
"globals": ["globals@16.2.0", "", {}, "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"i18next": ["i18next@21.10.0", "", { "dependencies": { "@babel/runtime": "^7.17.2" } }, "sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg=="], "i18next": ["i18next@21.10.0", "", { "dependencies": { "@babel/runtime": "^7.17.2" } }, "sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg=="],
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@6.1.8", "", { "dependencies": { "@babel/runtime": "^7.19.0" } }, "sha512-Svm+MduCElO0Meqpj1kJAriTC6OhI41VhlT/A0UPjGoPZBhAHIaGE5EfsHlTpgdH09UVX7rcc72pSDDBeKSQQA=="], "i18next-browser-languagedetector": ["i18next-browser-languagedetector@6.1.8", "", { "dependencies": { "@babel/runtime": "^7.19.0" } }, "sha512-Svm+MduCElO0Meqpj1kJAriTC6OhI41VhlT/A0UPjGoPZBhAHIaGE5EfsHlTpgdH09UVX7rcc72pSDDBeKSQQA=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="], "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
@ -404,8 +539,14 @@
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
@ -416,16 +557,30 @@
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json-stringify-pretty-compact": ["json-stringify-pretty-compact@4.0.0", "", {}, "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="], "json-stringify-pretty-compact": ["json-stringify-pretty-compact@4.0.0", "", {}, "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="],
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], "jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
"kdbush": ["kdbush@4.0.2", "", {}, "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="], "kdbush": ["kdbush@4.0.2", "", {}, "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
@ -448,8 +603,14 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lru-cache": ["lru-cache@11.1.0", "", {}, "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A=="], "lru-cache": ["lru-cache@11.1.0", "", {}, "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
@ -458,7 +619,11 @@
"maplibre-gl": ["maplibre-gl@5.5.0", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@maplibre/maplibre-gl-style-spec": "^23.2.2", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "earcut": "^3.0.1", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.3.0", "potpack": "^2.0.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-p8AOPuzzqn1ZA9gcXxKw0IED715we/2Owa/YUr6PANmgMvNMe/JG+V/C1hRra43Wm62Biz+Aa8AgbOLJimA8tA=="], "maplibre-gl": ["maplibre-gl@5.5.0", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@maplibre/maplibre-gl-style-spec": "^23.2.2", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "earcut": "^3.0.1", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.3.0", "potpack": "^2.0.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-p8AOPuzzqn1ZA9gcXxKw0IED715we/2Owa/YUr6PANmgMvNMe/JG+V/C1hRra43Wm62Biz+Aa8AgbOLJimA8tA=="],
"minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@ -480,12 +645,24 @@
"native-run": ["native-run@2.0.1", "", { "dependencies": { "@ionic/utils-fs": "^3.1.7", "@ionic/utils-terminal": "^2.3.4", "bplist-parser": "^0.3.2", "debug": "^4.3.4", "elementtree": "^0.1.7", "ini": "^4.1.1", "plist": "^3.1.0", "split2": "^4.2.0", "through2": "^4.0.2", "tslib": "^2.6.2", "yauzl": "^2.10.0" }, "bin": { "native-run": "bin/native-run" } }, "sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w=="], "native-run": ["native-run@2.0.1", "", { "dependencies": { "@ionic/utils-fs": "^3.1.7", "@ionic/utils-terminal": "^2.3.4", "bplist-parser": "^0.3.2", "debug": "^4.3.4", "elementtree": "^0.1.7", "ini": "^4.1.1", "plist": "^3.1.0", "split2": "^4.2.0", "through2": "^4.0.2", "tslib": "^2.6.2", "yauzl": "^2.10.0" }, "bin": { "native-run": "bin/native-run" } }, "sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
"opening_hours": ["opening_hours@3.8.0", "", { "dependencies": { "i18next": "^21.8.3", "i18next-browser-languagedetector": "^6.1.4", "suncalc": "^1.9.0" } }, "sha512-bRJroECQSe/itVcNmC3j9PPicxn/LBowdd1Hi+4Aa7hCswdt7w81WHfUwrEMbtk1BBYmGJEbSepl8oYYPviSuA=="], "opening_hours": ["opening_hours@3.8.0", "", { "dependencies": { "i18next": "^21.8.3", "i18next-browser-languagedetector": "^6.1.4", "suncalc": "^1.9.0" } }, "sha512-bRJroECQSe/itVcNmC3j9PPicxn/LBowdd1Hi+4Aa7hCswdt7w81WHfUwrEMbtk1BBYmGJEbSepl8oYYPviSuA=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
@ -504,24 +681,48 @@
"postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="], "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
"postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
"postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="],
"postcss-scss": ["postcss-scss@4.0.9", "", { "peerDependencies": { "postcss": "^8.4.29" } }, "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"potpack": ["potpack@2.0.0", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="], "potpack": ["potpack@2.0.0", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"protocol-buffers-schema": ["protocol-buffers-schema@3.6.0", "", {}, "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="], "protocol-buffers-schema": ["protocol-buffers-schema@3.6.0", "", {}, "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quickselect": ["quickselect@3.0.0", "", {}, "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="], "quickselect": ["quickselect@3.0.0", "", {}, "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="], "resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rimraf": ["rimraf@6.0.1", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="], "rimraf": ["rimraf@6.0.1", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="],
"rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="], "rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="], "runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
@ -558,16 +759,22 @@
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="], "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
"suncalc": ["suncalc@1.9.0", "", {}, "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A=="], "suncalc": ["suncalc@1.9.0", "", {}, "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A=="],
"supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="], "supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="],
"svelte": ["svelte@5.33.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-qQYkNHE3L26ahV4VvW4DhEHOrAT6LfZHjIIW7htZs2bFP0/ayep/t4RvA6Km7Yosxo0nR+o2vZHf99553l4ZtQ=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"svelte": ["svelte@5.34.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.8", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-5PEg+QQKce4t1qiOtVUhUS3AQRTtxJyGBTpxLcNWnr0Ve8q4r06bMo0Gv8uhtCPWlztZHoi3Ye7elLhu+PCTMg=="],
"svelte-check": ["svelte-check@4.2.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA=="], "svelte-check": ["svelte-check@4.2.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA=="],
"svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
"svelte-maplibre-gl": ["svelte-maplibre-gl@0.1.8", "", { "peerDependencies": { "@deck.gl/core": "^9.1.0", "@deck.gl/layers": "^9.1.0", "@deck.gl/mapbox": "^9.1.0", "maplibre-contour": ">=0.1.0", "maplibre-gl": "^5.0.0 || ^4.0.0", "pmtiles": "^4.0.0", "svelte": ">=5.0.0", "terra-draw": "^1.0.0", "terra-draw-maplibre-gl-adapter": "^1.0.3" } }, "sha512-YvMo25q/rpNDNE4iBvOuYYt+E+6jT+PBLxX7vR20LE5ZD2K3cLV9cR34S4SX7w81E00lP7InD2+CvFr7T0vBxg=="], "svelte-maplibre-gl": ["svelte-maplibre-gl@0.1.8", "", { "peerDependencies": { "@deck.gl/core": "^9.1.0", "@deck.gl/layers": "^9.1.0", "@deck.gl/mapbox": "^9.1.0", "maplibre-contour": ">=0.1.0", "maplibre-gl": "^5.0.0 || ^4.0.0", "pmtiles": "^4.0.0", "svelte": ">=5.0.0", "terra-draw": "^1.0.0", "terra-draw-maplibre-gl-adapter": "^1.0.3" } }, "sha512-YvMo25q/rpNDNE4iBvOuYYt+E+6jT+PBLxX7vR20LE5ZD2K3cLV9cR34S4SX7w81E00lP7InD2+CvFr7T0vBxg=="],
"svelte-toolbelt": ["svelte-toolbelt@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.28.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g=="], "svelte-toolbelt": ["svelte-toolbelt@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.28.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g=="],
@ -594,20 +801,30 @@
"tinyqueue": ["tinyqueue@3.0.0", "", {}, "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="], "tinyqueue": ["tinyqueue@3.0.0", "", {}, "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tw-animate-css": ["tw-animate-css@1.3.2", "", {}, "sha512-khGYcg4sHWFWcjpiWvy0KN0Bd6yVy6Ecc4r9ZP2u7FV+n4/Fp8MQscCWJkM0KMIRvrpGyKpIQnIbEd1hrewdeg=="], "tw-animate-css": ["tw-animate-css@1.3.2", "", {}, "sha512-khGYcg4sHWFWcjpiWvy0KN0Bd6yVy6Ecc4r9ZP2u7FV+n4/Fp8MQscCWJkM0KMIRvrpGyKpIQnIbEd1hrewdeg=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"typescript-eslint": ["typescript-eslint@8.34.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.34.1", "@typescript-eslint/parser": "8.34.1", "@typescript-eslint/utils": "8.34.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="], "untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="], "vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
@ -622,6 +839,8 @@
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@ -632,10 +851,22 @@
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.15.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw=="],
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@ionic/utils-fs/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@ionic/utils-fs/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
@ -660,18 +891,28 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="], "elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"glob/minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="],
"glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"global-prefix/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "global-prefix/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
"maplibre-gl/earcut": ["earcut@3.0.1", "", {}, "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="], "maplibre-gl/earcut": ["earcut@3.0.1", "", {}, "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@ -702,6 +943,10 @@
"@tailwindcss/oxide/tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "@tailwindcss/oxide/tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"global-prefix/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], "global-prefix/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
} }
} }

View File

@ -1,9 +1,9 @@
import type { CapacitorConfig } from '@capacitor/cli'; import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = { const config: CapacitorConfig = {
appId: 'de.trafficcue.trafficcue', appId: "de.trafficcue.trafficcue",
appName: 'TrafficCue', appName: "TrafficCue",
webDir: 'dist' webDir: "dist",
}; };
export default config; export default config;

55
eslint.config.mjs Normal file
View File

@ -0,0 +1,55 @@
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import { globalIgnores } from "eslint/config";
import svelte from "eslint-plugin-svelte";
import globals from "globals";
import svelteConfig from "./svelte.config.js";
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
tseslint.configs.stylistic,
...svelte.configs.recommended,
eslintConfigPrettier,
{
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
[globalIgnores(["./android", "./dist"])],
{
languageOptions: {
globals: {
...globals.browser,
},
},
},
{
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: [".svelte"], // Add support for additional file extensions, such as .svelte
parser: tseslint.parser,
// We recommend importing and specifying svelte.config.js.
// By doing so, some rules in eslint-plugin-svelte will automatically read the configuration and adjust their behavior accordingly.
// While certain Svelte settings may be statically loaded from svelte.config.js even if you dont specify it,
// explicitly specifying it ensures better compatibility and functionality.
svelteConfig,
},
},
rules: {
"no-undef": "off",
},
},
);

View File

@ -1,14 +1,14 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json" />
<title>TrafficCue</title> <title>TrafficCue</title>
</head> </head>
<body class="dark"> <body class="dark">
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -1,38 +1,45 @@
{ {
"name": "librenav", "name": "librenav",
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.8.1", "@internationalized/date": "^3.8.1",
"@lucide/svelte": "^0.515.0", "@lucide/svelte": "^0.515.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"@types/node": "^22.15.24", "@types/node": "^22.15.24",
"bits-ui": "^2.7.0", "bits-ui": "^2.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"svelte": "^5.28.1", "eslint-config-prettier": "^10.1.5",
"svelte-check": "^4.1.6", "prettier-plugin-svelte": "^3.4.0",
"tailwind-merge": "^3.0.2", "svelte": "^5.34.7",
"tailwind-variants": "^1.0.0", "svelte-check": "^4.1.6",
"tailwindcss": "^4.0.0", "tailwind-merge": "^3.0.2",
"tw-animate-css": "^1.3.2", "tailwind-variants": "^1.0.0",
"typescript": "~5.8.3", "tailwindcss": "^4.0.0",
"vaul-svelte": "^1.0.0-next.7", "tw-animate-css": "^1.3.2",
"vite": "^6.3.5" "typescript": "^5.8.3",
}, "vaul-svelte": "^1.0.0-next.7",
"private": true, "vite": "^6.3.5"
"scripts": { },
"dev": "vite", "private": true,
"build": "vite build", "scripts": {
"preview": "vite preview", "dev": "vite",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" "build": "vite build",
}, "preview": "vite preview",
"type": "module", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
"dependencies": { },
"@capacitor/android": "^7.3.0", "type": "module",
"@capacitor/cli": "^7.3.0", "dependencies": {
"@capacitor/core": "^7.3.0", "@capacitor/android": "^7.3.0",
"opening_hours": "^3.8.0", "@capacitor/cli": "^7.3.0",
"svelte-maplibre-gl": "^0.1.8" "@capacitor/core": "^7.3.0",
} "@eslint/js": "^9.29.0",
"eslint": "^9.29.0",
"eslint-plugin-svelte": "^3.9.3",
"globals": "^16.2.0",
"opening_hours": "^3.8.0",
"svelte-maplibre-gl": "^0.1.8",
"typescript-eslint": "^8.34.1"
}
} }

View File

@ -1,17 +1,17 @@
{ {
"name": "TrafficCue", "name": "TrafficCue",
"icons": [ "icons": [
{ {
"src": "img/icon512.png", "src": "img/icon512.png",
"type": "image/png", "type": "image/png",
"sizes": "512x512" "sizes": "512x512"
}, },
{ {
"src": "img/icon192.png", "src": "img/icon192.png",
"type": "image/png", "type": "image/png",
"sizes": "192x192" "sizes": "192x192"
} }
], ],
"start_url": "/", "start_url": "/",
"display": "standalone" "display": "standalone"
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import "./app.css"; import "./app.css";
import { GeolocateControl, Hash, MapLibre } from "svelte-maplibre-gl";
import Sidebar from "./lib/components/lnv/Sidebar.svelte"; import Sidebar from "./lib/components/lnv/Sidebar.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import Map from "$lib/components/lnv/Map.svelte"; import Map from "$lib/components/lnv/Map.svelte";
@ -9,8 +8,10 @@
import RoutingInfo from "$lib/components/lnv/RoutingInfo.svelte"; import RoutingInfo from "$lib/components/lnv/RoutingInfo.svelte";
onMount(() => { onMount(() => {
if(!checkWebGL()) { if (!checkWebGL()) {
alert("WebGL is not supported in your browser. Please try a different browser."); alert(
"WebGL is not supported in your browser. Please try a different browser.",
);
return; return;
} }
}); });
@ -20,4 +21,4 @@
<RoutingInfo /> <RoutingInfo />
{/if} {/if}
<Sidebar></Sidebar> <Sidebar></Sidebar>
<Map></Map> <Map></Map>

View File

@ -5,159 +5,177 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
:root { :root {
--background: oklch(1.0000 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.2644 0 0); --foreground: oklch(0.2644 0 0);
--card: oklch(1.0000 0 0); --card: oklch(1 0 0);
--card-foreground: oklch(0.2644 0 0); --card-foreground: oklch(0.2644 0 0);
--popover: oklch(1.0000 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.2644 0 0); --popover-foreground: oklch(0.2644 0 0);
--primary: oklch(0.3261 0 0); --primary: oklch(0.3261 0 0);
--primary-foreground: oklch(0.9886 0 0); --primary-foreground: oklch(0.9886 0 0);
--secondary: oklch(0.9772 0 0); --secondary: oklch(0.9772 0 0);
--secondary-foreground: oklch(0.3261 0 0); --secondary-foreground: oklch(0.3261 0 0);
--muted: oklch(0.9772 0 0); --muted: oklch(0.9772 0 0);
--muted-foreground: oklch(0.6460 0 0); --muted-foreground: oklch(0.646 0 0);
--accent: oklch(0.9772 0 0); --accent: oklch(0.9772 0 0);
--accent-foreground: oklch(0.3261 0 0); --accent-foreground: oklch(0.3261 0 0);
--destructive: oklch(0.6201 0.2092 25.7747); --destructive: oklch(0.6201 0.2092 25.7747);
--destructive-foreground: oklch(1.0000 0 0); --destructive-foreground: oklch(1 0 0);
--border: oklch(0.9404 0 0); --border: oklch(0.9404 0 0);
--input: oklch(0.9404 0 0); --input: oklch(0.9404 0 0);
--ring: oklch(0.7716 0 0); --ring: oklch(0.7716 0 0);
--chart-1: oklch(0.8241 0.1251 84.4866); --chart-1: oklch(0.8241 0.1251 84.4866);
--chart-2: oklch(0.8006 0.1116 203.6044); --chart-2: oklch(0.8006 0.1116 203.6044);
--chart-3: oklch(0.4198 0.1693 266.7798); --chart-3: oklch(0.4198 0.1693 266.7798);
--chart-4: oklch(0.9214 0.0762 125.5777); --chart-4: oklch(0.9214 0.0762 125.5777);
--chart-5: oklch(0.9151 0.1032 116.1913); --chart-5: oklch(0.9151 0.1032 116.1913);
--sidebar: oklch(0.9886 0 0); --sidebar: oklch(0.9886 0 0);
--sidebar-foreground: oklch(0.2644 0 0); --sidebar-foreground: oklch(0.2644 0 0);
--sidebar-primary: oklch(0.3261 0 0); --sidebar-primary: oklch(0.3261 0 0);
--sidebar-primary-foreground: oklch(0.9886 0 0); --sidebar-primary-foreground: oklch(0.9886 0 0);
--sidebar-accent: oklch(0.9772 0 0); --sidebar-accent: oklch(0.9772 0 0);
--sidebar-accent-foreground: oklch(0.3261 0 0); --sidebar-accent-foreground: oklch(0.3261 0 0);
--sidebar-border: oklch(0.9404 0 0); --sidebar-border: oklch(0.9404 0 0);
--sidebar-ring: oklch(0.7716 0 0); --sidebar-ring: oklch(0.7716 0 0);
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-sans:
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
--radius: 0.625rem; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --font-mono:
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); "Courier New", monospace;
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); --radius: 0.625rem;
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); --shadow-sm:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
} }
.dark { .dark {
--background: oklch(0.1405 0.0044 285.8238); --background: oklch(0.1405 0.0044 285.8238);
--foreground: oklch(0.9848 0 0); --foreground: oklch(0.9848 0 0);
--card: oklch(0.1405 0.0044 285.8238); --card: oklch(0.1405 0.0044 285.8238);
--card-foreground: oklch(0.9848 0 0); --card-foreground: oklch(0.9848 0 0);
--popover: oklch(0.1405 0.0044 285.8238); --popover: oklch(0.1405 0.0044 285.8238);
--popover-foreground: oklch(0.9848 0 0); --popover-foreground: oklch(0.9848 0 0);
--primary: oklch(0.5111 0.2152 266.7098); --primary: oklch(0.5111 0.2152 266.7098);
--primary-foreground: oklch(1.0000 0 0); --primary-foreground: oklch(1 0 0);
--secondary: oklch(0.2741 0.0055 286.0329); --secondary: oklch(0.2741 0.0055 286.0329);
--secondary-foreground: oklch(0.9848 0 0); --secondary-foreground: oklch(0.9848 0 0);
--muted: oklch(0.2741 0.0055 286.0329); --muted: oklch(0.2741 0.0055 286.0329);
--muted-foreground: oklch(0.7119 0.0129 286.0684); --muted-foreground: oklch(0.7119 0.0129 286.0684);
--accent: oklch(0.5111 0.2152 266.7098); --accent: oklch(0.5111 0.2152 266.7098);
--accent-foreground: oklch(0.9848 0 0); --accent-foreground: oklch(0.9848 0 0);
--destructive: oklch(0.3959 0.1331 25.7205); --destructive: oklch(0.3959 0.1331 25.7205);
--destructive-foreground: oklch(0.9710 0.0127 17.3758); --destructive-foreground: oklch(0.971 0.0127 17.3758);
--border: oklch(0.2741 0.0055 286.0329); --border: oklch(0.2741 0.0055 286.0329);
--input: oklch(0.2741 0.0055 286.0329); --input: oklch(0.2741 0.0055 286.0329);
--ring: oklch(0.8709 0.0055 286.2853); --ring: oklch(0.8709 0.0055 286.2853);
--chart-1: oklch(0.5292 0.1931 262.1292); --chart-1: oklch(0.5292 0.1931 262.1292);
--chart-2: oklch(0.6983 0.1337 165.4626); --chart-2: oklch(0.6983 0.1337 165.4626);
--chart-3: oklch(0.7232 0.1500 60.6307); --chart-3: oklch(0.7232 0.15 60.6307);
--chart-4: oklch(0.6192 0.2037 312.7283); --chart-4: oklch(0.6192 0.2037 312.7283);
--chart-5: oklch(0.6123 0.2093 6.3856); --chart-5: oklch(0.6123 0.2093 6.3856);
--sidebar: oklch(0.2103 0.0059 285.8835); --sidebar: oklch(0.2103 0.0059 285.8835);
--sidebar-foreground: oklch(0.9676 0.0013 286.3752); --sidebar-foreground: oklch(0.9676 0.0013 286.3752);
--sidebar-primary: oklch(0.4878 0.2170 264.3876); --sidebar-primary: oklch(0.4878 0.217 264.3876);
--sidebar-primary-foreground: oklch(1.0000 0 0); --sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.2741 0.0055 286.0329); --sidebar-accent: oklch(0.2741 0.0055 286.0329);
--sidebar-accent-foreground: oklch(0.9676 0.0013 286.3752); --sidebar-accent-foreground: oklch(0.9676 0.0013 286.3752);
--sidebar-border: oklch(0.2741 0.0055 286.0329); --sidebar-border: oklch(0.2741 0.0055 286.0329);
--sidebar-ring: oklch(0.8709 0.0055 286.2853); --sidebar-ring: oklch(0.8709 0.0055 286.2853);
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-sans:
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
--radius: 0.625rem; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --font-mono:
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); "Courier New", monospace;
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); --radius: 0.625rem;
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); --shadow-sm:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl:
0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
} }
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card); --color-card: var(--card);
--color-card-foreground: var(--card-foreground); --color-card-foreground: var(--card-foreground);
--color-popover: var(--popover); --color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground); --color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground); --color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-ring: var(--ring); --color-ring: var(--ring);
--color-chart-1: var(--chart-1); --color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2); --color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3); --color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4); --color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5); --color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar); --color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
--font-sans: var(--font-sans); --font-sans: var(--font-sans);
--font-mono: var(--font-mono); --font-mono: var(--font-mono);
--font-serif: var(--font-serif); --font-serif: var(--font-serif);
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
--shadow-2xs: var(--shadow-2xs); --shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs); --shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm); --shadow-sm: var(--shadow-sm);
--shadow: var(--shadow); --shadow: var(--shadow);
--shadow-md: var(--shadow-md); --shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg); --shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl); --shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl); --shadow-2xl: var(--shadow-2xl);
} }
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }
#app { #app {

View File

@ -1,4 +1,17 @@
import { BriefcaseMedicalIcon, CarIcon, ChefHatIcon, CroissantIcon, DrillIcon, FlameIcon, FuelIcon, HamburgerIcon, PackageIcon, SchoolIcon, SquareParkingIcon, StoreIcon } from "@lucide/svelte"; import {
BriefcaseMedicalIcon,
CarIcon,
ChefHatIcon,
CroissantIcon,
DrillIcon,
FlameIcon,
FuelIcon,
HamburgerIcon,
PackageIcon,
SchoolIcon,
SquareParkingIcon,
StoreIcon,
} from "@lucide/svelte";
import type { Component } from "svelte"; import type { Component } from "svelte";
export const POIIcons: Record<string, Component> = { export const POIIcons: Record<string, Component> = {
@ -14,5 +27,5 @@ export const POIIcons: Record<string, Component> = {
"shop=kiosk": StoreIcon, "shop=kiosk": StoreIcon,
"amenity=restaurant": ChefHatIcon, "amenity=restaurant": ChefHatIcon,
"amenity=fast_food": HamburgerIcon, "amenity=fast_food": HamburgerIcon,
"shop=bakery": CroissantIcon "shop=bakery": CroissantIcon,
}; };

View File

@ -1,8 +1,22 @@
<script lang="ts"> <script lang="ts">
import * as Drawer from "$lib/components/ui/drawer/index.js"; import * as Drawer from "$lib/components/ui/drawer/index.js";
import { BikeIcon, CarIcon, PlusCircleIcon, SaveIcon, TractorIcon, TruckIcon, XIcon } from "@lucide/svelte"; import {
BikeIcon,
CarIcon,
SaveIcon,
TractorIcon,
TruckIcon,
XIcon,
} from "@lucide/svelte";
import Button, { buttonVariants } from "../ui/button/button.svelte"; import Button, { buttonVariants } from "../ui/button/button.svelte";
import { DefaultVehicle, isValidFuel, selectVehicle, setVehicles, vehicles, type Vehicle, type VehicleType } from "$lib/vehicles/vehicles.svelte"; import {
isValidFuel,
selectVehicle,
setVehicles,
vehicles,
type Vehicle,
type VehicleType,
} from "$lib/vehicles/vehicles.svelte";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import * as Select from "../ui/select"; import * as Select from "../ui/select";
import Input from "../ui/input/input.svelte"; import Input from "../ui/input/input.svelte";
@ -36,25 +50,31 @@
actualMaxSpeed: 45, actualMaxSpeed: 45,
emissionClass: "euro_5", emissionClass: "euro_5",
fuelType: "diesel", fuelType: "diesel",
preferredFuel: "Diesel" preferredFuel: "Diesel",
}); });
</script> </script>
<Drawer.Root bind:open={open}> <Drawer.Root bind:open>
<Drawer.Trigger class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}> <Drawer.Trigger
class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}
>
{@render children()} {@render children()}
</Drawer.Trigger> </Drawer.Trigger>
<Drawer.Content> <Drawer.Content>
<Drawer.Header> <Drawer.Header>
<Drawer.Title>Add Vehicle</Drawer.Title> <Drawer.Title>Add Vehicle</Drawer.Title>
</Drawer.Header> </Drawer.Header>
<div class="p-4 pt-0 flex flex-col gap-2"> <div class="p-4 pt-0 flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<Select.Root type="single" bind:value={vehicle.type}> <Select.Root type="single" bind:value={vehicle.type}>
<Select.Trigger class="w-[180px]"> <Select.Trigger class="w-[180px]">
{@const Icon = getVehicleIcon(vehicle.type)} {@const Icon = getVehicleIcon(vehicle.type)}
<Icon /> <Icon />
{vehicle.type === "car" ? "Car" : vehicle.type === "motor_scooter" ? "Moped" : "?"} {vehicle.type === "car"
? "Car"
: vehicle.type === "motor_scooter"
? "Moped"
: "?"}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
<Select.Item value="car"> <Select.Item value="car">
@ -110,7 +130,11 @@
<div class="flex gap-2"> <div class="flex gap-2">
<Select.Root type="single" bind:value={vehicle.fuelType}> <Select.Root type="single" bind:value={vehicle.fuelType}>
<Select.Trigger class="w-full"> <Select.Trigger class="w-full">
{vehicle.fuelType === "diesel" ? "Diesel" : vehicle.fuelType === "petrol" ? "Petrol" : "Electric"} {vehicle.fuelType === "diesel"
? "Diesel"
: vehicle.fuelType === "petrol"
? "Petrol"
: "Electric"}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
<Select.Item value="diesel">Diesel</Select.Item> <Select.Item value="diesel">Diesel</Select.Item>
@ -137,33 +161,38 @@
</div> </div>
</div> </div>
<Drawer.Footer> <Drawer.Footer>
<Button onclick={() => { <Button
open = false; onclick={() => {
if (vehicle.name.trim() === "") { open = false;
alert("Please enter a vehicle name."); if (vehicle.name.trim() === "") {
return; alert("Please enter a vehicle name.");
} return;
if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) { }
alert("Please enter valid speeds."); if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) {
return; alert("Please enter valid speeds.");
} return;
if(!isValidFuel(vehicle)) { }
alert("Please select a valid fuel type and preferred fuel."); if (!isValidFuel(vehicle)) {
return; alert("Please select a valid fuel type and preferred fuel.");
} return;
setVehicles([...vehicles, vehicle]); }
selectVehicle(vehicle); setVehicles([...vehicles, vehicle]);
location.reload(); // TODO selectVehicle(vehicle);
}}> location.reload(); // TODO
}}
>
<SaveIcon /> <SaveIcon />
Save Save
</Button> </Button>
<Button variant="secondary" onclick={() => { <Button
open = false; variant="secondary"
}}> onclick={() => {
open = false;
}}
>
<XIcon /> <XIcon />
Cancel Cancel
</Button> </Button>
</Drawer.Footer> </Drawer.Footer>
</Drawer.Content> </Drawer.Content>
</Drawer.Root> </Drawer.Root>

View File

@ -3,8 +3,8 @@
import * as Select from "../ui/select"; import * as Select from "../ui/select";
</script> </script>
{#each EVConnectors as connector} {#each EVConnectors as connector (connector)}
<Select.Item value={connector}> <Select.Item value={connector}>
{connector} {connector}
</Select.Item> </Select.Item>
{/each} {/each}

View File

@ -6,7 +6,7 @@
import * as Popover from "$lib/components/ui/popover/index.js"; import * as Popover from "$lib/components/ui/popover/index.js";
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";
const frameworks = [ const frameworks = [
{ {
value: "sveltekit", value: "sveltekit",
@ -29,15 +29,13 @@
label: "Astro", label: "Astro",
}, },
]; ];
let open = $state(false); let open = $state(false);
let value = $state(""); let value = $state("");
let triggerRef = $state<HTMLButtonElement>(null!); let triggerRef = $state<HTMLButtonElement>(null!);
const selectedValue = $derived( const selectedValue = $derived(value === "location" ? "My Location" : value);
value === "location" ? "My Location" : value
);
// We want to refocus the trigger button when the user selects // We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the // an item from the list so users can continue navigating the
// rest of the form with the keyboard. // rest of the form with the keyboard.
@ -51,7 +49,7 @@
<Popover.Root bind:open> <Popover.Root bind:open>
<Popover.Trigger bind:ref={triggerRef}> <Popover.Trigger bind:ref={triggerRef}>
{#snippet child({ props }: { props: Record<string, any> })} {#snippet child({ props }: { props: Record<string, unknown> })}
<Button <Button
variant="outline" variant="outline"
class="justify-between" class="justify-between"
@ -66,46 +64,46 @@
</Popover.Trigger> </Popover.Trigger>
<Popover.Content class="w-[200px] p-0"> <Popover.Content class="w-[200px] p-0">
<Command.Root> <Command.Root>
<Command.Input placeholder="Search..." /> <Command.Input placeholder="Search..." />
<Command.List> <Command.List>
<Command.Empty>No location found.</Command.Empty> <Command.Empty>No location found.</Command.Empty>
<Command.Group> <Command.Group>
<Command.Item <Command.Item
value={"location"} value="location"
onSelect={() => { onSelect={() => {
value = "location"; value = "location";
closeAndFocusTrigger(); closeAndFocusTrigger();
}} }}
> >
<CheckIcon <CheckIcon
class={cn( class={cn(
"mr-2 size-4", "mr-2 size-4",
value !== "location" && "text-transparent" value !== "location" && "text-transparent",
)} )}
/> />
My Location My Location
</Command.Item> </Command.Item>
</Command.Group> </Command.Group>
<Command.Group> <Command.Group>
{#each frameworks as framework} {#each frameworks as framework (framework.value)}
<Command.Item <Command.Item
value={framework.value} value={framework.value}
onSelect={() => { onSelect={() => {
value = framework.value; value = framework.value;
closeAndFocusTrigger(); closeAndFocusTrigger();
}} }}
> >
<CheckIcon <CheckIcon
class={cn( class={cn(
"mr-2 size-4", "mr-2 size-4",
value !== framework.value && "text-transparent" value !== framework.value && "text-transparent",
)} )}
/> />
{framework.label} {framework.label}
</Command.Item> </Command.Item>
{/each} {/each}
</Command.Group> </Command.Group>
</Command.List> </Command.List>
</Command.Root> </Command.Root>
</Popover.Content> </Popover.Content>
</Popover.Root> </Popover.Root>

View File

@ -6,7 +6,7 @@
let name = $derived(maneuverTypes[maneuver] || "none"); let name = $derived(maneuverTypes[maneuver] || "none");
</script> </script>
<img src="/img/maneuver/{name}.svg" alt={name}> <img src="/img/maneuver/{name}.svg" alt={name} />
<style> <style>
img { img {

View File

@ -2,8 +2,6 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { import {
GeoJSONSource, GeoJSONSource,
GeolocateControl,
Hash,
LineLayer, LineLayer,
MapLibre, MapLibre,
Marker, Marker,
@ -11,13 +9,7 @@
} from "svelte-maplibre-gl"; } from "svelte-maplibre-gl";
import { view } from "./sidebar.svelte"; import { view } from "./sidebar.svelte";
import { map, pin } from "./map.svelte"; import { map, pin } from "./map.svelte";
import { import { routing } from "$lib/services/navigation/routing.svelte";
drawAllRoutes,
fetchRoute,
routing,
} from "$lib/services/navigation/routing.svelte";
import { createValhallaRequest } from "$lib/vehicles/ValhallaVehicles";
import { ROUTING_SERVER } from "$lib/services/hosts";
import { location } from "./location.svelte"; import { location } from "./location.svelte";
onMount(() => { onMount(() => {
@ -33,7 +25,9 @@
scheme="tiles" scheme="tiles"
loadFn={async (params) => { loadFn={async (params) => {
console.log(params.url); console.log(params.url);
const url = params.url.replace("tiles://", "").replace("tiles.openfreemap.org/", ""); const url = params.url
.replace("tiles://", "")
.replace("tiles.openfreemap.org/", "");
const path = url.split("/")[0]; const path = url.split("/")[0];
if (path == "natural_earth") { if (path == "natural_earth") {
const t = await fetch("https://tiles.openfreemap.org/" + url); const t = await fetch("https://tiles.openfreemap.org/" + url);
@ -73,7 +67,7 @@
} }
}} }}
onmove={(e) => { onmove={(e) => {
// @ts-ignore // @ts-expect-error - not typed
if (e.reason !== "location") { if (e.reason !== "location") {
location.locked = false; location.locked = false;
} }
@ -191,7 +185,10 @@
{#if location.available} {#if location.available}
<div class="maplibregl-user-location-dot" bind:this={locationDot}></div> <div class="maplibregl-user-location-dot" bind:this={locationDot}></div>
<div class="maplibregl-user-location-accuracy-circle" bind:this={locationAccuracyCircle}></div> <div
class="maplibregl-user-location-accuracy-circle"
bind:this={locationAccuracyCircle}
></div>
<Marker <Marker
lnglat={{ lat: location.lat, lng: location.lng }} lnglat={{ lat: location.lat, lng: location.lng }}
element={locationDot} element={locationDot}

View File

@ -2,7 +2,10 @@
import { hasCapability, type Capabilities } from "$lib/services/lnv"; import { hasCapability, type Capabilities } from "$lib/services/lnv";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
let { capability, children }: { let {
capability,
children,
}: {
capability: Capabilities[number]; capability: Capabilities[number];
children: Snippet; children: Snippet;
} = $props(); } = $props();
@ -12,6 +15,6 @@
{#if has} {#if has}
{@render children()} {@render children()}
{/if} {/if}
{:catch error} {:catch _error}
<!-- user is likely offline --> <!-- user is likely offline -->
{/await} {/await}

View File

@ -1,37 +1,52 @@
<script lang="ts"> <script lang="ts">
import LanesDisplay from "$lib/services/navigation/LanesDisplay.svelte"; import LanesDisplay from "$lib/services/navigation/LanesDisplay.svelte";
import { decodePolyline, routing } from "$lib/services/navigation/routing.svelte"; import {
decodePolyline,
routing,
} from "$lib/services/navigation/routing.svelte";
import { location } from "./location.svelte"; import { location } from "./location.svelte";
import ManeuverIcon from "./ManeuverIcon.svelte"; import ManeuverIcon from "./ManeuverIcon.svelte";
// Helper: Haversine distance in meters // Helper: Haversine distance in meters
function haversine(a: { lat: number; lon: number }, b: { lat: number; lon: number }) { function haversine(
a: { lat: number; lon: number },
b: { lat: number; lon: number },
) {
const R = 6371000; const R = 6371000;
const toRad = (d: number) => d * Math.PI / 180; const toRad = (d: number) => (d * Math.PI) / 180;
const dLat = toRad(b.lat - a.lat); const dLat = toRad(b.lat - a.lat);
const dLon = toRad(b.lon - a.lon); const dLon = toRad(b.lon - a.lon);
const lat1 = toRad(a.lat); const lat1 = toRad(a.lat);
const lat2 = toRad(b.lat); const lat2 = toRad(b.lat);
const aVal = Math.sin(dLat / 2) ** 2 + const aVal =
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2; Math.sin(dLat / 2) ** 2 +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.atan2(Math.sqrt(aVal), Math.sqrt(1 - aVal)); return 2 * R * Math.atan2(Math.sqrt(aVal), Math.sqrt(1 - aVal));
} }
// Helper: Project point onto segment AB, return projected point and distance along segment // Helper: Project point onto segment AB, return projected point and distance along segment
function projectPointToSegment(p: WorldLocation, a: WorldLocation, b: WorldLocation) { function projectPointToSegment(
const toRad = (deg: number) => deg * Math.PI / 180; p: WorldLocation,
const toDeg = (rad: number) => rad * 180 / Math.PI; a: WorldLocation,
b: WorldLocation,
) {
const toRad = (deg: number) => (deg * Math.PI) / 180;
const toDeg = (rad: number) => (rad * 180) / Math.PI;
const lat1 = toRad(a.lat), lon1 = toRad(a.lon); const lat1 = toRad(a.lat),
const lat2 = toRad(b.lat), lon2 = toRad(b.lon); lon1 = toRad(a.lon);
const lat3 = toRad(p.lat), lon3 = toRad(p.lon); const lat2 = toRad(b.lat),
lon2 = toRad(b.lon);
const lat3 = toRad(p.lat),
lon3 = toRad(p.lon);
const dLon = lon2 - lon1; const dLon = lon2 - lon1;
const dLat = lat2 - lat1; const dLat = lat2 - lat1;
const t = ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) / const t =
(dLat * dLat + dLon * dLon); ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
(dLat * dLat + dLon * dLon);
// Clamp to [0,1] // Clamp to [0,1]
const clampedT = Math.max(0, Math.min(1, t)); const clampedT = Math.max(0, Math.min(1, t));
@ -49,11 +64,13 @@
// const point = $derived(decodePolyline(routing.currentTrip?.legs[0].shape || "")[routing.currentTripInfo.currentManeuver?.end_shape_index || 0]); // const point = $derived(decodePolyline(routing.currentTrip?.legs[0].shape || "")[routing.currentTripInfo.currentManeuver?.end_shape_index || 0]);
// const distance = $derived(Math.sqrt(Math.pow(point.lat - location.lat, 2) + Math.pow(point.lon - location.lng, 2)) * 111000); // Approximate conversion to meters // const distance = $derived(Math.sqrt(Math.pow(point.lat - location.lat, 2) + Math.pow(point.lon - location.lng, 2)) * 111000); // Approximate conversion to meters
const shape = $derived(decodePolyline(routing.currentTrip?.legs[0].shape || "")); const shape = $derived(
decodePolyline(routing.currentTrip?.legs[0].shape || ""),
);
const maneuver = $derived(routing.currentTripInfo.currentManeuver); const maneuver = $derived(routing.currentTripInfo.currentManeuver);
const distance = $derived.by(() => { const distance = $derived.by(() => {
const lat = location.lat; const lat = location.lat;
const lon = location.lng; const lon = location.lng;
if (!shape || shape.length === 0 || !maneuver) return 0; if (!shape || shape.length === 0 || !maneuver) return 0;
const start = shape[maneuver.begin_shape_index]; const start = shape[maneuver.begin_shape_index];
@ -61,7 +78,7 @@
if (!start || !end) return 0; if (!start || !end) return 0;
const projected = projectPointToSegment({ lat, lon }, start, end); const projected = projectPointToSegment({ lat, lon }, start, end);
return projected.distToUser; return projected.distToUser;
}); });
const roundDistance = $derived.by(() => { const roundDistance = $derived.by(() => {
const dist = Math.round(distance); const dist = Math.round(distance);
@ -76,24 +93,27 @@
} else { } else {
return Math.round(dist / 5000) * 5000; return Math.round(dist / 5000) * 5000;
} }
}) });
const distanceText = $derived.by(() => { const distanceText = $derived.by(() => {
const dist = roundDistance; const dist = roundDistance;
if (dist < 1000) return `${dist} m`; if (dist < 1000) return `${dist} m`;
return `${(dist / 1000)} km`; return `${dist / 1000} km`;
}) });
</script> </script>
<div class="fixed top-4 left-4 z-50 w-[calc(100%-32px)] bg-background/60 text-white rounded-lg overflow-hidden" style="backdrop-filter: blur(5px);"> <div
class="fixed top-4 left-4 z-50 w-[calc(100%-32px)] bg-background/60 text-white rounded-lg overflow-hidden"
style="backdrop-filter: blur(5px);"
>
<div class="p-2 flex gap-2"> <div class="p-2 flex gap-2">
<ManeuverIcon maneuver={routing.currentTripInfo.currentManeuver?.type ?? 0} /> <ManeuverIcon
maneuver={routing.currentTripInfo.currentManeuver?.type ?? 0}
/>
<div class="flex gap-1 flex-col"> <div class="flex gap-1 flex-col">
<span class="text-xl font-bold">{distanceText}</span> <span class="text-xl font-bold">{distanceText}</span>
<span>{routing.currentTripInfo.currentManeuver?.instruction}</span> <span>{routing.currentTripInfo.currentManeuver?.instruction}</span>
</div> </div>
</div> </div>
<LanesDisplay <LanesDisplay lanes={routing.currentTripInfo.currentManeuver?.lanes} />
lanes={routing.currentTripInfo.currentManeuver?.lanes} </div>
/>
</div>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onMount, type Component } from "svelte"; import { type Component } from "svelte";
import InvalidSidebar from "./sidebar/InvalidSidebar.svelte"; import InvalidSidebar from "./sidebar/InvalidSidebar.svelte";
import { searchbar, view } from "./sidebar.svelte"; import { searchbar, view } from "./sidebar.svelte";
import MainSidebar from "./sidebar/MainSidebar.svelte"; import MainSidebar from "./sidebar/MainSidebar.svelte";
@ -8,18 +8,28 @@
import { map } from "./map.svelte"; import { map } from "./map.svelte";
import TripSidebar from "./sidebar/TripSidebar.svelte"; import TripSidebar from "./sidebar/TripSidebar.svelte";
import Input from "../ui/input/input.svelte"; import Input from "../ui/input/input.svelte";
import { EllipsisIcon, HomeIcon, SettingsIcon, UserIcon } from "@lucide/svelte"; import {
EllipsisIcon,
HomeIcon,
SettingsIcon,
UserIcon,
} from "@lucide/svelte";
import Button from "../ui/button/button.svelte"; import Button from "../ui/button/button.svelte";
import { search, type Feature } from "$lib/services/Search"; import { search, type Feature } from "$lib/services/Search";
import SearchSidebar from "./sidebar/SearchSidebar.svelte"; import SearchSidebar from "./sidebar/SearchSidebar.svelte";
import RequiresCapability from "./RequiresCapability.svelte"; import RequiresCapability from "./RequiresCapability.svelte";
import UserSidebar from "./sidebar/UserSidebar.svelte"; import UserSidebar from "./sidebar/UserSidebar.svelte";
import { advertiseRemoteLocation, location, remoteLocation } from "./location.svelte"; import {
advertiseRemoteLocation,
location,
remoteLocation,
} from "./location.svelte";
import * as Popover from "../ui/popover"; import * as Popover from "../ui/popover";
import { routing } from "$lib/services/navigation/routing.svelte"; import { routing } from "$lib/services/navigation/routing.svelte";
import InRouteSidebar from "./sidebar/InRouteSidebar.svelte"; import InRouteSidebar from "./sidebar/InRouteSidebar.svelte";
const views: {[key: string]: Component<any>} = { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const views: Record<string, Component<any>> = {
main: MainSidebar, main: MainSidebar,
info: InfoSidebar, info: InfoSidebar,
route: RouteSidebar, route: RouteSidebar,
@ -41,12 +51,14 @@
$effect(() => { $effect(() => {
const newValue = getter(); // read here to subscribe to it const newValue = getter(); // read here to subscribe to it
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(() => value = newValue, delay); timer = setTimeout(() => (value = newValue), delay);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}); });
return () => value; return () => value;
} }
// TODO: implement loading state
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let loading = $state(false); let loading = $state(false);
let searchText = $derived.by(debounce(() => searchbar.text, 300)); let searchText = $derived.by(debounce(() => searchbar.text, 300));
@ -54,20 +66,20 @@
let mobileView = $derived(window.innerWidth < 768 || routing.currentTrip); let mobileView = $derived(window.innerWidth < 768 || routing.currentTrip);
$effect(() => { $effect(() => {
if(!searchText) { if (!searchText) {
searchResults = []; searchResults = [];
if(view.current.type == "search") view.switch("main"); if (view.current.type == "search") view.switch("main");
return; return;
} }
if (searchText.length > 0) { if (searchText.length > 0) {
loading = true; loading = true;
search(searchText, 0, 0).then(results => { search(searchText, 0, 0).then((results) => {
searchResults = results; searchResults = results;
loading = false; loading = false;
view.switch("search", { view.switch("search", {
results: searchResults, results: searchResults,
query: searchText query: searchText,
}) });
}); });
} else { } else {
searchResults = []; searchResults = [];
@ -77,37 +89,48 @@
{#if !routing.currentTrip} {#if !routing.currentTrip}
<div id="floating-search" class={mobileView ? "mobileView" : ""}> <div id="floating-search" class={mobileView ? "mobileView" : ""}>
<Input class="h-10" <Input class="h-10" placeholder="Search..." bind:value={searchbar.text} />
placeholder="Search..." bind:value={searchbar.text} />
</div> </div>
{/if} {/if}
<div id="sidebar" class={mobileView ? "mobileView" : ""} style={(mobileView ? `height: ${sidebarHeight}px;` : "") + (routing.currentTrip ? "bottom: 0 !important;" : "")}> <div
id="sidebar"
class={mobileView ? "mobileView" : ""}
style={(mobileView ? `height: ${sidebarHeight}px;` : "") +
(routing.currentTrip ? "bottom: 0 !important;" : "")}
>
{#if mobileView} {#if mobileView}
<!-- svelte-ignore a11y_no_static_element_interactions --> <div
<!-- svelte-ignore a11y_interactive_supports_focus --> role="button"
<div role="button" id="grabber" style="height: 10px; cursor: grab; display: flex; justify-content: center; align-items: center; touch-action: none;" ontouchstart={(e) => { id="grabber"
isDragging = true; style="height: 10px; cursor: grab; display: flex; justify-content: center; align-items: center; touch-action: none;"
startY = e.touches[0].clientY; ontouchstart={(e) => {
startHeight = sidebarHeight; isDragging = true;
}} ontouchmove={(e) => { startY = e.touches[0].clientY;
if(!isDragging) return; startHeight = sidebarHeight;
e.preventDefault(); }}
const deltaY = e.touches[0].clientY - startY; ontouchmove={(e) => {
let newHeight = Math.max(100, startHeight - deltaY); if (!isDragging) return;
e.preventDefault();
const deltaY = e.touches[0].clientY - startY;
let newHeight = Math.max(100, startHeight - deltaY);
const snapPoint = 200; const snapPoint = 200;
const snapThreshold = 20; const snapThreshold = 20;
if (Math.abs(newHeight - snapPoint) < snapThreshold) { if (Math.abs(newHeight - snapPoint) < snapThreshold) {
newHeight = snapPoint; newHeight = snapPoint;
} }
sidebarHeight = newHeight; sidebarHeight = newHeight;
map.updateMapPadding(); map.updateMapPadding();
}} ontouchend={() => { }}
if(!isDragging) return; ontouchend={() => {
isDragging = false; if (!isDragging) return;
}}> isDragging = false;
<div style="height: 8px; background-color: #acacac; width: 40%; border-radius: 15px;"></div> }}
>
<div
style="height: 8px; background-color: #acacac; width: 40%; border-radius: 15px;"
></div>
</div> </div>
{/if} {/if}
@ -123,9 +146,11 @@
<HomeIcon /> <HomeIcon />
</button> </button>
<RequiresCapability capability="auth"> <RequiresCapability capability="auth">
<button onclick={async () => { <button
view.switch("user"); onclick={async () => {
}}> view.switch("user");
}}
>
<UserIcon /> <UserIcon />
</button> </button>
</RequiresCapability> </RequiresCapability>
@ -145,22 +170,31 @@
</Popover.Trigger> </Popover.Trigger>
<Popover.Content> <Popover.Content>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Button variant="outline" onclick={() => { <Button
location.toggleLock(); variant="outline"
}}> onclick={() => {
location.toggleLock();
}}
>
{location.locked ? "Unlock Location" : "Lock Location"} {location.locked ? "Unlock Location" : "Lock Location"}
</Button> </Button>
{#if location.code} {#if location.code}
<span>Advertise code: {location.code}</span> <span>Advertise code: {location.code}</span>
{/if} {/if}
<Button variant="outline" onclick={() => { <Button
advertiseRemoteLocation(); variant="outline"
}}> onclick={() => {
advertiseRemoteLocation();
}}
>
Advertise Location Advertise Location
</Button> </Button>
<Button variant="outline" onclick={() => { <Button
remoteLocation(prompt("Code?") || ""); variant="outline"
}}> onclick={() => {
remoteLocation(prompt("Code?") || "");
}}
>
Join Remote Location Join Remote Location
</Button> </Button>
</div> </div>
@ -255,4 +289,4 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
</style> </style>

View File

@ -1,8 +1,20 @@
<script lang="ts"> <script lang="ts">
import * as Drawer from "$lib/components/ui/drawer/index.js"; import * as Drawer from "$lib/components/ui/drawer/index.js";
import { BikeIcon, CarIcon, PlusCircleIcon, TractorIcon, TruckIcon } from "@lucide/svelte"; import {
BikeIcon,
CarIcon,
PlusCircleIcon,
TractorIcon,
TruckIcon,
} from "@lucide/svelte";
import Button, { buttonVariants } from "../ui/button/button.svelte"; import Button, { buttonVariants } from "../ui/button/button.svelte";
import { DefaultVehicle, selectedVehicle, selectVehicle, vehicles, type VehicleType } from "$lib/vehicles/vehicles.svelte"; import {
DefaultVehicle,
selectedVehicle,
selectVehicle,
vehicles,
type VehicleType,
} from "$lib/vehicles/vehicles.svelte";
import AddVehicleDrawer from "./AddVehicleDrawer.svelte"; import AddVehicleDrawer from "./AddVehicleDrawer.svelte";
let open = $state(false); let open = $state(false);
@ -24,28 +36,39 @@
} }
} }
</script> </script>
<Drawer.Root bind:open={open}> <Drawer.Root bind:open>
<Drawer.Trigger class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}> <Drawer.Trigger
class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}
>
{@const vehicle = selectedVehicle() ?? DefaultVehicle} {@const vehicle = selectedVehicle() ?? DefaultVehicle}
{@const Icon = getVehicleIcon(vehicle.type)} {@const Icon = getVehicleIcon(vehicle.type)}
<Icon /> <Icon />
{vehicle.name} {vehicle.name}
</Drawer.Trigger> </Drawer.Trigger>
<Drawer.Content> <Drawer.Content>
<Drawer.Header> <Drawer.Header>
<Drawer.Title>Vehicle Selector</Drawer.Title> <Drawer.Title>Vehicle Selector</Drawer.Title>
<Drawer.Description>Select your vehicle to customize routing just for you.</Drawer.Description> <Drawer.Description
</Drawer.Header> >Select your vehicle to customize routing just for you.</Drawer.Description
>
</Drawer.Header>
<div class="p-4 pt-0 flex flex-col gap-2"> <div class="p-4 pt-0 flex flex-col gap-2">
{#each vehicles as vehicle} {#each vehicles as vehicle (vehicle.name)}
<Button variant={selectedVehicle() === vehicle ? "default" : "secondary"} class="w-full p-5" onclick={() => {selectVehicle(vehicle); open = false;}}> <Button
variant={selectedVehicle() === vehicle ? "default" : "secondary"}
class="w-full p-5"
onclick={() => {
selectVehicle(vehicle);
open = false;
}}
>
{@const Icon = getVehicleIcon(vehicle.type)} {@const Icon = getVehicleIcon(vehicle.type)}
<Icon /> <Icon />
{vehicle.name} {vehicle.name}
</Button> </Button>
{/each} {/each}
<AddVehicleDrawer> <AddVehicleDrawer>
<Button variant="secondary" class="w-full p-5"> <Button variant="secondary" class="w-full p-5">
<PlusCircleIcon /> <PlusCircleIcon />
@ -53,5 +76,5 @@
</Button> </Button>
</AddVehicleDrawer> </AddVehicleDrawer>
</div> </div>
</Drawer.Content> </Drawer.Content>
</Drawer.Root> </Drawer.Root>

View File

@ -1,13 +1,13 @@
<script> <script>
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
import { getStations } from "$lib/services/MTSK"; import { getStations } from "$lib/services/MTSK";
let { tags, lat, lng } = $props(); let { tags, lat, lng } = $props();
</script> </script>
<h3 class="text-lg font-bold mt-2">Fuel Types</h3> <h3 class="text-lg font-bold mt-2">Fuel Types</h3>
<ul class="flex gap-2 flex-wrap"> <ul class="flex gap-2 flex-wrap">
{#each Object.entries(tags).filter(([key]) => key.startsWith("fuel:")) as [key, tag]} {#each Object.entries(tags).filter( ([key]) => key.startsWith("fuel:"), ) as [key, tag] (key)}
<!-- <li>{key.replace("fuel:", "")}: {tag}</li> --> <!-- <li>{key.replace("fuel:", "")}: {tag}</li> -->
<Badge> <Badge>
{key.replace("fuel:", "")} {key.replace("fuel:", "")}

View File

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import Input from "$lib/components/ui/input/input.svelte"; import Input from "$lib/components/ui/input/input.svelte";
import { LNV_SERVER } from "$lib/services/hosts";
import { ai } from "$lib/services/lnv"; import { ai } from "$lib/services/lnv";
import { SparklesIcon } from "@lucide/svelte"; import { SparklesIcon } from "@lucide/svelte";
let { lat, lon } = $props(); let { lat, lon } = $props();
let question = $state(""); let question = $state("");
@ -12,7 +11,7 @@
const chunks = res.split("\n"); const chunks = res.split("\n");
let text = ""; let text = "";
for (const chunk of chunks) { for (const chunk of chunks) {
if(chunk.startsWith("0:")) { if (chunk.startsWith("0:")) {
text += JSON.parse(chunk.substring(2).trim()); text += JSON.parse(chunk.substring(2).trim());
} }
} }
@ -33,9 +32,11 @@
{/await} {/await}
<Input <Input
type="text" type="text"
value={""} value=""
placeholder="Ask a question about this place..." onchange={(e) => { placeholder="Ask a question about this place..."
question = (e.target! as any).value; onchange={(e) => {
}} /> question = (e.target! as HTMLInputElement).value;
}}
/>
</div> </div>
</div> </div>

View File

@ -2,16 +2,19 @@
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
import opening_hours from "opening_hours"; import opening_hours from "opening_hours";
let { hours, lat, lon }: { hours: string, lat: number, lon: number } = $props(); let { hours, lat, lon }: { hours: string; lat: number; lon: number } =
$props();
const oh = $derived.by(() => { const oh = $derived.by(() => {
return new opening_hours(hours, { return new opening_hours(hours, {
lat, lon, address: { lat,
lon,
address: {
country_code: "de", // Default to Germany, can be overridden if needed country_code: "de", // Default to Germany, can be overridden if needed
state: "NRW", // Default to North Rhine-Westphalia, can be overridden if needed state: "NRW", // Default to North Rhine-Westphalia, can be overridden if needed
} },
}); });
}) });
</script> </script>
<h3 class="text-lg font-bold mt-2"> <h3 class="text-lg font-bold mt-2">
@ -24,4 +27,4 @@
</h3> </h3>
<p>{hours}</p> <p>{hours}</p>
<!-- todo --> <!-- todo -->

View File

@ -4,14 +4,14 @@
import { getReviews, postReview } from "$lib/services/lnv"; import { getReviews, postReview } from "$lib/services/lnv";
import Stars from "./Stars.svelte"; import Stars from "./Stars.svelte";
let { lat, lng }: { lat: number, lng: number } = $props(); let { lat, lng }: { lat: number; lng: number } = $props();
</script> </script>
<h3 class="text-lg font-bold mt-2">Reviews</h3> <h3 class="text-lg font-bold mt-2">Reviews</h3>
{#await getReviews({lat, lon: lng}) then reviews} {#await getReviews({ lat, lon: lng }) then reviews}
{#if reviews.length > 0} {#if reviews.length > 0}
<ul class="list-disc pl-5"> <ul class="list-disc pl-5">
{#each reviews as review} {#each reviews as review (review)}
<li class="flex justify-center gap-2 mb-2 flex-col"> <li class="flex justify-center gap-2 mb-2 flex-col">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Avatar.Root> <Avatar.Root>
@ -27,20 +27,27 @@
{:else} {:else}
<p>No reviews available.</p> <p>No reviews available.</p>
{/if} {/if}
<Button variant="secondary" onclick={async () => { <Button
const rating = prompt("Enter your rating (1-5):"); variant="secondary"
const comment = prompt("Enter your review comment:"); onclick={async () => {
if (rating && comment) { const rating = prompt("Enter your rating (1-5):");
console.log(`Rating: ${rating}, Comment: ${comment}`); const comment = prompt("Enter your review comment:");
await postReview({ lat, lon: lng }, { if (rating && comment) {
rating: parseInt(rating, 10), console.log(`Rating: ${rating}, Comment: ${comment}`);
comment await postReview(
}) { lat, lon: lng },
alert("Thank you for your review!"); {
} else { rating: parseInt(rating, 10),
alert("Review submission cancelled."); comment,
} },
}} disabled>Write a review</Button><br> );
alert("Thank you for your review!");
} else {
alert("Review submission cancelled.");
}
}}
disabled>Write a review</Button
><br />
{:catch error} {:catch error}
<p>Error loading reviews: {error.message}</p> <p>Error loading reviews: {error.message}</p>
{/await} {/await}

View File

@ -40,4 +40,4 @@
<StarIcon class="fill-white" /> <StarIcon class="fill-white" />
<StarIcon class="fill-white" /> <StarIcon class="fill-white" />
<StarIcon class="fill-white" /> <StarIcon class="fill-white" />
{/if} {/if}

View File

@ -1,6 +1,6 @@
import { LNV_SERVER } from "$lib/services/hosts" import { LNV_SERVER } from "$lib/services/hosts";
import { routing } from "$lib/services/navigation/routing.svelte" import { routing } from "$lib/services/navigation/routing.svelte";
import { map } from "./map.svelte" import { map } from "./map.svelte";
export const location = $state({ export const location = $state({
available: false, available: false,
@ -12,100 +12,117 @@ export const location = $state({
provider: "gps" as "gps" | "remote" | "simulated", provider: "gps" as "gps" | "remote" | "simulated",
locked: true, locked: true,
toggleLock: () => { toggleLock: () => {
location.locked = !location.locked location.locked = !location.locked;
console.log("Location lock toggled:", location.locked) console.log("Location lock toggled:", location.locked);
if (location.locked) { if (location.locked) {
map.value?.flyTo({ map.value?.flyTo(
center: [location.lng, location.lat], {
zoom: 16,
duration: 1000,
// bearing: location.heading !== null ? location.heading : undefined
}, {
reason: "location"
})
}
},
advertiser: null as WebSocket | null,
code: null as string | null,
lastUpdate: null as Date | null
})
export function watchLocation() {
if(navigator.geolocation) {
navigator.geolocation.watchPosition((pos) => {
if(location.provider !== "gps") return;
// console.log("Geolocation update:", pos)
location.lat = pos.coords.latitude
location.lng = pos.coords.longitude
location.accuracy = pos.coords.accuracy
location.speed = pos.coords.speed || 0
location.available = true
location.heading = pos.coords.heading
location.lastUpdate = new Date()
if (location.locked) {
map.value?.flyTo({
center: [location.lng, location.lat], center: [location.lng, location.lat],
zoom: 16, zoom: 16,
duration: 1000, duration: 1000,
// bearing: location.heading !== null ? location.heading : undefined // bearing: location.heading !== null ? location.heading : undefined
}, { },
reason: "location" {
}) reason: "location",
} },
);
}
},
advertiser: null as WebSocket | null,
code: null as string | null,
lastUpdate: null as Date | null,
});
// console.log(location.advertiser); export function watchLocation() {
if (navigator.geolocation) {
if (location.advertiser) { navigator.geolocation.watchPosition(
location.advertiser.send(JSON.stringify({ (pos) => {
type: "location", if (location.provider !== "gps") return;
location: { // console.log("Geolocation update:", pos)
lat: location.lat, location.lat = pos.coords.latitude;
lng: location.lng, location.lng = pos.coords.longitude;
accuracy: location.accuracy, location.accuracy = pos.coords.accuracy;
speed: location.speed, location.speed = pos.coords.speed || 0;
heading: location.heading location.available = true;
}, location.heading = pos.coords.heading;
route: { location.lastUpdate = new Date();
trip: routing.currentTrip,
info: routing.currentTripInfo, if (location.locked) {
geojson: routing.geojson map.value?.flyTo(
} {
})) center: [location.lng, location.lat],
} zoom: 16,
}, (err) => { duration: 1000,
console.error("Geolocation error:", err) // bearing: location.heading !== null ? location.heading : undefined
}, { },
enableHighAccuracy: true {
}) reason: "location",
},
);
}
// console.log(location.advertiser);
if (location.advertiser) {
location.advertiser.send(
JSON.stringify({
type: "location",
location: {
lat: location.lat,
lng: location.lng,
accuracy: location.accuracy,
speed: location.speed,
heading: location.heading,
},
route: {
trip: routing.currentTrip,
info: routing.currentTripInfo,
geojson: routing.geojson,
},
}),
);
}
},
(err) => {
console.error("Geolocation error:", err);
},
{
enableHighAccuracy: true,
},
);
} }
} }
let checkRunning = false; let checkRunning = false;
if(!checkRunning) { if (!checkRunning) {
setInterval(() => { setInterval(() => {
checkRunning = true; checkRunning = true;
if(location.provider !== "gps") return; if (location.provider !== "gps") return;
// If the last update was more than 5 seconds ago, recall watchPosition // If the last update was more than 5 seconds ago, recall watchPosition
// console.log("Checking location update status") // console.log("Checking location update status")
if (location.lastUpdate && (new Date().getTime() - location.lastUpdate.getTime()) > 10000) { if (
console.warn("Location update is stale, rewatching position") location.lastUpdate &&
new Date().getTime() - location.lastUpdate.getTime() > 10000
) {
console.warn("Location update is stale, rewatching position");
watchLocation(); watchLocation();
} }
}, 1000) }, 1000);
checkRunning = true; checkRunning = true;
} }
watchLocation() watchLocation();
export function advertiseRemoteLocation(code?: string) { export function advertiseRemoteLocation(code?: string) {
const ws = new WebSocket( const ws = new WebSocket(
`${LNV_SERVER.replace("https", "wss").replace("http", "ws")}/ws` `${LNV_SERVER.replace("https", "wss").replace("http", "ws")}/ws`,
); );
ws.addEventListener("open", () => { ws.addEventListener("open", () => {
console.log("WebSocket connection established for remote location advertisement") console.log(
ws.send(JSON.stringify({ type: "advertise", code })) "WebSocket connection established for remote location advertisement",
);
ws.send(JSON.stringify({ type: "advertise", code }));
}); });
ws.addEventListener("message", (event) => { ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
@ -127,40 +144,43 @@ export function remoteLocation(code: string) {
// Open websocket connection // Open websocket connection
// Use LNV_SERVER, change to ws or wss based on protocol // Use LNV_SERVER, change to ws or wss based on protocol
const ws = new WebSocket( const ws = new WebSocket(
`${LNV_SERVER.replace("https", "wss").replace("http", "ws")}/ws` `${LNV_SERVER.replace("https", "wss").replace("http", "ws")}/ws`,
); );
ws.addEventListener("open", () => { ws.addEventListener("open", () => {
console.log("WebSocket connection established for remote location") console.log("WebSocket connection established for remote location");
ws.send(JSON.stringify({ type: "subscribe", code })) ws.send(JSON.stringify({ type: "subscribe", code }));
location.provider = "remote" location.provider = "remote";
location.code = code location.code = code;
}) });
ws.addEventListener("message", (event) => { ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data) const data = JSON.parse(event.data);
if (data.type === "location") { if (data.type === "location") {
console.log("Remote location update:", data.location) console.log("Remote location update:", data.location);
location.lat = data.location.lat location.lat = data.location.lat;
location.lng = data.location.lng location.lng = data.location.lng;
location.accuracy = data.location.accuracy location.accuracy = data.location.accuracy;
location.speed = data.location.speed || 0 location.speed = data.location.speed || 0;
location.available = true location.available = true;
location.heading = data.location.heading || null location.heading = data.location.heading || null;
routing.currentTrip = data.route.trip || null routing.currentTrip = data.route.trip || null;
routing.currentTripInfo = data.route.info || null routing.currentTripInfo = data.route.info || null;
routing.geojson = data.route.geojson || null routing.geojson = data.route.geojson || null;
if (location.locked) { if (location.locked) {
map.value?.flyTo({ map.value?.flyTo(
center: [location.lng, location.lat], {
zoom: 16, center: [location.lng, location.lat],
duration: 1000, zoom: 16,
// bearing: location.heading !== null ? location.heading : undefined duration: 1000,
}, { // bearing: location.heading !== null ? location.heading : undefined
reason: "location" },
}) {
reason: "location",
},
);
} }
} }
}) });
} }
// setInterval(() => { // setInterval(() => {

View File

@ -1,5 +1,4 @@
import { routing } from "$lib/services/navigation/routing.svelte"; import { routing } from "$lib/services/navigation/routing.svelte";
import { reverseGeocode } from "$lib/services/Search";
import { view } from "./sidebar.svelte"; import { view } from "./sidebar.svelte";
// export const geolocate = $state({ // export const geolocate = $state({
@ -9,27 +8,31 @@ import { view } from "./sidebar.svelte";
export const map = $state({ export const map = $state({
value: undefined as maplibregl.Map | undefined, value: undefined as maplibregl.Map | undefined,
updateMapPadding: () => { updateMapPadding: () => {
if(document.querySelector<HTMLDivElement>("#sidebar") == null) { if (document.querySelector<HTMLDivElement>("#sidebar") == null) {
map._setPadding({ map._setPadding({
top: 0, top: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
left: 0 left: 0,
}); });
return; return;
} }
console.log("Updating map padding"); console.log("Updating map padding");
if (window.innerWidth < 768 || routing.currentTrip) { if (window.innerWidth < 768 || routing.currentTrip) {
const calculatedSidebarHeight = document.querySelector<HTMLDivElement>("#sidebar")!.getBoundingClientRect().height; const calculatedSidebarHeight = document
.querySelector<HTMLDivElement>("#sidebar")!
.getBoundingClientRect().height;
map._setPadding({ map._setPadding({
top: routing.currentTrip ? 64 : 0, top: routing.currentTrip ? 64 : 0,
right: 0, right: 0,
bottom: calculatedSidebarHeight, bottom: calculatedSidebarHeight,
left: 0 left: 0,
}); });
return; return;
} }
const calculatedSidebarWidth = document.querySelector<HTMLDivElement>("#sidebar")!.getBoundingClientRect().width; const calculatedSidebarWidth = document
.querySelector<HTMLDivElement>("#sidebar")!
.getBoundingClientRect().width;
map._setPadding({ map._setPadding({
top: 0, top: 0,
right: 0, right: 0,
@ -41,14 +44,19 @@ export const map = $state({
top: 0, top: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
left: 0 left: 0,
}, },
_setPadding: (_padding: { top: number, right: number, bottom: number, left: number }) => { _setPadding: (_padding: {
top: number;
right: number;
bottom: number;
left: number;
}) => {
map.padding = _padding; map.padding = _padding;
if (map.value) { if (map.value) {
map.value.setPadding(map.padding); map.value.setPadding(map.padding);
} }
} },
}); });
export const pin = $state({ export const pin = $state({
@ -66,12 +74,12 @@ export const pin = $state({
pin.lng = 0; pin.lng = 0;
}, },
showInfo: async () => { showInfo: async () => {
if(!pin.isDropped) return; if (!pin.isDropped) return;
// const res = await reverseGeocode({ lat: pin.lat, lon: pin.lng }); // const res = await reverseGeocode({ lat: pin.lat, lon: pin.lng });
// if(res.length > 0) { // if(res.length > 0) {
// const feature = res[0]; // const feature = res[0];
// view.switch("info", { feature }); // view.switch("info", { feature });
// } // }
view.switch("info", { lat: pin.lat, lng: pin.lng }); view.switch("info", { lat: pin.lat, lng: pin.lng });
} },
}) });

View File

@ -1,6 +1,6 @@
export type View = { export interface View {
type: string; type: string;
props?: Record<string, any>; props?: Record<string, unknown>;
} }
export const view = $state({ export const view = $state({
@ -13,14 +13,14 @@ export const view = $state({
view.current = { type: "main" } as View; // Reset to main view if history is empty view.current = { type: "main" } as View; // Reset to main view if history is empty
} }
}, },
switch: (to: string, props?: Record<string, any>) => { switch: (to: string, props?: Record<string, unknown>) => {
if (view.current.type !== to) { if (view.current.type !== to) {
view.history.push(view.current); view.history.push(view.current);
} }
view.current = { type: to, props } as View; view.current = { type: to, props } as View;
} },
}); });
export const searchbar = $state({ export const searchbar = $state({
text: "" text: "",
}) });

View File

@ -1,36 +1,52 @@
<script lang="ts"> <script lang="ts">
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { decodePolyline, routing, stopNavigation } from "$lib/services/navigation/routing.svelte"; import {
import { advertiseRemoteLocation, location } from "../location.svelte"; decodePolyline,
routing,
stopNavigation,
} from "$lib/services/navigation/routing.svelte";
import { advertiseRemoteLocation, location } from "../location.svelte";
// Helper: Haversine distance in meters // Helper: Haversine distance in meters
function haversine(a: { lat: number; lon: number }, b: { lat: number; lon: number }) { function haversine(
a: { lat: number; lon: number },
b: { lat: number; lon: number },
) {
const R = 6371000; const R = 6371000;
const toRad = (d: number) => d * Math.PI / 180; const toRad = (d: number) => (d * Math.PI) / 180;
const dLat = toRad(b.lat - a.lat); const dLat = toRad(b.lat - a.lat);
const dLon = toRad(b.lon - a.lon); const dLon = toRad(b.lon - a.lon);
const lat1 = toRad(a.lat); const lat1 = toRad(a.lat);
const lat2 = toRad(b.lat); const lat2 = toRad(b.lat);
const aVal = Math.sin(dLat / 2) ** 2 + const aVal =
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2; Math.sin(dLat / 2) ** 2 +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.atan2(Math.sqrt(aVal), Math.sqrt(1 - aVal)); return 2 * R * Math.atan2(Math.sqrt(aVal), Math.sqrt(1 - aVal));
} }
// Helper: Project point onto segment AB, return projected point and distance along segment // Helper: Project point onto segment AB, return projected point and distance along segment
function projectPointToSegment(p: WorldLocation, a: WorldLocation, b: WorldLocation) { function projectPointToSegment(
const toRad = (deg: number) => deg * Math.PI / 180; p: WorldLocation,
const toDeg = (rad: number) => rad * 180 / Math.PI; a: WorldLocation,
b: WorldLocation,
) {
const toRad = (deg: number) => (deg * Math.PI) / 180;
const toDeg = (rad: number) => (rad * 180) / Math.PI;
const lat1 = toRad(a.lat), lon1 = toRad(a.lon); const lat1 = toRad(a.lat),
const lat2 = toRad(b.lat), lon2 = toRad(b.lon); lon1 = toRad(a.lon);
const lat3 = toRad(p.lat), lon3 = toRad(p.lon); const lat2 = toRad(b.lat),
lon2 = toRad(b.lon);
const lat3 = toRad(p.lat),
lon3 = toRad(p.lon);
const dLon = lon2 - lon1; const dLon = lon2 - lon1;
const dLat = lat2 - lat1; const dLat = lat2 - lat1;
const t = ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) / const t =
(dLat * dLat + dLon * dLon); ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
(dLat * dLat + dLon * dLon);
// Clamp to [0,1] // Clamp to [0,1]
const clampedT = Math.max(0, Math.min(1, t)); const clampedT = Math.max(0, Math.min(1, t));
@ -46,37 +62,43 @@
}; };
} }
const shape = $derived(decodePolyline(routing.currentTrip?.legs[0].shape || "")); const shape = $derived(
const maneuver = $derived(routing.currentTripInfo.currentManeuver); decodePolyline(routing.currentTrip?.legs[0].shape || ""),
);
// const maneuver = $derived(routing.currentTripInfo.currentManeuver);
const fullDistance = $derived.by(() => { const fullDistance = $derived.by(() => {
const lat = location.lat; const lat = location.lat;
const lon = location.lng; const lon = location.lng;
if (!shape.length) return 0; if (!shape.length) return 0;
// 1⃣ find projection onto any segment of the full shape // 1⃣ find projection onto any segment of the full shape
let best = { idx: 0, proj: shape[0], dist: Infinity }; let best = { idx: 0, proj: shape[0], dist: Infinity };
for (let i = 0; i < shape.length - 1; i++) { for (let i = 0; i < shape.length - 1; i++) {
const a = shape[i]; const a = shape[i];
const b = shape[i + 1]; const b = shape[i + 1];
const proj = projectPointToSegment({ lat, lon }, a, b); const proj = projectPointToSegment({ lat, lon }, a, b);
if (proj.distToUser < best.dist) { if (proj.distToUser < best.dist) {
best = { idx: i, proj: { lat: proj.lat, lon: proj.lon }, dist: proj.distToUser }; best = {
} idx: i,
} proj: { lat: proj.lat, lon: proj.lon },
dist: proj.distToUser,
};
}
}
// 2⃣ sum from the projection point to the very last point // 2⃣ sum from the projection point to the very last point
let total = 0; let total = 0;
// from projection → next vertex // from projection → next vertex
total += haversine(best.proj, shape[best.idx + 1]); total += haversine(best.proj, shape[best.idx + 1]);
// then each remaining segment // then each remaining segment
for (let j = best.idx + 1; j < shape.length - 1; j++) { for (let j = best.idx + 1; j < shape.length - 1; j++) {
total += haversine(shape[j], shape[j + 1]); total += haversine(shape[j], shape[j + 1]);
} }
return total; return total;
}); });
const roundFullDistance = $derived.by(() => { const roundFullDistance = $derived.by(() => {
const dist = Math.round(fullDistance); const dist = Math.round(fullDistance);
@ -91,40 +113,50 @@
} else { } else {
return Math.round(dist / 5000) * 5000; return Math.round(dist / 5000) * 5000;
} }
}) });
const fullDistanceText = $derived.by(() => { const fullDistanceText = $derived.by(() => {
const dist = roundFullDistance; const dist = roundFullDistance;
if (dist < 1000) return `${dist} m`; if (dist < 1000) return `${dist} m`;
return `${(dist / 1000)} km`; return `${dist / 1000} km`;
}) });
</script> </script>
{fullDistanceText} left {fullDistanceText} left
<Button onclick={() => { <Button
location.toggleLock(); onclick={() => {
}}>LOCK</Button> location.toggleLock();
}}>LOCK</Button
>
<Button onclick={() => { <Button
stopNavigation(); onclick={() => {
}}>End Trip</Button> stopNavigation();
}}>End Trip</Button
>
<div class="flex flex-col gap-2 mt-5"> <div class="flex flex-col gap-2 mt-5">
{#if location.code} {#if location.code}
<span>Share Code: {location.code}</span> <span>Share Code: {location.code}</span>
<Button variant="secondary" onclick={() => { <Button
location.advertiser?.close(); variant="secondary"
location.advertiser = null; onclick={() => {
location.code = null; location.advertiser?.close();
}}> location.advertiser = null;
location.code = null;
}}
>
Stop Sharing Location Stop Sharing Location
</Button> </Button>
{:else} {:else}
<Button variant="secondary" onclick={() => { <Button
advertiseRemoteLocation(); variant="secondary"
}}> onclick={() => {
advertiseRemoteLocation();
}}
>
Share Trip Status & Location Share Trip Status & Location
</Button> </Button>
{/if} {/if}
</div> </div>

View File

@ -1,8 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { POIIcons } from "$lib/POIIcons"; import { POIIcons } from "$lib/POIIcons";
import { OVERPASS_SERVER } from "$lib/services/hosts"; import {
import { BriefcaseIcon, EllipsisIcon, GlobeIcon, HomeIcon, MailIcon, PhoneIcon, RouteIcon } from "@lucide/svelte"; BriefcaseIcon,
EllipsisIcon,
GlobeIcon,
HomeIcon,
MailIcon,
PhoneIcon,
RouteIcon,
} from "@lucide/svelte";
import { pin } from "../map.svelte"; import { pin } from "../map.svelte";
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
import { fetchPOI, type OverpassElement } from "$lib/services/Overpass"; import { fetchPOI, type OverpassElement } from "$lib/services/Overpass";
@ -13,57 +20,78 @@
import * as Popover from "$lib/components/ui/popover"; import * as Popover from "$lib/components/ui/popover";
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 { hasCapability } from "$lib/services/lnv";
import RequiresCapability from "../RequiresCapability.svelte"; import RequiresCapability from "../RequiresCapability.svelte";
// let { feature }: { feature: Feature } = $props(); // let { feature }: { feature: Feature } = $props();
// let Icon = $derived(POIIcons[feature.properties.osm_key + "=" + feature.properties.osm_value]); // let Icon = $derived(POIIcons[feature.properties.osm_key + "=" + feature.properties.osm_value]);
let { lat, lng }: { lat: number, lng: number } = $props(); let { lat, lng }: { lat: number; lng: number } = $props();
function getIcon(tags: Record<string, string>): typeof POIIcons[keyof typeof POIIcons] | null { function getIcon(
const key = Object.keys(tags).find(k => k.startsWith("amenity") || k.startsWith("shop")); tags: Record<string, string>,
): (typeof POIIcons)[keyof typeof POIIcons] | null {
const key = Object.keys(tags).find(
(k) => k.startsWith("amenity") || k.startsWith("shop"),
);
if (key && POIIcons[key + "=" + tags[key]]) { if (key && POIIcons[key + "=" + tags[key]]) {
return POIIcons[key + "=" + tags[key]]; return POIIcons[key + "=" + tags[key]];
} }
return null; return null;
} }
function getDistance(aLat: number, aLon: number, lat: number, lon: number): number { function getDistance(
aLat: number,
aLon: number,
lat: number,
lon: number,
): number {
const R = 6371e3; // Earth radius in meters const R = 6371e3; // Earth radius in meters
const φ1 = lat * Math.PI / 180; const φ1 = (lat * Math.PI) / 180;
const φ2 = aLat * Math.PI / 180; const φ2 = (aLat * Math.PI) / 180;
const Δφ = (aLat - lat) * Math.PI / 180; const Δφ = ((aLat - lat) * Math.PI) / 180;
const Δλ = (aLon - lon) * Math.PI / 180; const Δλ = ((aLon - lon) * Math.PI) / 180;
const a = Math.sin(Δφ/2)**2 + Math.cos(φ1)*Math.cos(φ2)*Math.sin(Δλ/2)**2; const a =
Math.sin(Δφ / 2) ** 2 +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; return R * c;
} }
function sortByDistance(elements: OverpassElement[], lat: number, lng: number): OverpassElement[] { function sortByDistance(
elements: OverpassElement[],
lat: number,
lng: number,
): OverpassElement[] {
return elements.sort((a: OverpassElement, b: OverpassElement) => { return elements.sort((a: OverpassElement, b: OverpassElement) => {
const aLoc = a.center || a; const aLoc = a.center || a;
const bLoc = b.center || b; const bLoc = b.center || b;
return getDistance(aLoc.lat!, aLoc.lon!, lat, lng) - getDistance(bLoc.lat!, bLoc.lon!, lat, lng); return (
getDistance(aLoc.lat!, aLoc.lon!, lat, lng) -
getDistance(bLoc.lat!, bLoc.lon!, lat, lng)
);
}); });
} }
</script> </script>
{#await fetchPOI(lat, lng, 20)} {#await fetchPOI(lat, lng, 20)}
<SidebarHeader onback={() => { <SidebarHeader
pin.liftPin(); onback={() => {
}}> pin.liftPin();
}}
>
Dropped Pin Dropped Pin
</SidebarHeader> </SidebarHeader>
<p>Loading...</p> <p>Loading...</p>
{:then res} {:then res}
{#if res.elements.length === 0} {#if res.elements.length === 0}
<SidebarHeader onback={() => { <SidebarHeader
pin.liftPin(); onback={() => {
}}> pin.liftPin();
}}
>
Dropped Pin Dropped Pin
</SidebarHeader> </SidebarHeader>
<span style="color: #acacac;">&copy; OpenStreetMap</span> <span style="color: #acacac;">&copy; OpenStreetMap</span>
@ -75,38 +103,57 @@
{@const ellat = firstElement.center?.lat || firstElement.lat!} {@const ellat = firstElement.center?.lat || firstElement.lat!}
{@const ellng = firstElement.center?.lon || firstElement.lon!} {@const ellng = firstElement.center?.lon || firstElement.lon!}
<SidebarHeader onback={() => { <SidebarHeader
pin.liftPin(); onback={() => {
}}> pin.liftPin();
}}
>
{#if getIcon(tags)} {#if getIcon(tags)}
{@const Icon = getIcon(tags)} {@const Icon = getIcon(tags)}
<Icon /> <Icon />
{/if} {/if}
{tags.name || (tags["addr:street"] ? (tags["addr:street"] + " " + tags["addr:housenumber"]) : "")} {tags.name ||
(tags["addr:street"]
? tags["addr:street"] + " " + tags["addr:housenumber"]
: "")}
</SidebarHeader> </SidebarHeader>
<div id="actions"> <div id="actions">
<Button onclick={() => { <Button
view.switch("route", { onclick={() => {
to: lat + "," + lng, view.switch("route", {
}) to: lat + "," + lng,
}}> });
}}
>
<RouteIcon /> <RouteIcon />
Route Route
</Button> </Button>
{#if tags.email || tags["contact:email"]} {#if tags.email || tags["contact:email"]}
<Button variant="secondary" href={`mailto:${tags.email || tags["contact:email"]}`} target="_blank"> <Button
variant="secondary"
href={`mailto:${tags.email || tags["contact:email"]}`}
target="_blank"
>
<MailIcon /> <MailIcon />
Email Email
</Button> </Button>
{/if} {/if}
{#if tags.website || tags["contact:website"]} {#if tags.website || tags["contact:website"]}
<Button variant="secondary" href={tags.website || tags["contact:website"]} target="_blank"> <Button
variant="secondary"
href={tags.website || tags["contact:website"]}
target="_blank"
>
<GlobeIcon /> <GlobeIcon />
Website Website
</Button> </Button>
{/if} {/if}
{#if tags.phone || tags["contact:phone"]} {#if tags.phone || tags["contact:phone"]}
<Button variant="secondary" href={`tel:${tags.phone || tags["contact:phone"]}`} target="_blank"> <Button
variant="secondary"
href={`tel:${tags.phone || tags["contact:phone"]}`}
target="_blank"
>
<PhoneIcon /> <PhoneIcon />
Call Call
</Button> </Button>
@ -120,15 +167,27 @@
</Popover.Trigger> </Popover.Trigger>
<Popover.Content> <Popover.Content>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Button variant="outline" onclick={() => { <Button
localStorage.setItem("saved.home", JSON.stringify({ lat, lon: lng })); variant="outline"
}}> onclick={() => {
localStorage.setItem(
"saved.home",
JSON.stringify({ lat, lon: lng }),
);
}}
>
<HomeIcon /> <HomeIcon />
Set as Home Set as Home
</Button> </Button>
<Button variant="outline" onclick={() => { <Button
localStorage.setItem("saved.work", JSON.stringify({ lat, lon: lng })); variant="outline"
}}> onclick={() => {
localStorage.setItem(
"saved.work",
JSON.stringify({ lat, lon: lng }),
);
}}
>
<BriefcaseIcon /> <BriefcaseIcon />
Set as Work Set as Work
</Button> </Button>
@ -160,12 +219,11 @@
{/if} {/if}
<!-- any payment:* tag --> <!-- any payment:* tag -->
{#if Object.keys(tags).some(key => key.startsWith("payment:"))} {#if Object.keys(tags).some((key) => key.startsWith("payment:"))}
<h3 class="text-lg font-bold mt-2">Payment Methods</h3> <h3 class="text-lg font-bold mt-2">Payment Methods</h3>
<ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;"> <ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
{#each Object.entries(tags).filter(([key]) => key.startsWith("payment:")) as [key, value]} {#each Object.entries(tags).filter( ([key]) => key.startsWith("payment:"), ) as [key, value] (key)}
<!-- <li>{key.replace("payment:", "")}: {value}</li> --> <Badge>{key.replace("payment:", "")}: {value}</Badge>
<Badge>{key.replace("payment:", "")}</Badge>
{/each} {/each}
</ul> </ul>
{/if} {/if}
@ -175,13 +233,15 @@
</RequiresCapability> </RequiresCapability>
<span style="color: #acacac;">&copy; OpenStreetMap</span> <span style="color: #acacac;">&copy; OpenStreetMap</span>
<pre>{JSON.stringify(elements, null, 2)}</pre> <pre>{JSON.stringify(elements, null, 2)}</pre>
{/if} {/if}
{:catch err} {:catch err}
<SidebarHeader onback={() => { <SidebarHeader
pin.liftPin(); onback={() => {
}}> pin.liftPin();
}}
>
Dropped Pin Dropped Pin
</SidebarHeader> </SidebarHeader>
<p>Error: {err.message}</p> <p>Error: {err.message}</p>

View File

@ -1,2 +1,2 @@
<h1>Error</h1> <h1>Error</h1>
<p>Invalid sidebar configuration.</p> <p>Invalid sidebar configuration.</p>

View File

@ -1,117 +0,0 @@
<script lang="ts">
import { BriefcaseIcon, HomeIcon } from "@lucide/svelte";
import { Button } from "../../ui/button";
import { Input } from "../../ui/input";
import { fly } from "svelte/transition";
import { circInOut } from "svelte/easing";
import { search, type Feature } from "$lib/services/Search";
import { view } from "../sidebar.svelte";
import { map, pin } from "../map.svelte";
function debounce<T>(getter: () => T, delay: number): () => T | undefined {
let value = $state<T>();
let timer: NodeJS.Timeout;
$effect(() => {
const newValue = getter(); // read here to subscribe to it
clearTimeout(timer);
timer = setTimeout(() => value = newValue, delay);
return () => clearTimeout(timer);
});
return () => value;
}
let typedText = $state("");
let loading = $state(false);
let searchText = $derived.by(debounce(() => typedText, 300));
let searchResults: Feature[] = $state([]);
$effect(() => {
if(!searchText) {
searchResults = [];
return;
}
if (searchText.length > 0) {
loading = true;
search(searchText, 0, 0).then(results => {
searchResults = results;
loading = false;
});
} else {
searchResults = [];
}
});
$inspect("searchText", searchText);
</script>
<div id="search-progress" style="min-height: calc(3px + 3px); width: 100%; min-height: 3ch;">
{#if loading}
LOADING
{/if}
</div>
<Input placeholder="Search..." bind:value={typedText} class="mb-2" />
{#if searchResults.length == 0}
<div id="saved" in:fly={{ y: 20, duration: 200, easing: circInOut }}>
<Button variant="secondary" class="flex-1" onclick={() => {
const home = localStorage.getItem("saved.home");
if(!home) {
alert("No home location saved.");
return;
}
const {lat, lon} = JSON.parse(home);
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19
});
}}>
<HomeIcon />
Home
</Button>
<Button variant="secondary" class="flex-1" onclick={() => {
const work = localStorage.getItem("saved.work");
if(!work) {
alert("No home location saved.");
return;
}
const {lat, lon} = JSON.parse(work);
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19
});
}}>
<BriefcaseIcon />
Work
</Button>
</div>
{:else}
<div id="results" in:fly={{ y: 20, duration: 200, easing: circInOut }}>
{#each searchResults as result}
<Button variant="secondary" class="flex-1" onclick={() => {
// view.switch("info", { feature: result });
pin.dropPin(result.geometry.coordinates[1], result.geometry.coordinates[0]);
pin.showInfo();
map.value?.flyTo({
center: [result.geometry.coordinates[0], result.geometry.coordinates[1]],
zoom: 19
});
}}>
{result.properties.name}
</Button>
{/each}
</div>
{/if}
<style>
#saved {
display: flex;
gap: 0.5rem;
/* justify-content: space-evenly; */
width: 100%;
max-width: 100%;
}
</style>

View File

@ -6,41 +6,53 @@
import { map, pin } from "../map.svelte"; 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";
</script> </script>
<div id="saved" class="mt-2 mb-2" in:fly={{ y: 20, duration: 200, easing: circInOut }}> <div
<Button variant="secondary" class="flex-1" onclick={() => { id="saved"
const home = localStorage.getItem("saved.home"); class="mt-2 mb-2"
if(!home) { in:fly={{ y: 20, duration: 200, easing: circInOut }}
alert("No home location saved."); >
return; <Button
} variant="secondary"
const {lat, lon} = JSON.parse(home); class="flex-1"
pin.dropPin(lat, lon); onclick={() => {
pin.showInfo(); const home = localStorage.getItem("saved.home");
map.value?.flyTo({ if (!home) {
center: [lon, lat], alert("No home location saved.");
zoom: 19 return;
}); }
}}> const { lat, lon } = JSON.parse(home);
pin.dropPin(lat, lon);
pin.showInfo();
map.value?.flyTo({
center: [lon, lat],
zoom: 19,
});
}}
>
<HomeIcon /> <HomeIcon />
Home Home
</Button> </Button>
<Button variant="secondary" class="flex-1" onclick={() => { <Button
const work = localStorage.getItem("saved.work"); variant="secondary"
if(!work) { class="flex-1"
alert("No work location saved."); onclick={() => {
return; const work = localStorage.getItem("saved.work");
} if (!work) {
const {lat, lon} = JSON.parse(work); alert("No work location saved.");
pin.dropPin(lat, lon); return;
pin.showInfo(); }
map.value?.flyTo({ const { lat, lon } = JSON.parse(work);
center: [lon, lat], pin.dropPin(lat, lon);
zoom: 19 pin.showInfo();
}); map.value?.flyTo({
}}> center: [lon, lat],
zoom: 19,
});
}}
>
<BriefcaseIcon /> <BriefcaseIcon />
Work Work
</Button> </Button>
@ -51,7 +63,7 @@
<RequiresCapability capability="post"> <RequiresCapability capability="post">
<div> <div>
<h2>In your area</h2> <h2>In your area</h2>
<Post /> <Post />
</div> </div>
</RequiresCapability> </RequiresCapability>

View File

@ -1,19 +1,29 @@
<script lang="ts"> <script lang="ts">
import { CircleArrowDown, CircleDotIcon, StarIcon } from "@lucide/svelte"; import { CircleArrowDown, CircleDotIcon, StarIcon } from "@lucide/svelte";
import LocationSelect from "../LocationSelect.svelte";
import Input from "$lib/components/ui/input/input.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";
import { drawAllRoutes, fetchRoute, removeAllRoutes, zoomToPoints } from "$lib/services/navigation/routing.svelte"; import {
drawAllRoutes,
fetchRoute,
removeAllRoutes,
zoomToPoints,
} from "$lib/services/navigation/routing.svelte";
import { ROUTING_SERVER } from "$lib/services/hosts"; import { ROUTING_SERVER } from "$lib/services/hosts";
import { map } from "../map.svelte"; import { map } from "../map.svelte";
import { view } from "../sidebar.svelte"; import { view } from "../sidebar.svelte";
import { DefaultVehicle, selectedVehicle } from "$lib/vehicles/vehicles.svelte"; import {
DefaultVehicle,
selectedVehicle,
} from "$lib/vehicles/vehicles.svelte";
let { from, to }: { let {
from?: string, from,
to?: string to,
}: {
from?: string;
to?: string;
} = $props(); } = $props();
let fromLocation = $state(from || ""); let fromLocation = $state(from || "");
@ -29,13 +39,18 @@
} }
</script> </script>
<SidebarHeader onback={() => { <SidebarHeader
removeAllRoutes(); onback={() => {
}}> removeAllRoutes();
}}
>
Route Route
</SidebarHeader> </SidebarHeader>
<span>Driving with <strong>{(selectedVehicle() ?? DefaultVehicle).name}</strong></span> <span
>Driving with <strong>{(selectedVehicle() ?? DefaultVehicle).name}</strong
></span
>
<div class="flex flex-col gap-2 w-full mb-2"> <div class="flex flex-col gap-2 w-full mb-2">
<div class="flex gap-2 items-center w-full"> <div class="flex gap-2 items-center w-full">
<CircleDotIcon /> <CircleDotIcon />
@ -49,39 +64,51 @@
<Input bind:value={toLocation} /> <Input bind:value={toLocation} />
</div> </div>
</div> </div>
<Button onclick={async () => { <Button
const FROM: WorldLocation = fromLocation == "home" ? JSON.parse(localStorage.getItem("saved.home")!) onclick={async () => {
: fromLocation == "work" ? JSON.parse(localStorage.getItem("saved.work")!) const FROM: WorldLocation =
: { fromLocation == "home"
lat: parseFloat(fromLocation.split(",")[0]), ? JSON.parse(localStorage.getItem("saved.home")!)
lon: parseFloat(fromLocation.split(",")[1]) : fromLocation == "work"
}; ? JSON.parse(localStorage.getItem("saved.work")!)
const TO: WorldLocation = toLocation == "home" ? JSON.parse(localStorage.getItem("saved.home")!) : {
: toLocation == "work" ? JSON.parse(localStorage.getItem("saved.work")!) lat: parseFloat(fromLocation.split(",")[0]),
: { lon: parseFloat(fromLocation.split(",")[1]),
lat: parseFloat(toLocation.split(",")[0]), };
lon: parseFloat(toLocation.split(",")[1]) const TO: WorldLocation =
}; toLocation == "home"
const req = createValhallaRequest(selectedVehicle() ?? DefaultVehicle, [FROM, TO]); ? JSON.parse(localStorage.getItem("saved.home")!)
const res = await fetchRoute(ROUTING_SERVER, req); : toLocation == "work"
routes = [ ? JSON.parse(localStorage.getItem("saved.work")!)
res.trip, : {
]; lat: parseFloat(toLocation.split(",")[0]),
for(const alternate of res.alternates) { lon: parseFloat(toLocation.split(",")[1]),
if(alternate.trip) { };
routes.push(alternate.trip); const req = createValhallaRequest(selectedVehicle() ?? DefaultVehicle, [
FROM,
TO,
]);
const res = await fetchRoute(ROUTING_SERVER, req);
routes = [res.trip];
for (const alternate of res.alternates) {
if (alternate.trip) {
routes.push(alternate.trip);
}
} }
} drawAllRoutes(routes);
drawAllRoutes(routes); zoomToPoints(FROM, TO, map.value!);
zoomToPoints(FROM, TO, map.value!); }}>Calculate</Button
}}>Calculate</Button> >
{#if routes} {#if routes}
<div class="mt-2 flex gap-2 flex-col"> <div class="mt-2 flex gap-2 flex-col">
{#each routes as route, i (route?.summary?.length)} {#each routes as route, i (route?.summary?.length)}
<Button variant="secondary" onclick={() => { <Button
view.switch("trip", { route }); variant="secondary"
}}> onclick={() => {
view.switch("trip", { route });
}}
>
{#if i == 0} {#if i == 0}
<StarIcon /> <StarIcon />
{/if} {/if}
@ -89,4 +116,4 @@
</Button> </Button>
{/each} {/each}
</div> </div>
{/if} {/if}

View File

@ -7,29 +7,48 @@
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
import { searchbar } from "../sidebar.svelte"; import { searchbar } from "../sidebar.svelte";
let { results, query }: { let {
results: Feature[], results,
query: string query,
}: {
results: Feature[];
query: string;
} = $props(); } = $props();
</script> </script>
<SidebarHeader onback={() => { <SidebarHeader
searchbar.text = ""; onback={() => {
}}> searchbar.text = "";
Search Results }}
>
Search Results for "{query}"
</SidebarHeader> </SidebarHeader>
<div id="results" class="mt-2" in:fly={{ y: 20, duration: 200, easing: circInOut }}> <div
{#each results as result} id="results"
<Button variant="secondary" class="flex-1" onclick={() => { class="mt-2"
// view.switch("info", { feature: result }); in:fly={{ y: 20, duration: 200, easing: circInOut }}
pin.dropPin(result.geometry.coordinates[1], result.geometry.coordinates[0]); >
pin.showInfo(); {#each results as result (result.properties.osm_id)}
map.value?.flyTo({ <Button
center: [result.geometry.coordinates[0], result.geometry.coordinates[1]], variant="secondary"
zoom: 19 class="flex-1"
}); onclick={() => {
}}> // view.switch("info", { feature: result });
pin.dropPin(
result.geometry.coordinates[1],
result.geometry.coordinates[0],
);
pin.showInfo();
map.value?.flyTo({
center: [
result.geometry.coordinates[0],
result.geometry.coordinates[1],
],
zoom: 19,
});
}}
>
{result.properties.name} {result.properties.name}
</Button> </Button>
{/each} {/each}
</div> </div>

View File

@ -3,18 +3,26 @@
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import { view } from "../sidebar.svelte"; import { view } from "../sidebar.svelte";
let { children, onback }: { let {
children: Snippet, children,
onback?: () => void, onback,
}: {
children: Snippet;
onback?: () => void;
} = $props(); } = $props();
</script> </script>
<div class="flex gap-2 items-center mb-2"> <div class="flex gap-2 items-center mb-2">
<Button variant="outline" onclick={() => { <Button
view.back(); variant="outline"
if (onback) { onclick={() => {
onback(); view.back();
} if (onback) {
}}>&lt;</Button> onback();
<h2 class="text-lg font-bold flex gap-2 items-center">{@render children?.()}</h2> }
</div> }}>&lt;</Button
>
<h2 class="text-lg font-bold flex gap-2 items-center">
{@render children?.()}
</h2>
</div>

View File

@ -1,34 +1,44 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
import { drawRoute, removeAllRoutes, startRoute } from "$lib/services/navigation/routing.svelte"; import {
drawRoute,
removeAllRoutes,
startRoute,
} from "$lib/services/navigation/routing.svelte";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte"; import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte";
import { map } from "../map.svelte"; import { map } from "../map.svelte";
let { route }: { let {
route: Trip route,
}: {
route: Trip;
} = $props(); } = $props();
onMount(() => { onMount(() => {
removeAllRoutes(); removeAllRoutes();
drawRoute(route); drawRoute(route);
}) });
</script> </script>
<SidebarHeader onback={() => { <SidebarHeader
removeAllRoutes(); onback={() => {
}}> removeAllRoutes();
}}
>
Trip Details Trip Details
</SidebarHeader> </SidebarHeader>
<div id="actions" class="flex gap-2"> <div id="actions" class="flex gap-2">
<Button onclick={async () => { <Button
await startRoute(route); onclick={async () => {
requestAnimationFrame(() => { await startRoute(route);
map.updateMapPadding(); requestAnimationFrame(() => {
}) map.updateMapPadding();
}}> });
}}
>
<RouteIcon /> <RouteIcon />
Start Navigation Start Navigation
</Button> </Button>
@ -43,9 +53,9 @@
</div> </div>
<div class="flex flex-col gap-2 mt-2"> <div class="flex flex-col gap-2 mt-2">
{#each route.legs[0].maneuvers as maneuver} {#each route.legs[0].maneuvers as maneuver (maneuver)}
<li> <li>
{maneuver.instruction} {maneuver.instruction}
</li> </li>
{/each} {/each}
</div> </div>

View File

@ -5,49 +5,60 @@
import { getAuthURL, getOIDCUser } from "$lib/services/oidc"; import { getAuthURL, getOIDCUser } from "$lib/services/oidc";
import * as Avatar from "$lib/components/ui/avatar"; import * as Avatar from "$lib/components/ui/avatar";
let user: any = $state(null); interface OIDCUser {
sub: string;
preferred_username: string;
name?: string;
picture?: string;
}
let user: OIDCUser | null = $state(null);
onMount(() => { onMount(() => {
if(!localStorage.getItem("lnv-token")) { if (!localStorage.getItem("lnv-token")) {
user = null; user = null;
} else { } else {
user = JSON.parse(atob((localStorage.getItem("lnv-id") || "").split(".")[1])); user = JSON.parse(
atob((localStorage.getItem("lnv-id") || "").split(".")[1]),
);
} }
}) });
</script> </script>
{#if !user} {#if !user}
<SidebarHeader> <SidebarHeader>User</SidebarHeader>
User
</SidebarHeader>
<Button onclick={async () => { <Button
const auth = await getAuthURL(); onclick={async () => {
// localStorage.setItem("lnv-codeVerifier", auth.codeVerifier); const auth = await getAuthURL();
// localStorage.setItem("lnv-oidcstate", auth.state); // localStorage.setItem("lnv-codeVerifier", auth.codeVerifier);
const popup = window.open(auth.url, "Login", "width=500,height=600"); // localStorage.setItem("lnv-oidcstate", auth.state);
window.addEventListener("message", async (e) => { const popup = window.open(auth.url, "Login", "width=500,height=600");
if(e.origin !== window.location.origin) return; window.addEventListener("message", async (e) => {
if (e.origin !== window.location.origin) return;
const { code, state } = e.data; const { code, state } = e.data;
console.log("Received data from popup:", e.data); console.log("Received data from popup:", e.data);
if(!code || !state) { if (!code || !state) {
console.error("Invalid response from popup"); console.error("Invalid response from popup");
return; return;
} }
popup?.close(); popup?.close();
if(state !== auth.state) { if (state !== auth.state) {
alert("State mismatch. Please try again."); alert("State mismatch. Please try again.");
return; return;
} }
const token = await getOIDCUser(code, auth.codeVerifier); const token = await getOIDCUser(code, auth.codeVerifier);
localStorage.setItem("lnv-id", token.id_token); localStorage.setItem("lnv-id", token.id_token);
localStorage.setItem("lnv-token", token.access_token); localStorage.setItem("lnv-token", token.access_token);
localStorage.setItem("lnv-refresh", token.refresh_token); localStorage.setItem("lnv-refresh", token.refresh_token);
user = JSON.parse(atob((localStorage.getItem("lnv-id") || "").split(".")[1])); user = JSON.parse(
}) atob((localStorage.getItem("lnv-id") || "").split(".")[1]),
}}>Login</Button> );
});
}}>Login</Button
>
{:else} {:else}
<SidebarHeader> <SidebarHeader>
<Avatar.Root> <Avatar.Root>
@ -58,4 +69,4 @@
</SidebarHeader> </SidebarHeader>
<pre>{user.sub}</pre> <pre>{user.sub}</pre>
{JSON.stringify(user, null, 2)} {JSON.stringify(user, null, 2)}
{/if} {/if}

View File

@ -12,6 +12,9 @@
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
bind:ref bind:ref
data-slot="avatar-fallback" data-slot="avatar-fallback"
class={cn("bg-muted flex size-full items-center justify-center rounded-full", className)} class={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className,
)}
{...restProps} {...restProps}
/> />

View File

@ -12,6 +12,9 @@
<AvatarPrimitive.Root <AvatarPrimitive.Root
bind:ref bind:ref
data-slot="avatar" data-slot="avatar"
class={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)} class={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className,
)}
{...restProps} {...restProps}
/> />

View File

@ -11,7 +11,8 @@
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent", "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
destructive: destructive:
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white", "bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@ -1,19 +1,25 @@
<script lang="ts" module> <script lang="ts" module>
import { cn, type WithElementRef } from "$lib/utils.js"; import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; import type {
HTMLAnchorAttributes,
HTMLButtonAttributes,
} from "svelte/elements";
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from "tailwind-variants";
export const buttonVariants = tv({ 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", 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",
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: destructive:
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white", "bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
outline: outline:
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border", "bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", secondary:
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {

View File

@ -13,7 +13,10 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="card-action" data-slot="card-action"
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} class={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -10,6 +10,11 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}> <div
bind:this={ref}
data-slot="card-content"
class={cn("px-6", className)}
{...restProps}
>
{@render children?.()} {@render children?.()}
</div> </div>

View File

@ -15,7 +15,7 @@
data-slot="card-header" data-slot="card-header"
class={cn( class={cn(
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6", "@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -15,7 +15,7 @@
data-slot="card" data-slot="card"
class={cn( class={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -1,5 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from "bits-ui"; import type {
Command as CommandPrimitive,
Dialog as DialogPrimitive,
} from "bits-ui";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import Command from "./command.svelte"; import Command from "./command.svelte";
import * as Dialog from "$lib/components/ui/dialog/index.js"; import * as Dialog from "$lib/components/ui/dialog/index.js";

View File

@ -11,13 +11,16 @@
}: CommandPrimitive.InputProps = $props(); }: CommandPrimitive.InputProps = $props();
</script> </script>
<div class="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper"> <div
class="flex h-9 items-center gap-2 border-b px-3"
data-slot="command-input-wrapper"
>
<SearchIcon class="size-4 shrink-0 opacity-50" /> <SearchIcon class="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input <CommandPrimitive.Input
data-slot="command-input" data-slot="command-input"
class={cn( class={cn(
"placeholder:text-muted-foreground outline-hidden flex h-10 w-full rounded-md bg-transparent py-3 text-sm disabled:cursor-not-allowed disabled:opacity-50", "placeholder:text-muted-foreground outline-hidden flex h-10 w-full rounded-md bg-transparent py-3 text-sm disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
bind:ref bind:ref
{...restProps} {...restProps}

View File

@ -14,7 +14,7 @@
data-slot="command-item" data-slot="command-item"
class={cn( class={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -14,7 +14,7 @@
data-slot="command-item" data-slot="command-item"
class={cn( class={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -12,6 +12,9 @@
<CommandPrimitive.List <CommandPrimitive.List
bind:ref bind:ref
data-slot="command-list" data-slot="command-list"
class={cn("max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden", className)} class={cn(
"max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden",
className,
)}
{...restProps} {...restProps}
/> />

View File

@ -16,7 +16,7 @@
data-slot="command" data-slot="command"
class={cn( class={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props(); let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps =
$props();
</script> </script>
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} /> <DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />

View File

@ -24,7 +24,7 @@
data-slot="dialog-content" data-slot="dialog-content"
class={cn( class={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -13,7 +13,10 @@
<div <div
bind:this={ref} bind:this={ref}
data-slot="dialog-footer" data-slot="dialog-footer"
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} class={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@ -14,7 +14,7 @@
data-slot="dialog-overlay" data-slot="dialog-overlay"
class={cn( class={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui"; import { Dialog as DialogPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props(); let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps =
$props();
</script> </script>
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} /> <DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte"; import { Drawer as DrawerPrimitive } from "vaul-svelte";
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props(); let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps =
$props();
</script> </script>
<DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} /> <DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} />

View File

@ -25,7 +25,7 @@
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t", "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm", "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm", "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -9,4 +9,9 @@
}: DrawerPrimitive.RootProps = $props(); }: DrawerPrimitive.RootProps = $props();
</script> </script>
<DrawerPrimitive.NestedRoot {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} /> <DrawerPrimitive.NestedRoot
{shouldScaleBackground}
bind:open
bind:activeSnapPoint
{...restProps}
/>

View File

@ -14,7 +14,7 @@
data-slot="drawer-overlay" data-slot="drawer-overlay"
class={cn( class={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte"; import { Drawer as DrawerPrimitive } from "vaul-svelte";
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props(); let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps =
$props();
</script> </script>
<DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} /> <DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} />

View File

@ -9,4 +9,9 @@
}: DrawerPrimitive.RootProps = $props(); }: DrawerPrimitive.RootProps = $props();
</script> </script>
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} /> <DrawerPrimitive.Root
{shouldScaleBackground}
bind:open
bind:activeSnapPoint
{...restProps}
/>

View File

@ -1,12 +1,18 @@
<script lang="ts"> <script lang="ts">
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements"; import type {
HTMLInputAttributes,
HTMLInputTypeAttribute,
} from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js"; import { cn, type WithElementRef } from "$lib/utils.js";
type InputType = Exclude<HTMLInputTypeAttribute, "file">; type InputType = Exclude<HTMLInputTypeAttribute, "file">;
type Props = WithElementRef< type Props = WithElementRef<
Omit<HTMLInputAttributes, "type"> & Omit<HTMLInputAttributes, "type"> &
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined }) (
| { type: "file"; files?: FileList }
| { type?: InputType; files?: undefined }
)
>; >;
let { let {
@ -27,7 +33,7 @@
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-2 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-2 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className,
)} )}
type="file" type="file"
bind:files bind:files
@ -42,7 +48,7 @@
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className,
)} )}
{type} {type}
bind:value bind:value

View File

@ -22,7 +22,7 @@
{align} {align}
class={cn( class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin) outline-hidden z-50 w-72 rounded-md border p-4 shadow-md", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin) outline-hidden z-50 w-72 rounded-md border p-4 shadow-md",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -23,14 +23,14 @@
data-slot="select-content" data-slot="select-content"
class={cn( class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) relative z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) relative z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className,
)} )}
{...restProps} {...restProps}
> >
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
class={cn( class={cn(
"h-(--bits-select-anchor-height) min-w-(--bits-select-anchor-width) w-full scroll-my-1 p-1" "h-(--bits-select-anchor-height) min-w-(--bits-select-anchor-width) w-full scroll-my-1 p-1",
)} )}
> >
{@render children?.()} {@render children?.()}

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from "bits-ui"; import { Select as SelectPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props(); let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps =
$props();
</script> </script>
<SelectPrimitive.Group data-slot="select-group" {...restProps} /> <SelectPrimitive.Group data-slot="select-group" {...restProps} />

View File

@ -19,7 +19,7 @@
data-slot="select-item" data-slot="select-item"
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -20,7 +20,7 @@
data-size={size} data-size={size}
class={cn( class={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 shadow-xs flex w-fit select-none items-center justify-between gap-2 whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 shadow-xs flex w-fit select-none items-center justify-between gap-2 whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className className,
)} )}
{...restProps} {...restProps}
> >

View File

@ -14,7 +14,7 @@
data-slot="separator" data-slot="separator"
class={cn( class={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px", "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px",
className className,
)} )}
{...restProps} {...restProps}
/> />

View File

@ -1,7 +1,7 @@
import { LNV_SERVER } from "./hosts"; import { LNV_SERVER } from "./hosts";
import { hasCapability } from "./lnv"; import { hasCapability } from "./lnv";
type Station = { interface Station {
id: string; id: string;
name: string; name: string;
brand: string; brand: string;
@ -18,13 +18,13 @@ type Station = {
postCode: number; postCode: number;
} }
type StationsResponse = { interface StationsResponse {
ok: boolean; ok: boolean;
license: "CC BY 4.0 - https:\/\/creativecommons.tankerkoenig.de"; license: "CC BY 4.0 - https://creativecommons.tankerkoenig.de";
data: "MTS-K"; data: "MTS-K";
status: string; status: string;
stations: Station[]; stations: Station[];
}; }
type StationDetails = { type StationDetails = {
openingTimes: StationOpeningTime[]; openingTimes: StationOpeningTime[];
@ -32,37 +32,49 @@ type StationDetails = {
wholeDay: boolean; wholeDay: boolean;
} & Station; } & Station;
type StationOpeningTime = { interface StationOpeningTime {
text: string; text: string;
start: string; start: string;
end: string; end: string;
}; }
type StationDetailsResponse = { interface StationDetailsResponse {
ok: boolean; ok: boolean;
license: "CC BY 4.0 - https:\/\/creativecommons.tankerkoenig.de"; license: "CC BY 4.0 - https://creativecommons.tankerkoenig.de";
data: "MTS-K"; data: "MTS-K";
status: string; status: string;
station: StationDetails; station: StationDetails;
};
export async function getStations(lat: number, lon: number): Promise<StationsResponse> {
if(!await hasCapability("fuel")) {
throw new Error("Fuel capability is not available");
}
return await fetch(`${LNV_SERVER}/fuel/list?lat=${lat}&lng=${lon}&rad=1&sort=dist&type=all`).then(res => res.json());
} }
export async function getPrices(id: string) { // TODO: add type export async function getStations(
if(!await hasCapability("fuel")) { lat: number,
lon: number,
): Promise<StationsResponse> {
if (!(await hasCapability("fuel"))) {
throw new Error("Fuel capability is not available"); throw new Error("Fuel capability is not available");
} }
return await fetch(`${LNV_SERVER}/fuel/prices?ids=${id}`).then(res => res.json()); return await fetch(
`${LNV_SERVER}/fuel/list?lat=${lat}&lng=${lon}&rad=1&sort=dist&type=all`,
).then((res) => res.json());
} }
export async function getStationDetails(id: string): Promise<StationDetailsResponse> { export async function getPrices(id: string) {
if(!await hasCapability("fuel")) { // TODO: add type
if (!(await hasCapability("fuel"))) {
throw new Error("Fuel capability is not available"); throw new Error("Fuel capability is not available");
} }
return await fetch(`${LNV_SERVER}/fuel/detail?id=${id}`).then(res => res.json()); return await fetch(`${LNV_SERVER}/fuel/prices?ids=${id}`).then((res) =>
res.json(),
);
}
export async function getStationDetails(
id: string,
): Promise<StationDetailsResponse> {
if (!(await hasCapability("fuel"))) {
throw new Error("Fuel capability is not available");
}
return await fetch(`${LNV_SERVER}/fuel/detail?id=${id}`).then((res) =>
res.json(),
);
} }

View File

@ -1,10 +1,10 @@
import { OVERPASS_SERVER } from "./hosts"; import { OVERPASS_SERVER } from "./hosts";
export type OverpassResult = { export interface OverpassResult {
elements: OverpassElement[]; elements: OverpassElement[];
}; }
export type OverpassElement = { export interface OverpassElement {
type: "node" | "way" | "relation"; type: "node" | "way" | "relation";
id: number; id: number;
tags: Record<string, string>; tags: Record<string, string>;
@ -15,7 +15,7 @@ export type OverpassElement = {
lat: number; // Only for relations lat: number; // Only for relations
lon: number; // Only for relations lon: number; // Only for relations
}; };
}; }
/** /**
[out:json]; [out:json];
@ -40,11 +40,7 @@ out geom;
out geom; out geom;
*/ */
export async function fetchPOI( export async function fetchPOI(lat: number, lon: number, radius: number) {
lat: number,
lon: number,
radius: number,
) {
return await fetch(OVERPASS_SERVER, { return await fetch(OVERPASS_SERVER, {
method: "POST", method: "POST",
body: `[out:json]; body: `[out:json];
@ -60,6 +56,6 @@ export async function fetchPOI(
node(around:${radius}, ${lat}, ${lon})["amenity"="parking"]; node(around:${radius}, ${lat}, ${lon})["amenity"="parking"];
way(around:${radius}, ${lat}, ${lon})["amenity"="parking"]; way(around:${radius}, ${lat}, ${lon})["amenity"="parking"];
); );
out center tags;` out center tags;`,
}).then(res => res.json() as Promise<OverpassResult>); }).then((res) => res.json() as Promise<OverpassResult>);
} }

View File

@ -2,38 +2,50 @@
import { SEARCH_SERVER } from "./hosts"; import { SEARCH_SERVER } from "./hosts";
// import { Capacitor } from "@capacitor/core"; // import { Capacitor } from "@capacitor/core";
export type Feature = { export interface Feature {
type: "Feature", type: "Feature";
geometry: { geometry: {
coordinates: [number, number], coordinates: [number, number];
type: "Point" type: "Point";
}, };
properties: { properties: {
osm_key: string; osm_key: string;
osm_value: string; osm_value: string;
osm_id: number, osm_id: number;
city: string, city: string;
country: string, country: string;
name: string, name: string;
street: string, street: string;
housenumber: string, housenumber: string;
type: string, type: string;
// There is more, but not needed atm // There is more, but not needed atm
} };
} }
export async function searchPlaces(query: string, lat: number, lon: number): Promise<Feature[]> { export async function searchPlaces(
const res = await fetch(SEARCH_SERVER + "/api/?q=" + query + "&lat=" + lat + "&lon=" + lon).then((res) => res.json()); query: string,
lat: number,
lon: number,
): Promise<Feature[]> {
const res = await fetch(
SEARCH_SERVER + "/api/?q=" + query + "&lat=" + lat + "&lon=" + lon,
).then((res) => res.json());
return res.features; return res.features;
} }
export async function reverseGeocode(coord: WorldLocation): Promise<Feature[]> { export async function reverseGeocode(coord: WorldLocation): Promise<Feature[]> {
const res = await fetch(SEARCH_SERVER + "/reverse?lat=" + coord.lat + "&lon=" + coord.lon).then((res) => res.json()); const res = await fetch(
SEARCH_SERVER + "/reverse?lat=" + coord.lat + "&lon=" + coord.lon,
).then((res) => res.json());
return res.features; return res.features;
} }
export async function search(query: string, lat: number, lon: number): Promise<Feature[]> { export async function search(
if(query.startsWith("@")) { query: string,
lat: number,
lon: number,
): Promise<Feature[]> {
if (query.startsWith("@")) {
// if(Capacitor.isNativePlatform()) { // if(Capacitor.isNativePlatform()) {
// return await searchContacts(query, lat, lon); // return await searchContacts(query, lat, lon);
// } // }

View File

@ -4,4 +4,7 @@ export const ROUTING_SERVER = "https://valhalla1.openstreetmap.de/";
// export const ROUTING_SERVER = "https://routing.map.picoscratch.de"; // export const ROUTING_SERVER = "https://routing.map.picoscratch.de";
export const SEARCH_SERVER = "https://photon.komoot.io/"; export const SEARCH_SERVER = "https://photon.komoot.io/";
export const OVERPASS_SERVER = "https://overpass-api.de/api/interpreter"; export const OVERPASS_SERVER = "https://overpass-api.de/api/interpreter";
export const LNV_SERVER = location.hostname == "localhost" ? "http://localhost:3000/api" : "https://trafficcue-api.picoscratch.de/api"; export const LNV_SERVER =
location.hostname == "localhost"
? "http://localhost:3000/api"
: "https://trafficcue-api.picoscratch.de/api";

View File

@ -2,7 +2,11 @@ import { LNV_SERVER } from "./hosts";
export type Capabilities = ("auth" | "reviews" | "ai" | "fuel" | "post")[]; export type Capabilities = ("auth" | "reviews" | "ai" | "fuel" | "post")[];
export let capabilities: Capabilities = []; export let capabilities: Capabilities = [];
export let oidcConfig: { AUTH_URL: string; CLIENT_ID: string; TOKEN_URL: string } | null = null; export let oidcConfig: {
AUTH_URL: string;
CLIENT_ID: string;
TOKEN_URL: string;
} | null = null;
export async function fetchConfig() { export async function fetchConfig() {
const res = await fetch(LNV_SERVER + "/config"); const res = await fetch(LNV_SERVER + "/config");
@ -10,7 +14,12 @@ export async function fetchConfig() {
throw new Error(`Failed to fetch capabilities: ${res.statusText}`); throw new Error(`Failed to fetch capabilities: ${res.statusText}`);
} }
const data = await res.json(); const data = await res.json();
return data as { name: string; version: string; capabilities: Capabilities; oidc?: { AUTH_URL: string; CLIENT_ID: string; TOKEN_URL: string } }; return data as {
name: string;
version: string;
capabilities: Capabilities;
oidc?: { AUTH_URL: string; CLIENT_ID: string; TOKEN_URL: string };
};
} }
export async function getCapabilities() { export async function getCapabilities() {
@ -30,28 +39,32 @@ export async function getOIDCConfig() {
oidcConfig = { oidcConfig = {
AUTH_URL: config.oidc.AUTH_URL, AUTH_URL: config.oidc.AUTH_URL,
CLIENT_ID: config.oidc.CLIENT_ID, CLIENT_ID: config.oidc.CLIENT_ID,
TOKEN_URL: config.oidc.TOKEN_URL TOKEN_URL: config.oidc.TOKEN_URL,
}; };
} }
return oidcConfig; return oidcConfig;
} }
export async function hasCapability(capability: Capabilities[number]): Promise<boolean> { export async function hasCapability(
capability: Capabilities[number],
): Promise<boolean> {
const caps = await getCapabilities(); const caps = await getCapabilities();
return caps.includes(capability); return caps.includes(capability);
} }
export type Review = { export interface Review {
user_id: string; user_id: string;
username: string; username: string;
rating: number; rating: number;
comment: string; comment: string;
} }
export async function getReviews(location: WorldLocation) { export async function getReviews(location: WorldLocation) {
if(!await hasCapability("reviews")) { if (!(await hasCapability("reviews"))) {
throw new Error("Reviews capability is not available"); throw new Error("Reviews capability is not available");
} }
const res = await fetch(LNV_SERVER + `/reviews?lat=${location.lat}&lon=${location.lon}`); const res = await fetch(
LNV_SERVER + `/reviews?lat=${location.lat}&lon=${location.lon}`,
);
if (!res.ok) { if (!res.ok) {
throw new Error(`Failed to fetch reviews: ${res.statusText}`); throw new Error(`Failed to fetch reviews: ${res.statusText}`);
} }
@ -59,8 +72,11 @@ export async function getReviews(location: WorldLocation) {
return data as Review[]; return data as Review[];
} }
export async function postReview(location: WorldLocation, review: Omit<Review, 'user_id' | 'username'>) { export async function postReview(
if(!await hasCapability("reviews")) { location: WorldLocation,
review: Omit<Review, "user_id" | "username">,
) {
if (!(await hasCapability("reviews"))) {
throw new Error("Reviews capability is not available"); throw new Error("Reviews capability is not available");
} }
const token = localStorage.getItem("lnv-token"); const token = localStorage.getItem("lnv-token");
@ -71,13 +87,13 @@ export async function postReview(location: WorldLocation, review: Omit<Review, '
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${token}` Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
...review, ...review,
lat: location.lat, lat: location.lat,
lon: location.lon lon: location.lon,
}) }),
}); });
if (!res.ok) { if (!res.ok) {
throw new Error(`Failed to post review: ${res.statusText}`); throw new Error(`Failed to post review: ${res.statusText}`);
@ -86,7 +102,7 @@ export async function postReview(location: WorldLocation, review: Omit<Review, '
} }
export async function ai(query: string, location?: WorldLocation) { export async function ai(query: string, location?: WorldLocation) {
if(!await hasCapability("ai")) { if (!(await hasCapability("ai"))) {
throw new Error("AI capability is not available"); throw new Error("AI capability is not available");
} }
const res = await fetch(LNV_SERVER + `/ai`, { const res = await fetch(LNV_SERVER + `/ai`, {
@ -96,8 +112,8 @@ export async function ai(query: string, location?: WorldLocation) {
}, },
body: JSON.stringify({ body: JSON.stringify({
text: query, text: query,
coords: location coords: location,
}) }),
}); });
if (!res.ok) { if (!res.ok) {
throw new Error(`Failed to get AI response: ${res.statusText}`); throw new Error(`Failed to get AI response: ${res.statusText}`);

View File

@ -2,26 +2,30 @@
let { lane }: { lane: Lane } = $props(); let { lane }: { lane: Lane } = $props();
const knownDirections = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]; const knownDirections = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024];
async function fetchImage(bit: number) { async function fetchImage(bit: number) {
if (knownDirections.includes(bit)) { if (knownDirections.includes(bit)) {
return await fetch(`/img/lanes/${bit}.svg`).then(res => res.text()); return await fetch(`/img/lanes/${bit}.svg`).then((res) => res.text());
} else { } else {
return `<span>${bit}</span>`; return `<span>${bit}</span>`;
} }
} }
function loadImage(node: HTMLElement, bit: number) { function loadImage(node: HTMLElement, bit: number) {
fetchImage(bit).then(img => { fetchImage(bit).then((img) => {
node.innerHTML = img; node.innerHTML = img;
}); });
} }
</script> </script>
<div class="lane"> <div class="lane">
{#each Array(10).fill(0).map((_, i) => 1 << i) as bit} {#each Array(10)
.fill(0)
.map((_, i) => 1 << i) as bit (bit)}
{#if lane.directions & bit} {#if lane.directions & bit}
<div <div
class="lane-image {lane.valid & bit ? 'valid' : ''} {lane.active & bit ? 'active' : ''}" class="lane-image {lane.valid & bit ? 'valid' : ''} {lane.active & bit
? 'active'
: ''}"
use:loadImage={bit} use:loadImage={bit}
></div> ></div>
{/if} {/if}
@ -67,4 +71,4 @@
font-weight: bold; font-weight: bold;
color: #cc2c2c; color: #cc2c2c;
} }
</style> </style>

View File

@ -10,7 +10,7 @@ export async function displayLane(lane: Lane) {
// Check if the bit is in the known directions // Check if the bit is in the known directions
let img = ""; let img = "";
if (knownDirections.includes(bit)) { if (knownDirections.includes(bit)) {
img = await fetch(`/img/lanes/${bit}.svg`).then(res => res.text()); img = await fetch(`/img/lanes/${bit}.svg`).then((res) => res.text());
} else { } else {
img = `<span>${bit}</span>`; img = `<span>${bit}</span>`;
} }
@ -32,4 +32,4 @@ export async function displayLane(lane: Lane) {
} }
} }
return laneDiv; return laneDiv;
} }

View File

@ -6,7 +6,7 @@
{#if lanes} {#if lanes}
<div id="lanes"> <div id="lanes">
{#each lanes as lane} {#each lanes as lane (lane)}
<LaneDisplay {lane} /> <LaneDisplay {lane} />
{/each} {/each}
</div> </div>
@ -19,4 +19,4 @@
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
</style> </style>

View File

@ -43,4 +43,4 @@ export const maneuverTypes = [
"escalatorEnter", // ??? "escalatorEnter", // ???
"buildingEnter", // ??? "buildingEnter", // ???
"buildingExit", // ??? "buildingExit", // ???
]; ];

View File

@ -1,19 +1,38 @@
/* eslint-disable no-empty */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/prefer-for-of */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck // @ts-nocheck
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
// import { maneuverTypes } from "./Maneuver"; // import { maneuverTypes } from "./Maneuver";
import { hideRouteStatus, updateRouteStatus } from "../../components/routestatus"; import {
import { NavigationLayer, removeAllNavigationLayers, updateNavigationLayer } from "./NavigationLayers"; hideRouteStatus,
updateRouteStatus,
} from "../../components/routestatus";
import {
NavigationLayer,
removeAllNavigationLayers,
updateNavigationLayer,
} from "./NavigationLayers";
import { updateMapPadding } from "../../main"; import { updateMapPadding } from "../../main";
import say from "../TTS"; import say from "../TTS";
import { ROUTING_SERVER } from "../servers"; import { ROUTING_SERVER } from "../servers";
import { createValhallaRequest } from "./ValhallaRequest"; import { createValhallaRequest } from "./ValhallaRequest";
import { Vehicle } from "../../components/vehicles"; import { Vehicle } from "../../components/vehicles";
import { KeepAwake } from "@capacitor-community/keep-awake"; import { KeepAwake } from "@capacitor-community/keep-awake";
import { getCurrentViewName, getSidebarView } from "../../components/sidebar/SidebarRegistry"; import {
getCurrentViewName,
getSidebarView,
} from "../../components/sidebar/SidebarRegistry";
// import { displayLane } from "./LaneDisplay"; // import { displayLane } from "./LaneDisplay";
export async function fetchRoute(vehicle: Vehicle, from: WorldLocation, to: WorldLocation): Promise<RouteResult> { export async function fetchRoute(
vehicle: Vehicle,
from: WorldLocation,
to: WorldLocation,
): Promise<RouteResult> {
// const req = { // const req = {
// locations: [ // locations: [
// from, // from,
@ -35,7 +54,7 @@ export async function fetchRoute(vehicle: Vehicle, from: WorldLocation, to: Worl
const res = await fetch( const res = await fetch(
ROUTING_SERVER + "/route?json=" + JSON.stringify(req), ROUTING_SERVER + "/route?json=" + JSON.stringify(req),
).then((res) => res.json()); ).then((res) => res.json());
console.log(res); console.log(res);
return res; return res;
} catch (e) { } catch (e) {
@ -65,7 +84,11 @@ function drawRoute(trip: Trip, name: NavigationLayer) {
updateNavigationLayer(name, geometry); updateNavigationLayer(name, geometry);
} }
export async function findRoute(vehicle: Vehicle, from: WorldLocation, to: WorldLocation): Promise<Trip[]> { export async function findRoute(
vehicle: Vehicle,
from: WorldLocation,
to: WorldLocation,
): Promise<Trip[]> {
fromMarker = new maplibregl.Marker() fromMarker = new maplibregl.Marker()
.setLngLat([from.lon, from.lat]) .setLngLat([from.lon, from.lat])
.addTo(window.glmap); .addTo(window.glmap);
@ -75,10 +98,10 @@ export async function findRoute(vehicle: Vehicle, from: WorldLocation, to: World
const route = await fetchRoute(vehicle, from, to); const route = await fetchRoute(vehicle, from, to);
let routes = [route.trip]; let routes = [route.trip];
if(route.alternates) { if (route.alternates) {
for(let i = 0; i < route.alternates.length; i++) { for (let i = 0; i < route.alternates.length; i++) {
routes.push(route.alternates[i].trip); routes.push(route.alternates[i].trip);
} }
} }
drawAllRoutes(routes); drawAllRoutes(routes);
@ -117,7 +140,11 @@ function getUserLocation(): WorldLocation {
let pastRoute: WorldLocation[] = []; let pastRoute: WorldLocation[] = [];
// Check if the location is on the line between from and to (3 meter tolerance) // Check if the location is on the line between from and to (3 meter tolerance)
function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocation) { function isOnLine(
location: WorldLocation,
from: WorldLocation,
to: WorldLocation,
) {
// Convert the 6-meter tolerance to degrees (approximation) // Convert the 6-meter tolerance to degrees (approximation)
const tolerance = 6 / 111320; // 1 degree latitude ≈ 111.32 km const tolerance = 6 / 111320; // 1 degree latitude ≈ 111.32 km
@ -126,7 +153,9 @@ function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocatio
const dy = to.lat - from.lat; const dy = to.lat - from.lat;
// Calculate the projection of the location onto the line segment // Calculate the projection of the location onto the line segment
const t = ((location.lon - from.lon) * dx + (location.lat - from.lat) * dy) / (dx * dx + dy * dy); const t =
((location.lon - from.lon) * dx + (location.lat - from.lat) * dy) /
(dx * dx + dy * dy);
// Clamp t to the range [0, 1] to ensure the projection is on the segment // Clamp t to the range [0, 1] to ensure the projection is on the segment
const clampedT = Math.max(0, Math.min(1, t)); const clampedT = Math.max(0, Math.min(1, t));
@ -139,7 +168,8 @@ function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocatio
// Calculate the distance from the location to the closest point // Calculate the distance from the location to the closest point
const distance = Math.sqrt( const distance = Math.sqrt(
Math.pow(location.lon - closestPoint.lon, 2) + Math.pow(location.lat - closestPoint.lat, 2) Math.pow(location.lon - closestPoint.lon, 2) +
Math.pow(location.lat - closestPoint.lat, 2),
); );
// Check if the distance is within the tolerance // Check if the distance is within the tolerance
@ -175,9 +205,9 @@ export async function startNavigation(trip: Trip) {
// @ts-ignore The types are not correct // @ts-ignore The types are not correct
int = setInterval(() => { int = setInterval(() => {
if(instructionIdx != 0) { if (instructionIdx != 0) {
// Only continue if the user location is at the end shape index of the current maneuver // Only continue if the user location is at the end shape index of the current maneuver
if(currentManeuver == null) { if (currentManeuver == null) {
return; return;
} }
const bgi = currentManeuver.begin_shape_index; const bgi = currentManeuver.begin_shape_index;
@ -212,7 +242,7 @@ export async function startNavigation(trip: Trip) {
updateRouteStatus({ updateRouteStatus({
time: trip.summary.time, time: trip.summary.time,
distance: trip.summary.length, distance: trip.summary.length,
currentManeuver currentManeuver,
}); });
} }
@ -228,7 +258,7 @@ export async function startNavigation(trip: Trip) {
// pastRoute.push(...polyline.slice(0, bgi + 1)); // pastRoute.push(...polyline.slice(0, bgi + 1));
pastRoute = polyline.slice(0, bgi + 1); pastRoute = polyline.slice(0, bgi + 1);
updateNavigationLayer("route-past", pastRoute.flat()) updateNavigationLayer("route-past", pastRoute.flat());
// Remove from shape begin to end from the route line // Remove from shape begin to end from the route line
const newShape = polyline.slice(bgi); const newShape = polyline.slice(bgi);
@ -242,11 +272,14 @@ export async function startNavigation(trip: Trip) {
return; return;
} }
const maneuver = trip.legs[0].maneuvers[instructionIdx]; const maneuver = trip.legs[0].maneuvers[instructionIdx];
updateRouteStatus({ updateRouteStatus(
time: trip.summary.time, {
distance: trip.summary.length, time: trip.summary.time,
currentManeuver: trip.legs[0].maneuvers[instructionIdx], distance: trip.summary.length,
}, maneuver.lanes); currentManeuver: trip.legs[0].maneuvers[instructionIdx],
},
maneuver.lanes,
);
currentManeuver = maneuver; currentManeuver = maneuver;
// document.querySelector<HTMLDivElement>("#lanes")!.innerHTML = ""; // document.querySelector<HTMLDivElement>("#lanes")!.innerHTML = "";
@ -266,22 +299,27 @@ export async function startNavigation(trip: Trip) {
if (instructionIdx > 0) { if (instructionIdx > 0) {
const prevManeuver = trip.legs[0].maneuvers[instructionIdx - 1]; const prevManeuver = trip.legs[0].maneuvers[instructionIdx - 1];
if (prevManeuver.verbal_post_transition_instruction) { if (prevManeuver.verbal_post_transition_instruction) {
console.log("Saying: " + prevManeuver.verbal_post_transition_instruction); console.log(
"Saying: " + prevManeuver.verbal_post_transition_instruction,
);
say(prevManeuver.verbal_post_transition_instruction); say(prevManeuver.verbal_post_transition_instruction);
} }
} }
}, 1000); }, 1000);
updateRouteStatus({ updateRouteStatus(
time: trip.summary.time, {
distance: trip.summary.length, time: trip.summary.time,
currentManeuver: trip.legs[0].maneuvers[0], distance: trip.summary.length,
}, trip.legs[0].maneuvers[0].lanes); currentManeuver: trip.legs[0].maneuvers[0],
},
trip.legs[0].maneuvers[0].lanes,
);
currentTrip = trip; currentTrip = trip;
} }
export async function stopNavigation() { export async function stopNavigation() {
if(int) clearInterval(int); if (int) clearInterval(int);
await KeepAwake.allowSleep(); await KeepAwake.allowSleep();
hideRouteStatus(); hideRouteStatus();
document.querySelector<HTMLBodyElement>("body")!.classList.remove("isInTrip"); document.querySelector<HTMLBodyElement>("body")!.classList.remove("isInTrip");
@ -320,7 +358,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
shift += 5; shift += 5;
} while (byte >= 0x20); } while (byte >= 0x20);
let deltaLat = (result & 1) ? ~(result >> 1) : (result >> 1); let deltaLat = result & 1 ? ~(result >> 1) : result >> 1;
lat += deltaLat; lat += deltaLat;
shift = 0; shift = 0;
@ -331,7 +369,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
shift += 5; shift += 5;
} while (byte >= 0x20); } while (byte >= 0x20);
let deltaLng = (result & 1) ? ~(result >> 1) : (result >> 1); let deltaLng = result & 1 ? ~(result >> 1) : result >> 1;
lng += deltaLng; lng += deltaLng;
// Convert the latitude and longitude to decimal format with six digits of precision // Convert the latitude and longitude to decimal format with six digits of precision

View File

@ -8,7 +8,7 @@ export type ValhallaCosting =
| "motor_scooter" | "motor_scooter"
| "multimodal" | "multimodal"
| "pedestrian"; | "pedestrian";
export type ValhallaRequest = { export interface ValhallaRequest {
locations: WorldLocation[]; locations: WorldLocation[];
costing: ValhallaCosting; costing: ValhallaCosting;
units: "miles" | "kilometers"; units: "miles" | "kilometers";
@ -16,8 +16,8 @@ export type ValhallaRequest = {
alternates: number; alternates: number;
costing_options: ValhallaCostingOptions; costing_options: ValhallaCostingOptions;
turn_lanes: boolean; turn_lanes: boolean;
}; }
export type GeneralCostingOptions = { export interface GeneralCostingOptions {
/** /**
* A penalty applied when transitioning between roads that do not have consistent * A penalty applied when transitioning between roads that do not have consistent
* naming - in other words, no road names in common. This penalty can be used to * naming - in other words, no road names in common. This penalty can be used to
@ -59,7 +59,7 @@ export type GeneralCostingOptions = {
* @default 0 for trucks, 15 for cars, buses, motor scooters and motorcycles * @default 0 for trucks, 15 for cars, buses, motor scooters and motorcycles
*/ */
service_penalty?: number; service_penalty?: number;
}; }
export type AutomobileCostingOptions = { export type AutomobileCostingOptions = {
/** /**
* A penalty applied when a gate or bollard with access=private is encountered. * A penalty applied when a gate or bollard with access=private is encountered.
@ -226,7 +226,7 @@ export type AutomobileCostingOptions = {
*/ */
hierarchy_limits?: void; hierarchy_limits?: void;
} & GeneralCostingOptions; } & GeneralCostingOptions;
export type OtherCostingOptions = { export interface OtherCostingOptions {
/** /**
* The height of the vehicle (in meters). * The height of the vehicle (in meters).
* @default 1.9 for car, bus, taxi and 4.11 for truck * @default 1.9 for car, bus, taxi and 4.11 for truck
@ -267,7 +267,7 @@ export type OtherCostingOptions = {
* @default false * @default false
*/ */
include_hot?: boolean; include_hot?: boolean;
}; }
/** /**
* The type of the bicycle. * The type of the bicycle.
* Road: a road-style bicycle with narrow tires that is generally lightweight and designed for speed on paved surfaces. * Road: a road-style bicycle with narrow tires that is generally lightweight and designed for speed on paved surfaces.
@ -276,7 +276,7 @@ export type OtherCostingOptions = {
* Mountain: a mountain bicycle suitable for most surfaces but generally heavier and slower on paved surfaces. * Mountain: a mountain bicycle suitable for most surfaces but generally heavier and slower on paved surfaces.
*/ */
export type BicycleType = "Road" | "Hybrid" | "City" | "Mountain"; export type BicycleType = "Road" | "Hybrid" | "City" | "Mountain";
export type BicycleCostingOptions = { export interface BicycleCostingOptions {
/** /**
* @default "Hybrid" * @default "Hybrid"
*/ */
@ -367,9 +367,8 @@ export type BicycleCostingOptions = {
* @default false * @default false
*/ */
shortest?: boolean; shortest?: boolean;
}
}; export type BikeshareCostingOptions = unknown; // TODO
export type BikeshareCostingOptions = {}; // TODO
export type MotorScooterCostingOptions = { export type MotorScooterCostingOptions = {
/** /**
* A rider's propensity to use primary roads. * A rider's propensity to use primary roads.
@ -398,9 +397,9 @@ export type MotorScooterCostingOptions = {
*/ */
use_hills?: boolean; use_hills?: boolean;
} & AutomobileCostingOptions; } & AutomobileCostingOptions;
export type MultimodalCostingOptions = {}; // TODO export type MultimodalCostingOptions = unknown; // TODO
export type PedestrianCostingOptions = {}; // TODO export type PedestrianCostingOptions = unknown; // TODO
export type TruckCostingOptions = { export interface TruckCostingOptions {
/** /**
* The length of the truck (in meters). * The length of the truck (in meters).
* @default 21.64 * @default 21.64
@ -448,8 +447,8 @@ export type TruckCostingOptions = {
* @default 0 * @default 0
*/ */
use_truck_route?: boolean; use_truck_route?: boolean;
}; }
export type ValhallaCostingOptions = { export interface ValhallaCostingOptions {
auto?: AutomobileCostingOptions & OtherCostingOptions; auto?: AutomobileCostingOptions & OtherCostingOptions;
bicycle?: BicycleCostingOptions; bicycle?: BicycleCostingOptions;
bus?: AutomobileCostingOptions & OtherCostingOptions; bus?: AutomobileCostingOptions & OtherCostingOptions;
@ -459,4 +458,4 @@ export type ValhallaCostingOptions = {
motor_scooter?: MotorScooterCostingOptions; motor_scooter?: MotorScooterCostingOptions;
multimodal?: MultimodalCostingOptions; multimodal?: MultimodalCostingOptions;
pedestrian?: PedestrianCostingOptions; pedestrian?: PedestrianCostingOptions;
}; }

View File

@ -1,15 +1,18 @@
type Language = "de-DE" | "en-US"; type Language = "de-DE" | "en-US";
type WorldLocation = { lat: number; lon: number }; interface WorldLocation {
lat: number;
lon: number;
}
type Units = "kilometers" | "miles"; type Units = "kilometers" | "miles";
type RouteResult = { interface RouteResult {
alternates?: { alternates?: {
trip: Trip; trip: Trip;
}[]; }[];
trip: Trip; trip: Trip;
} }
type Trip = { interface Trip {
language: Language; language: Language;
legs: Leg[]; legs: Leg[];
status: number; status: number;
@ -17,16 +20,16 @@ type Trip = {
summary: Summary; summary: Summary;
units: Units; units: Units;
locations: WorldLocation[]; locations: WorldLocation[];
}; }
type Leg = { interface Leg {
maneuvers: Maneuver[]; maneuvers: Maneuver[];
shape: string; shape: string;
summary: Summary; summary: Summary;
locations: WorldLocation[]; locations: WorldLocation[];
} }
type Summary = { interface Summary {
cost: number; cost: number;
has_ferry: boolean; has_ferry: boolean;
has_highway: boolean; has_highway: boolean;
@ -55,13 +58,13 @@ type Summary = {
* 512 = MergeToLeft * 512 = MergeToLeft
* 1024 = MergeToRight * 1024 = MergeToRight
*/ */
type Lane = { interface Lane {
directions: number; directions: number;
valid: number; valid: number;
active: number; active: number;
}; }
type Maneuver = { interface Maneuver {
bearing_after: number; bearing_after: number;
begin_shape_index: number; begin_shape_index: number;
cost: number; cost: number;
@ -78,4 +81,4 @@ type Maneuver = {
verbal_pre_transition_instruction: string; verbal_pre_transition_instruction: string;
verbal_succinct_transition_instruction: string; verbal_succinct_transition_instruction: string;
lanes?: Lane[]; lanes?: Lane[];
}; }

View File

@ -18,8 +18,8 @@ export const routing = $state({
int: null as NodeJS.Timeout | null, int: null as NodeJS.Timeout | null,
isOffRoute: false, isOffRoute: false,
currentManeuver: null as Maneuver | null, currentManeuver: null as Maneuver | null,
} },
}) });
export function resetRouting() { export function resetRouting() {
routing.geojson.route = null; routing.geojson.route = null;
@ -30,9 +30,10 @@ export function resetRouting() {
export async function fetchRoute(server: string, request: ValhallaRequest) { export async function fetchRoute(server: string, request: ValhallaRequest) {
try { try {
const res = await fetch(server + "/route?json=" + JSON.stringify(request)) const res = await fetch(
.then((res) => res.json()); server + "/route?json=" + JSON.stringify(request),
).then((res) => res.json());
console.log(res); console.log(res);
return res; return res;
} catch (error) { } catch (error) {
@ -65,9 +66,9 @@ function geometryToGeoJSON(polyline: WorldLocation[]): GeoJSON.Feature {
} }
export function decodePolyline(encoded: string): WorldLocation[] { export function decodePolyline(encoded: string): WorldLocation[] {
let points = []; const points = [];
let index = 0; let index = 0;
let len = encoded.length; const len = encoded.length;
let lat = 0; let lat = 0;
let lng = 0; let lng = 0;
@ -81,7 +82,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
shift += 5; shift += 5;
} while (byte >= 0x20); } while (byte >= 0x20);
let deltaLat = (result & 1) ? ~(result >> 1) : (result >> 1); const deltaLat = result & 1 ? ~(result >> 1) : result >> 1;
lat += deltaLat; lat += deltaLat;
shift = 0; shift = 0;
@ -92,7 +93,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
shift += 5; shift += 5;
} while (byte >= 0x20); } while (byte >= 0x20);
let deltaLng = (result & 1) ? ~(result >> 1) : (result >> 1); const deltaLng = result & 1 ? ~(result >> 1) : result >> 1;
lng += deltaLng; lng += deltaLng;
// Convert the latitude and longitude to decimal format with six digits of precision // Convert the latitude and longitude to decimal format with six digits of precision
@ -108,8 +109,8 @@ export function decodePolyline(encoded: string): WorldLocation[] {
export function drawAllRoutes(trips: Trip[]) { export function drawAllRoutes(trips: Trip[]) {
routing.geojson.routePast = null; routing.geojson.routePast = null;
routing.geojson.route = tripToGeoJSON(trips[0]); routing.geojson.route = tripToGeoJSON(trips[0]);
if(trips[1]) routing.geojson.al0 = tripToGeoJSON(trips[1]); if (trips[1]) routing.geojson.al0 = tripToGeoJSON(trips[1]);
if(trips[2]) routing.geojson.al1 = tripToGeoJSON(trips[2]); if (trips[2]) routing.geojson.al1 = tripToGeoJSON(trips[2]);
} }
export function drawRoute(trip: Trip) { export function drawRoute(trip: Trip) {
@ -119,7 +120,9 @@ export function drawRoute(trip: Trip) {
function drawCurrentTrip() { function drawCurrentTrip() {
if (!routing.currentTrip) return; if (!routing.currentTrip) return;
routing.geojson.route = geometryToGeoJSON(routing.currentTripInfo.route); routing.geojson.route = geometryToGeoJSON(routing.currentTripInfo.route);
routing.geojson.routePast = geometryToGeoJSON(routing.currentTripInfo.past.flat()); routing.geojson.routePast = geometryToGeoJSON(
routing.currentTripInfo.past.flat(),
);
} }
export async function startRoute(trip: Trip) { export async function startRoute(trip: Trip) {
@ -131,7 +134,8 @@ export async function startRoute(trip: Trip) {
routing.currentTripInfo.isOffRoute = false; routing.currentTripInfo.isOffRoute = false;
drawRoute(trip); drawRoute(trip);
routing.currentTripInfo.currentManeuver = routing.currentTrip.legs[0].maneuvers[0]; routing.currentTripInfo.currentManeuver =
routing.currentTrip.legs[0].maneuvers[0];
routing.currentTripInfo.int = setInterval(tickRoute, 500); routing.currentTripInfo.int = setInterval(tickRoute, 500);
} }
@ -143,7 +147,8 @@ async function tickRoute() {
const info = routing.currentTripInfo; const info = routing.currentTripInfo;
if (!trip) return; if (!trip) return;
const currentManeuver = trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx]; const currentManeuver =
trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx];
if (!currentManeuver) { if (!currentManeuver) {
// No more maneuvers, stop navigation // No more maneuvers, stop navigation
stopNavigation(); stopNavigation();
@ -155,14 +160,14 @@ async function tickRoute() {
const polyline = decodePolyline(trip.legs[0].shape); const polyline = decodePolyline(trip.legs[0].shape);
// Check if the user location is on the last point of the entire route // Check if the user location is on the last point of the entire route
if(isOnPoint(location, polyline[polyline.length - 1])) { if (isOnPoint(location, polyline[polyline.length - 1])) {
console.log("Reached destination!"); console.log("Reached destination!");
stopNavigation(); stopNavigation();
return; return;
} }
// Check if the user is on the route // Check if the user is on the route
if(!isOnShape(location, polyline)) { if (!isOnShape(location, polyline)) {
console.log("Off route!"); console.log("Off route!");
info.isOffRoute = true; info.isOffRoute = true;
// TODO: Implement re-routing logic // TODO: Implement re-routing logic
@ -171,18 +176,24 @@ async function tickRoute() {
info.isOffRoute = false; info.isOffRoute = false;
} }
if (currentManeuver.verbal_pre_transition_instruction && !hasAnnouncedPreInstruction) { if (
currentManeuver.verbal_pre_transition_instruction &&
!hasAnnouncedPreInstruction
) {
const distanceToEnd = calculateDistance(location, polyline[bgi]); const distanceToEnd = calculateDistance(location, polyline[bgi]);
// console.log("Distance to end of current maneuver: ", distanceToEnd, " meters"); // console.log("Distance to end of current maneuver: ", distanceToEnd, " meters");
if (distanceToEnd <= 100) { if (distanceToEnd <= 100) {
hasAnnouncedPreInstruction = true; hasAnnouncedPreInstruction = true;
console.log("[Verbal instruction] ", currentManeuver.verbal_pre_transition_instruction); console.log(
"[Verbal instruction] ",
currentManeuver.verbal_pre_transition_instruction,
);
} }
} }
// Check if the user is past the current maneuver // Check if the user is past the current maneuver
// Checks if the user is still on the current maneuver's polyline // Checks if the user is still on the current maneuver's polyline
if(!isOnShape(location, polyline.slice(bgi))) { if (!isOnShape(location, polyline.slice(bgi))) {
return; // User is not on the current maneuver's polyline, do not update return; // User is not on the current maneuver's polyline, do not update
} }
@ -196,15 +207,25 @@ async function tickRoute() {
// announce the "verbal_post_transition_instruction" // announce the "verbal_post_transition_instruction"
if (currentManeuver.verbal_post_transition_instruction) { if (currentManeuver.verbal_post_transition_instruction) {
hasAnnouncedPreInstruction = false; hasAnnouncedPreInstruction = false;
const distanceToEnd = calculateDistance(location, polyline[trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx + 1].begin_shape_index]); const distanceToEnd = calculateDistance(
location,
polyline[
trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx + 1]
.begin_shape_index
],
);
if (distanceToEnd >= 200) { if (distanceToEnd >= 200) {
console.log("[Verbal instruction] ", currentManeuver.verbal_post_transition_instruction); console.log(
"[Verbal instruction] ",
currentManeuver.verbal_post_transition_instruction,
);
} }
} }
// Advance to the next maneuver // Advance to the next maneuver
info.maneuverIdx++; info.maneuverIdx++;
if(info.maneuverIdx >= trip.legs[0].maneuvers.length) { // No more maneuvers if (info.maneuverIdx >= trip.legs[0].maneuvers.length) {
// No more maneuvers
stopNavigation(); stopNavigation();
return; return;
} }
@ -228,8 +249,8 @@ function getUserLocation(): WorldLocation {
// return geolocate.currentLocation!; // return geolocate.currentLocation!;
return { return {
lat: location.lat, lat: location.lat,
lon: location.lng lon: location.lng,
} };
// const lnglat = window.geolocate._userLocationDotMarker.getLngLat(); // const lnglat = window.geolocate._userLocationDotMarker.getLngLat();
// return { lat: lnglat.lat, lon: lnglat.lng }; // return { lat: lnglat.lat, lon: lnglat.lng };
// console.log(map.value!) // console.log(map.value!)
@ -239,7 +260,11 @@ function getUserLocation(): WorldLocation {
// } // }
} }
function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocation) { function isOnLine(
location: WorldLocation,
from: WorldLocation,
to: WorldLocation,
) {
// Convert the 12-meter tolerance to degrees (approximation) // Convert the 12-meter tolerance to degrees (approximation)
const tolerance = 12 / 111320; // 1 degree latitude ≈ 111.32 km const tolerance = 12 / 111320; // 1 degree latitude ≈ 111.32 km
@ -248,7 +273,9 @@ function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocatio
const dy = to.lat - from.lat; const dy = to.lat - from.lat;
// Calculate the projection of the location onto the line segment // Calculate the projection of the location onto the line segment
const t = ((location.lon - from.lon) * dx + (location.lat - from.lat) * dy) / (dx * dx + dy * dy); const t =
((location.lon - from.lon) * dx + (location.lat - from.lat) * dy) /
(dx * dx + dy * dy);
// Clamp t to the range [0, 1] to ensure the projection is on the segment // Clamp t to the range [0, 1] to ensure the projection is on the segment
const clampedT = Math.max(0, Math.min(1, t)); const clampedT = Math.max(0, Math.min(1, t));
@ -261,7 +288,8 @@ function isOnLine(location: WorldLocation, from: WorldLocation, to: WorldLocatio
// Calculate the distance from the location to the closest point // Calculate the distance from the location to the closest point
const distance = Math.sqrt( const distance = Math.sqrt(
Math.pow(location.lon - closestPoint.lon, 2) + Math.pow(location.lat - closestPoint.lat, 2) Math.pow(location.lon - closestPoint.lon, 2) +
Math.pow(location.lat - closestPoint.lat, 2),
); );
// Check if the distance is within the tolerance // Check if the distance is within the tolerance
@ -273,7 +301,8 @@ function isOnPoint(location: WorldLocation, point: WorldLocation) {
const tolerance = 6 / 111320; // 1 degree latitude ≈ 111.32 km const tolerance = 6 / 111320; // 1 degree latitude ≈ 111.32 km
// Calculate the distance from the location to the point // Calculate the distance from the location to the point
const distance = Math.sqrt( const distance = Math.sqrt(
Math.pow(location.lon - point.lon, 2) + Math.pow(location.lat - point.lat, 2) Math.pow(location.lon - point.lon, 2) +
Math.pow(location.lat - point.lat, 2),
); );
// Check if the distance is within the tolerance // Check if the distance is within the tolerance
return distance <= tolerance; return distance <= tolerance;
@ -289,34 +318,47 @@ function isOnShape(location: WorldLocation, shape: WorldLocation[]) {
return false; return false;
} }
function calculateDistance(point1: WorldLocation, point2: WorldLocation): number { function calculateDistance(
point1: WorldLocation,
point2: WorldLocation,
): number {
const R = 6371000; // Earth's radius in meters const R = 6371000; // Earth's radius in meters
const lat1 = point1.lat * (Math.PI / 180); const lat1 = point1.lat * (Math.PI / 180);
const lat2 = point2.lat * (Math.PI / 180); const lat2 = point2.lat * (Math.PI / 180);
const deltaLat = (point2.lat - point1.lat) * (Math.PI / 180); const deltaLat = (point2.lat - point1.lat) * (Math.PI / 180);
const deltaLon = (point2.lon - point1.lon) * (Math.PI / 180); const deltaLon = (point2.lon - point1.lon) * (Math.PI / 180);
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + const a =
Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); Math.cos(lat1) *
Math.cos(lat2) *
Math.sin(deltaLon / 2) *
Math.sin(deltaLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in meters return R * c; // Distance in meters
} }
export function zoomToPoints(from: WorldLocation, to: WorldLocation, map: maplibregl.Map) { export function zoomToPoints(
const getBoundingBox = (point1: [number, number], point2: [number, number]): LngLatBoundsLike => { from: WorldLocation,
to: WorldLocation,
map: maplibregl.Map,
) {
const getBoundingBox = (
point1: [number, number],
point2: [number, number],
): LngLatBoundsLike => {
const [lng1, lat1] = point1; const [lng1, lat1] = point1;
const [lng2, lat2] = point2; const [lng2, lat2] = point2;
const sw = [Math.min(lng1, lng2), Math.min(lat1, lat2)] as [number, number]; const sw = [Math.min(lng1, lng2), Math.min(lat1, lat2)] as [number, number];
const ne = [Math.max(lng1, lng2), Math.max(lat1, lat2)] as [number, number]; const ne = [Math.max(lng1, lng2), Math.max(lat1, lat2)] as [number, number];
return [sw, ne]; return [sw, ne];
}; };
map.fitBounds(getBoundingBox([from.lon, from.lat], [to.lon, to.lat]), { map.fitBounds(getBoundingBox([from.lon, from.lat], [to.lon, to.lat]), {
padding: 40 padding: 40,
}); });
} }

View File

@ -3,7 +3,7 @@
import { getOIDCConfig, hasCapability } from "./lnv"; import { getOIDCConfig, hasCapability } from "./lnv";
export async function getAuthURL() { export async function getAuthURL() {
if(!await hasCapability("auth")) { if (!(await hasCapability("auth"))) {
throw new Error("Server does not support OIDC authentication"); throw new Error("Server does not support OIDC authentication");
} }
const oidcConfig = await getOIDCConfig(); const oidcConfig = await getOIDCConfig();
@ -17,8 +17,7 @@ export async function getAuthURL() {
const state = generateRandomString(16); const state = generateRandomString(16);
return { return {
url: url: `${AUTH_URL}?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${window.location.origin}/login/callback&scope=openid%20profile&code_challenge=${pkce.codeChallenge}&code_challenge_method=S256&state=${state}`,
`${AUTH_URL}?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${window.location.origin}/login/callback&scope=openid%20profile&code_challenge=${pkce.codeChallenge}&code_challenge_method=S256&state=${state}`,
codeVerifier: pkce.codeVerifier, codeVerifier: pkce.codeVerifier,
state, state,
}; };
@ -41,7 +40,9 @@ function generateRandomString(length: number) {
window.crypto.getRandomValues(array); window.crypto.getRandomValues(array);
return Array.from(array, (dec) => ("0" + dec.toString(16)).substr(-2)).join(""); return Array.from(array, (dec) => ("0" + dec.toString(16)).substr(-2)).join(
"",
);
} }
// Encodes a string to base64url (no padding) // Encodes a string to base64url (no padding)
@ -60,7 +61,7 @@ async function sha256(input: string | undefined): Promise<ArrayBuffer> {
} }
export async function getOIDCUser(code: string, codeVerifier: string) { export async function getOIDCUser(code: string, codeVerifier: string) {
if(!await hasCapability("auth")) { if (!(await hasCapability("auth"))) {
throw new Error("Server does not support OIDC authentication"); throw new Error("Server does not support OIDC authentication");
} }
const oidcConfig = await getOIDCConfig(); const oidcConfig = await getOIDCConfig();
@ -81,8 +82,8 @@ export async function getOIDCUser(code: string, codeVerifier: string) {
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: params body: params,
}).then(res => res.json()); }).then((res) => res.json());
return res; return res;
// return JSON.parse(atob(id_token.split(".")[1])); // return JSON.parse(atob(id_token.split(".")[1]));

View File

@ -8,6 +8,10 @@ export function cn(...inputs: ClassValue[]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T; export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T; export type WithoutChildren<T> = T extends { children?: any }
? Omit<T, "children">
: T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
ref?: U | null;
};

View File

@ -1,4 +1,8 @@
import type { ValhallaCosting, ValhallaCostingOptions, ValhallaRequest } from "$lib/services/navigation/ValhallaRequest"; import type {
ValhallaCosting,
ValhallaCostingOptions,
ValhallaRequest,
} from "$lib/services/navigation/ValhallaRequest";
import type { Vehicle } from "./vehicles.svelte"; import type { Vehicle } from "./vehicles.svelte";
function getVehicleCosting(vehicle: Vehicle): ValhallaCosting { function getVehicleCosting(vehicle: Vehicle): ValhallaCosting {
@ -17,31 +21,43 @@ function getVehicleCosting(vehicle: Vehicle): ValhallaCosting {
} }
} }
export function createValhallaRequest(vehicle: Vehicle, locations: WorldLocation[]): ValhallaRequest { export function createValhallaRequest(
vehicle: Vehicle,
locations: WorldLocation[],
): ValhallaRequest {
const costing = getVehicleCosting(vehicle); const costing = getVehicleCosting(vehicle);
let costingOptions: ValhallaCostingOptions = costing == "auto" ? { const costingOptions: ValhallaCostingOptions =
auto: { costing == "auto"
top_speed: vehicle.legalMaxSpeed, ? {
fixed_speed: vehicle.actualMaxSpeed auto: {
} top_speed: vehicle.legalMaxSpeed,
} : costing == "motor_scooter" ? { fixed_speed: vehicle.actualMaxSpeed,
motor_scooter: { },
top_speed: vehicle.legalMaxSpeed, }
fixed_speed: vehicle.actualMaxSpeed : costing == "motor_scooter"
} ? {
} : costing == "truck" ? { motor_scooter: {
truck: { top_speed: vehicle.legalMaxSpeed,
top_speed: vehicle.legalMaxSpeed, fixed_speed: vehicle.actualMaxSpeed,
fixed_speed: vehicle.actualMaxSpeed, },
length: vehicle.length, }
weight: vehicle.weight, : costing == "truck"
axle_load: vehicle.axisLoad ? {
} truck: {
} : costing == "bicycle" ? { top_speed: vehicle.legalMaxSpeed,
bicycle: { fixed_speed: vehicle.actualMaxSpeed,
cycling_speed: vehicle.actualMaxSpeed length: vehicle.length,
} weight: vehicle.weight,
} : {}; axle_load: vehicle.axisLoad,
},
}
: costing == "bicycle"
? {
bicycle: {
cycling_speed: vehicle.actualMaxSpeed,
},
}
: {};
return { return {
locations, locations,
costing, costing,
@ -49,6 +65,6 @@ export function createValhallaRequest(vehicle: Vehicle, locations: WorldLocation
alternates: 2, alternates: 2,
language: "de-DE", language: "de-DE",
costing_options: costingOptions, costing_options: costingOptions,
turn_lanes: true turn_lanes: true,
} };
} }

View File

@ -6,19 +6,42 @@ bicycle, prefer cycleways and bicycle lanes = bicycle
truck, prioritizes truck routes = truck truck, prioritizes truck routes = truck
motor_scooter = motor scooter, moped, lkfz motor_scooter = motor scooter, moped, lkfz
*/ */
export const VehicleTypes = ["car", "truck", "motorcycle", "bicycle", "motor_scooter"] as const; export const VehicleTypes = [
export type VehicleType = typeof VehicleTypes[number]; "car",
export const FuelTypes = ["petrol", "diesel", "electric"] as const; "truck",
export type FuelType = typeof FuelTypes[number]; "motorcycle",
export const EVConnectors = [ "bicycle",
"Type 2", "CCS", "CHAdeMO", "Tesla Supercharger", "Type 1", "Type 3", "motor_scooter",
"SEV 1011 (Type 13)", "SEV 1011 (Type 15)", "SEV 1011 (Type 23)", "SEV 1011 (Type 25)",
"CEE (red)", "CEE (blue)", "Schuko", "CCS Type 2", "Other"
] as const; ] as const;
export type EVConnector = typeof EVConnectors[number]; export type VehicleType = (typeof VehicleTypes)[number];
export const PreferredFuels = ["Super", "Super E10", "Diesel", ...EVConnectors] as const; export const FuelTypes = ["petrol", "diesel", "electric"] as const;
export type PreferredFuel = typeof PreferredFuels[number]; export type FuelType = (typeof FuelTypes)[number];
export type Vehicle = { export const EVConnectors = [
"Type 2",
"CCS",
"CHAdeMO",
"Tesla Supercharger",
"Type 1",
"Type 3",
"SEV 1011 (Type 13)",
"SEV 1011 (Type 15)",
"SEV 1011 (Type 23)",
"SEV 1011 (Type 25)",
"CEE (red)",
"CEE (blue)",
"Schuko",
"CCS Type 2",
"Other",
] as const;
export type EVConnector = (typeof EVConnectors)[number];
export const PreferredFuels = [
"Super",
"Super E10",
"Diesel",
...EVConnectors,
] as const;
export type PreferredFuel = (typeof PreferredFuels)[number];
export interface Vehicle {
name: string; name: string;
legalMaxSpeed: number; legalMaxSpeed: number;
actualMaxSpeed: number; actualMaxSpeed: number;
@ -31,7 +54,7 @@ export type Vehicle = {
emissionClass: string; emissionClass: string;
fuelType: FuelType; fuelType: FuelType;
preferredFuel: PreferredFuel; preferredFuel: PreferredFuel;
}; }
export const DefaultVehicle: Vehicle = { export const DefaultVehicle: Vehicle = {
name: "Default Vehicle", name: "Default Vehicle",
@ -40,49 +63,64 @@ export const DefaultVehicle: Vehicle = {
type: "motor_scooter", type: "motor_scooter",
emissionClass: "Euro 4", emissionClass: "Euro 4",
fuelType: "diesel", fuelType: "diesel",
preferredFuel: "Diesel" preferredFuel: "Diesel",
} };
type StateValue<T> = {v: T}; interface StateValue<T> {
export const vehicles: Vehicle[] = $state(localStorage.getItem("vehicles") ? JSON.parse(localStorage.getItem("vehicles")!) : []); v: T;
}
export const vehicles: Vehicle[] = $state(
localStorage.getItem("vehicles")
? JSON.parse(localStorage.getItem("vehicles")!)
: [],
);
export const selectedVehicleIdx: StateValue<number | null> = $state({ export const selectedVehicleIdx: StateValue<number | null> = $state({
v: localStorage.getItem("selectedVehicle") ? parseInt(localStorage.getItem("selectedVehicle")!) : null v: localStorage.getItem("selectedVehicle")
? parseInt(localStorage.getItem("selectedVehicle")!)
: null,
}); });
export const selectedVehicle: () => Vehicle | null = () => { export const selectedVehicle: () => Vehicle | null = () => {
return vehicles[selectedVehicleIdx.v !== null ? selectedVehicleIdx.v : 0] || null return (
vehicles[selectedVehicleIdx.v !== null ? selectedVehicleIdx.v : 0] || null
);
}; };
export function setVehicles(_vehicles: Vehicle[]) { export function setVehicles(_vehicles: Vehicle[]) {
// vehicles = _vehicles; // vehicles = _vehicles;
// Hack to update without reassigning the array // Hack to update without reassigning the array
vehicles.length = 0; vehicles.length = 0;
_vehicles.forEach(vehicle => vehicles.push(vehicle)); _vehicles.forEach((vehicle) => vehicles.push(vehicle));
localStorage.setItem("vehicles", JSON.stringify(vehicles)); localStorage.setItem("vehicles", JSON.stringify(vehicles));
} }
export function selectVehicle(vehicle: Vehicle | null) { export function selectVehicle(vehicle: Vehicle | null) {
if(vehicle == null) { if (vehicle == null) {
selectedVehicleIdx.v = null; selectedVehicleIdx.v = null;
} else { } else {
selectedVehicleIdx.v = vehicles.findIndex(v => v.name === vehicle.name); selectedVehicleIdx.v = vehicles.findIndex((v) => v.name === vehicle.name);
if(selectedVehicleIdx.v === -1) { if (selectedVehicleIdx.v === -1) {
selectedVehicleIdx.v = null; selectedVehicleIdx.v = null;
} }
} }
localStorage.setItem("selectedVehicle", selectedVehicleIdx.v !== null ? selectedVehicleIdx.v.toString() : ""); localStorage.setItem(
"selectedVehicle",
selectedVehicleIdx.v !== null ? selectedVehicleIdx.v.toString() : "",
);
} }
/** /**
* Check if the vehicle uses the correct preferred fuel type * Check if the vehicle uses the correct preferred fuel type
*/ */
export function isValidFuel(vehicle: Vehicle): boolean { export function isValidFuel(vehicle: Vehicle): boolean {
if(vehicle.fuelType == "petrol") { if (vehicle.fuelType == "petrol") {
return vehicle.preferredFuel == "Super" || vehicle.preferredFuel == "Super E10"; return (
vehicle.preferredFuel == "Super" || vehicle.preferredFuel == "Super E10"
);
} }
if(vehicle.fuelType == "diesel") { if (vehicle.fuelType == "diesel") {
return vehicle.preferredFuel == "Diesel"; return vehicle.preferredFuel == "Diesel";
} }
if(vehicle.fuelType == "electric") { if (vehicle.fuelType == "electric") {
return EVConnectors.includes(vehicle.preferredFuel as EVConnector); return EVConnectors.includes(vehicle.preferredFuel as EVConnector);
} }
return false; return false;

View File

@ -1,18 +1,22 @@
export function checkWebGL() { export function checkWebGL() {
if(window.WebGLRenderingContext) { if (window.WebGLRenderingContext) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
try { try {
const ctx = canvas.getContext("webgl2") || canvas.getContext("webgl"); const ctx = canvas.getContext("webgl2") || canvas.getContext("webgl");
if(ctx && typeof ctx.getParameter == "function") { if (ctx && typeof ctx.getParameter == "function") {
return true; return true;
} }
} catch(e) { } catch (_e) {
// Supported, but disabled // Supported, but disabled
alert("WebGL is supported but disabled in your browser. Please enable it in your settings.") alert(
"WebGL is supported but disabled in your browser. Please enable it in your settings.",
);
} }
return false; return false;
} }
// WebGL is not supported // WebGL is not supported
alert("WebGL is not supported in your browser. Please try a different browser."); alert(
"WebGL is not supported in your browser. Please try a different browser.",
);
return false; return false;
} }

View File

@ -1,21 +1,25 @@
import { mount } from 'svelte' import { mount } from "svelte";
import './app.css' import "./app.css";
import App from './App.svelte' import App from "./App.svelte";
if(location.href.includes("/login/callback")) { if (location.href.includes("/login/callback")) {
const url = new URL(location.href); const url = new URL(location.href);
const code = url.searchParams.get("code"); const code = url.searchParams.get("code");
const state = url.searchParams.get("state"); const state = url.searchParams.get("state");
if(code && state) { if (code && state) {
window.opener.postMessage({ window.opener.postMessage(
code, state {
}, window.location.origin); code,
state,
},
window.location.origin,
);
window.close(); window.close();
} }
} }
const app = mount(App, { const app = mount(App, {
target: document.getElementById('app')!, target: document.getElementById("app")!,
}) });
export default app export default app;

View File

@ -1,10 +1,10 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
export default { export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
compilerOptions: { compilerOptions: {
runes: true runes: true,
} },
} };

View File

@ -1,25 +1,25 @@
{ {
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"resolveJsonModule": true, "resolveJsonModule": true,
/** /**
* Typecheck JS in `.svelte` and `.js` files by default. * Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS. * Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use * Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files. * of JS in `.svelte` files.
*/ */
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"isolatedModules": true, "isolatedModules": true,
"moduleDetection": "force", "moduleDetection": "force",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"$lib": ["./src/lib"], "$lib": ["./src/lib"],
"$lib/*": ["./src/lib/*"] "$lib/*": ["./src/lib/*"]
} }
}, },
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
} }

View File

@ -1,14 +1,14 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }
], ],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"$lib": ["./src/lib"], "$lib": ["./src/lib"],
"$lib/*": ["./src/lib/*"] "$lib/*": ["./src/lib/*"]
} }
} }
} }

View File

@ -1,25 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022", "target": "ES2022",
"lib": ["ES2023"], "lib": ["ES2023"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }

View File

@ -1,13 +1,13 @@
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
import { svelte } from '@sveltejs/vite-plugin-svelte'; import { svelte } from "@sveltejs/vite-plugin-svelte";
import path from "path"; import path from "path";
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), svelte()], plugins: [tailwindcss(), svelte()],
resolve: { resolve: {
alias: { alias: {
$lib: path.resolve("./src/lib"), $lib: path.resolve("./src/lib"),
}, },
}, },
}); });