style: add eslint and prettier
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@ dist-ssr
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
|
||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
android/
|
||||
dist/
|
||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"eslint.validate": ["javascript", "typescript", "svelte"]
|
||||
}
|
||||
@ -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!).
|
||||
Mainstream navigation often routes these vehicles onto roads they're not legally allowed to use. **We don’t.**
|
||||
|
||||
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
259
bun.lock
@ -7,8 +7,13 @@
|
||||
"@capacitor/android": "^7.3.0",
|
||||
"@capacitor/cli": "^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",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
@ -19,13 +24,15 @@
|
||||
"@types/node": "^22.15.24",
|
||||
"bits-ui": "^2.7.0",
|
||||
"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",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vaul-svelte": "^1.0.0-next.7",
|
||||
"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=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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/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/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__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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
||||
@ -334,12 +407,18 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@ -370,6 +481,16 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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-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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@ -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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"@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=="],
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
import type { CapacitorConfig } from "@capacitor/cli";
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'de.trafficcue.trafficcue',
|
||||
appName: 'TrafficCue',
|
||||
webDir: 'dist'
|
||||
appId: "de.trafficcue.trafficcue",
|
||||
appName: "TrafficCue",
|
||||
webDir: "dist",
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
55
eslint.config.mjs
Normal file
55
eslint.config.mjs
Normal 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 don’t specify it,
|
||||
// explicitly specifying it ensures better compatibility and functionality.
|
||||
svelteConfig,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
);
|
||||
22
index.html
22
index.html
@ -1,14 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<title>TrafficCue</title>
|
||||
</head>
|
||||
<body class="dark">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<title>TrafficCue</title>
|
||||
</head>
|
||||
<body class="dark">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
79
package.json
79
package.json
@ -1,38 +1,45 @@
|
||||
{
|
||||
"name": "librenav",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/node": "^22.15.24",
|
||||
"bits-ui": "^2.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte-check": "^4.1.6",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"typescript": "~5.8.3",
|
||||
"vaul-svelte": "^1.0.0-next.7",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^7.3.0",
|
||||
"@capacitor/cli": "^7.3.0",
|
||||
"@capacitor/core": "^7.3.0",
|
||||
"opening_hours": "^3.8.0",
|
||||
"svelte-maplibre-gl": "^0.1.8"
|
||||
}
|
||||
"name": "librenav",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/node": "^22.15.24",
|
||||
"bits-ui": "^2.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.34.7",
|
||||
"svelte-check": "^4.1.6",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vaul-svelte": "^1.0.0-next.7",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^7.3.0",
|
||||
"@capacitor/cli": "^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",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
"typescript-eslint": "^8.34.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "TrafficCue",
|
||||
"icons": [
|
||||
{
|
||||
"src": "img/icon512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "img/icon192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"name": "TrafficCue",
|
||||
"icons": [
|
||||
{
|
||||
"src": "img/icon512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "img/icon192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
9029
public/style.json
9029
public/style.json
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import "./app.css";
|
||||
import { GeolocateControl, Hash, MapLibre } from "svelte-maplibre-gl";
|
||||
import Sidebar from "./lib/components/lnv/Sidebar.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import Map from "$lib/components/lnv/Map.svelte";
|
||||
@ -9,8 +8,10 @@
|
||||
import RoutingInfo from "$lib/components/lnv/RoutingInfo.svelte";
|
||||
|
||||
onMount(() => {
|
||||
if(!checkWebGL()) {
|
||||
alert("WebGL is not supported in your browser. Please try a different browser.");
|
||||
if (!checkWebGL()) {
|
||||
alert(
|
||||
"WebGL is not supported in your browser. Please try a different browser.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
300
src/app.css
300
src/app.css
@ -5,159 +5,177 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(1.0000 0 0);
|
||||
--foreground: oklch(0.2644 0 0);
|
||||
--card: oklch(1.0000 0 0);
|
||||
--card-foreground: oklch(0.2644 0 0);
|
||||
--popover: oklch(1.0000 0 0);
|
||||
--popover-foreground: oklch(0.2644 0 0);
|
||||
--primary: oklch(0.3261 0 0);
|
||||
--primary-foreground: oklch(0.9886 0 0);
|
||||
--secondary: oklch(0.9772 0 0);
|
||||
--secondary-foreground: oklch(0.3261 0 0);
|
||||
--muted: oklch(0.9772 0 0);
|
||||
--muted-foreground: oklch(0.6460 0 0);
|
||||
--accent: oklch(0.9772 0 0);
|
||||
--accent-foreground: oklch(0.3261 0 0);
|
||||
--destructive: oklch(0.6201 0.2092 25.7747);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.9404 0 0);
|
||||
--input: oklch(0.9404 0 0);
|
||||
--ring: oklch(0.7716 0 0);
|
||||
--chart-1: oklch(0.8241 0.1251 84.4866);
|
||||
--chart-2: oklch(0.8006 0.1116 203.6044);
|
||||
--chart-3: oklch(0.4198 0.1693 266.7798);
|
||||
--chart-4: oklch(0.9214 0.0762 125.5777);
|
||||
--chart-5: oklch(0.9151 0.1032 116.1913);
|
||||
--sidebar: oklch(0.9886 0 0);
|
||||
--sidebar-foreground: oklch(0.2644 0 0);
|
||||
--sidebar-primary: oklch(0.3261 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.9886 0 0);
|
||||
--sidebar-accent: oklch(0.9772 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.3261 0 0);
|
||||
--sidebar-border: oklch(0.9404 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-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.625rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.2644 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.2644 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.2644 0 0);
|
||||
--primary: oklch(0.3261 0 0);
|
||||
--primary-foreground: oklch(0.9886 0 0);
|
||||
--secondary: oklch(0.9772 0 0);
|
||||
--secondary-foreground: oklch(0.3261 0 0);
|
||||
--muted: oklch(0.9772 0 0);
|
||||
--muted-foreground: oklch(0.646 0 0);
|
||||
--accent: oklch(0.9772 0 0);
|
||||
--accent-foreground: oklch(0.3261 0 0);
|
||||
--destructive: oklch(0.6201 0.2092 25.7747);
|
||||
--destructive-foreground: oklch(1 0 0);
|
||||
--border: oklch(0.9404 0 0);
|
||||
--input: oklch(0.9404 0 0);
|
||||
--ring: oklch(0.7716 0 0);
|
||||
--chart-1: oklch(0.8241 0.1251 84.4866);
|
||||
--chart-2: oklch(0.8006 0.1116 203.6044);
|
||||
--chart-3: oklch(0.4198 0.1693 266.7798);
|
||||
--chart-4: oklch(0.9214 0.0762 125.5777);
|
||||
--chart-5: oklch(0.9151 0.1032 116.1913);
|
||||
--sidebar: oklch(0.9886 0 0);
|
||||
--sidebar-foreground: oklch(0.2644 0 0);
|
||||
--sidebar-primary: oklch(0.3261 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.9886 0 0);
|
||||
--sidebar-accent: oklch(0.9772 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.3261 0 0);
|
||||
--sidebar-border: oklch(0.9404 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-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--radius: 0.625rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--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 {
|
||||
--background: oklch(0.1405 0.0044 285.8238);
|
||||
--foreground: oklch(0.9848 0 0);
|
||||
--card: oklch(0.1405 0.0044 285.8238);
|
||||
--card-foreground: oklch(0.9848 0 0);
|
||||
--popover: oklch(0.1405 0.0044 285.8238);
|
||||
--popover-foreground: oklch(0.9848 0 0);
|
||||
--primary: oklch(0.5111 0.2152 266.7098);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.2741 0.0055 286.0329);
|
||||
--secondary-foreground: oklch(0.9848 0 0);
|
||||
--muted: oklch(0.2741 0.0055 286.0329);
|
||||
--muted-foreground: oklch(0.7119 0.0129 286.0684);
|
||||
--accent: oklch(0.5111 0.2152 266.7098);
|
||||
--accent-foreground: oklch(0.9848 0 0);
|
||||
--destructive: oklch(0.3959 0.1331 25.7205);
|
||||
--destructive-foreground: oklch(0.9710 0.0127 17.3758);
|
||||
--border: oklch(0.2741 0.0055 286.0329);
|
||||
--input: oklch(0.2741 0.0055 286.0329);
|
||||
--ring: oklch(0.8709 0.0055 286.2853);
|
||||
--chart-1: oklch(0.5292 0.1931 262.1292);
|
||||
--chart-2: oklch(0.6983 0.1337 165.4626);
|
||||
--chart-3: oklch(0.7232 0.1500 60.6307);
|
||||
--chart-4: oklch(0.6192 0.2037 312.7283);
|
||||
--chart-5: oklch(0.6123 0.2093 6.3856);
|
||||
--sidebar: oklch(0.2103 0.0059 285.8835);
|
||||
--sidebar-foreground: oklch(0.9676 0.0013 286.3752);
|
||||
--sidebar-primary: oklch(0.4878 0.2170 264.3876);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.2741 0.0055 286.0329);
|
||||
--sidebar-accent-foreground: oklch(0.9676 0.0013 286.3752);
|
||||
--sidebar-border: oklch(0.2741 0.0055 286.0329);
|
||||
--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-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.625rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
--background: oklch(0.1405 0.0044 285.8238);
|
||||
--foreground: oklch(0.9848 0 0);
|
||||
--card: oklch(0.1405 0.0044 285.8238);
|
||||
--card-foreground: oklch(0.9848 0 0);
|
||||
--popover: oklch(0.1405 0.0044 285.8238);
|
||||
--popover-foreground: oklch(0.9848 0 0);
|
||||
--primary: oklch(0.5111 0.2152 266.7098);
|
||||
--primary-foreground: oklch(1 0 0);
|
||||
--secondary: oklch(0.2741 0.0055 286.0329);
|
||||
--secondary-foreground: oklch(0.9848 0 0);
|
||||
--muted: oklch(0.2741 0.0055 286.0329);
|
||||
--muted-foreground: oklch(0.7119 0.0129 286.0684);
|
||||
--accent: oklch(0.5111 0.2152 266.7098);
|
||||
--accent-foreground: oklch(0.9848 0 0);
|
||||
--destructive: oklch(0.3959 0.1331 25.7205);
|
||||
--destructive-foreground: oklch(0.971 0.0127 17.3758);
|
||||
--border: oklch(0.2741 0.0055 286.0329);
|
||||
--input: oklch(0.2741 0.0055 286.0329);
|
||||
--ring: oklch(0.8709 0.0055 286.2853);
|
||||
--chart-1: oklch(0.5292 0.1931 262.1292);
|
||||
--chart-2: oklch(0.6983 0.1337 165.4626);
|
||||
--chart-3: oklch(0.7232 0.15 60.6307);
|
||||
--chart-4: oklch(0.6192 0.2037 312.7283);
|
||||
--chart-5: oklch(0.6123 0.2093 6.3856);
|
||||
--sidebar: oklch(0.2103 0.0059 285.8835);
|
||||
--sidebar-foreground: oklch(0.9676 0.0013 286.3752);
|
||||
--sidebar-primary: oklch(0.4878 0.217 264.3876);
|
||||
--sidebar-primary-foreground: oklch(1 0 0);
|
||||
--sidebar-accent: oklch(0.2741 0.0055 286.0329);
|
||||
--sidebar-accent-foreground: oklch(0.9676 0.0013 286.3752);
|
||||
--sidebar-border: oklch(0.2741 0.0055 286.0329);
|
||||
--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-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--radius: 0.625rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--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 {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--font-serif: var(--font-serif);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--font-serif: var(--font-serif);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
|
||||
@ -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";
|
||||
|
||||
export const POIIcons: Record<string, Component> = {
|
||||
@ -14,5 +27,5 @@ export const POIIcons: Record<string, Component> = {
|
||||
"shop=kiosk": StoreIcon,
|
||||
"amenity=restaurant": ChefHatIcon,
|
||||
"amenity=fast_food": HamburgerIcon,
|
||||
"shop=bakery": CroissantIcon
|
||||
"shop=bakery": CroissantIcon,
|
||||
};
|
||||
|
||||
@ -1,8 +1,22 @@
|
||||
<script lang="ts">
|
||||
import * as Drawer from "$lib/components/ui/drawer/index.js";
|
||||
import { BikeIcon, CarIcon, PlusCircleIcon, SaveIcon, TractorIcon, TruckIcon, XIcon } from "@lucide/svelte";
|
||||
import * as Drawer from "$lib/components/ui/drawer/index.js";
|
||||
import {
|
||||
BikeIcon,
|
||||
CarIcon,
|
||||
SaveIcon,
|
||||
TractorIcon,
|
||||
TruckIcon,
|
||||
XIcon,
|
||||
} from "@lucide/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 * as Select from "../ui/select";
|
||||
import Input from "../ui/input/input.svelte";
|
||||
@ -36,25 +50,31 @@
|
||||
actualMaxSpeed: 45,
|
||||
emissionClass: "euro_5",
|
||||
fuelType: "diesel",
|
||||
preferredFuel: "Diesel"
|
||||
preferredFuel: "Diesel",
|
||||
});
|
||||
</script>
|
||||
|
||||
<Drawer.Root bind:open={open}>
|
||||
<Drawer.Trigger class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}>
|
||||
<Drawer.Root bind:open>
|
||||
<Drawer.Trigger
|
||||
class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}
|
||||
>
|
||||
{@render children()}
|
||||
</Drawer.Trigger>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>Add Vehicle</Drawer.Title>
|
||||
</Drawer.Header>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>Add Vehicle</Drawer.Title>
|
||||
</Drawer.Header>
|
||||
<div class="p-4 pt-0 flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<Select.Root type="single" bind:value={vehicle.type}>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
{@const Icon = getVehicleIcon(vehicle.type)}
|
||||
<Icon />
|
||||
{vehicle.type === "car" ? "Car" : vehicle.type === "motor_scooter" ? "Moped" : "?"}
|
||||
{vehicle.type === "car"
|
||||
? "Car"
|
||||
: vehicle.type === "motor_scooter"
|
||||
? "Moped"
|
||||
: "?"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="car">
|
||||
@ -110,7 +130,11 @@
|
||||
<div class="flex gap-2">
|
||||
<Select.Root type="single" bind:value={vehicle.fuelType}>
|
||||
<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.Content>
|
||||
<Select.Item value="diesel">Diesel</Select.Item>
|
||||
@ -137,33 +161,38 @@
|
||||
</div>
|
||||
</div>
|
||||
<Drawer.Footer>
|
||||
<Button onclick={() => {
|
||||
open = false;
|
||||
if (vehicle.name.trim() === "") {
|
||||
alert("Please enter a vehicle name.");
|
||||
return;
|
||||
}
|
||||
if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) {
|
||||
alert("Please enter valid speeds.");
|
||||
return;
|
||||
}
|
||||
if(!isValidFuel(vehicle)) {
|
||||
alert("Please select a valid fuel type and preferred fuel.");
|
||||
return;
|
||||
}
|
||||
setVehicles([...vehicles, vehicle]);
|
||||
selectVehicle(vehicle);
|
||||
location.reload(); // TODO
|
||||
}}>
|
||||
<Button
|
||||
onclick={() => {
|
||||
open = false;
|
||||
if (vehicle.name.trim() === "") {
|
||||
alert("Please enter a vehicle name.");
|
||||
return;
|
||||
}
|
||||
if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) {
|
||||
alert("Please enter valid speeds.");
|
||||
return;
|
||||
}
|
||||
if (!isValidFuel(vehicle)) {
|
||||
alert("Please select a valid fuel type and preferred fuel.");
|
||||
return;
|
||||
}
|
||||
setVehicles([...vehicles, vehicle]);
|
||||
selectVehicle(vehicle);
|
||||
location.reload(); // TODO
|
||||
}}
|
||||
>
|
||||
<SaveIcon />
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="secondary" onclick={() => {
|
||||
open = false;
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={() => {
|
||||
open = false;
|
||||
}}
|
||||
>
|
||||
<XIcon />
|
||||
Cancel
|
||||
</Button>
|
||||
</Drawer.Footer>
|
||||
</Drawer.Content>
|
||||
</Drawer.Content>
|
||||
</Drawer.Root>
|
||||
@ -3,7 +3,7 @@
|
||||
import * as Select from "../ui/select";
|
||||
</script>
|
||||
|
||||
{#each EVConnectors as connector}
|
||||
{#each EVConnectors as connector (connector)}
|
||||
<Select.Item value={connector}>
|
||||
{connector}
|
||||
</Select.Item>
|
||||
|
||||
@ -34,9 +34,7 @@
|
||||
let value = $state("");
|
||||
let triggerRef = $state<HTMLButtonElement>(null!);
|
||||
|
||||
const selectedValue = $derived(
|
||||
value === "location" ? "My Location" : value
|
||||
);
|
||||
const selectedValue = $derived(value === "location" ? "My Location" : value);
|
||||
|
||||
// We want to refocus the trigger button when the user selects
|
||||
// an item from the list so users can continue navigating the
|
||||
@ -51,7 +49,7 @@
|
||||
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger bind:ref={triggerRef}>
|
||||
{#snippet child({ props }: { props: Record<string, any> })}
|
||||
{#snippet child({ props }: { props: Record<string, unknown> })}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="justify-between"
|
||||
@ -66,46 +64,46 @@
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-[200px] p-0">
|
||||
<Command.Root>
|
||||
<Command.Input placeholder="Search..." />
|
||||
<Command.List>
|
||||
<Command.Empty>No location found.</Command.Empty>
|
||||
<Command.Group>
|
||||
<Command.Item
|
||||
value={"location"}
|
||||
onSelect={() => {
|
||||
value = "location";
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
value !== "location" && "text-transparent"
|
||||
)}
|
||||
/>
|
||||
My Location
|
||||
</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Group>
|
||||
{#each frameworks as framework}
|
||||
<Command.Item
|
||||
value={framework.value}
|
||||
onSelect={() => {
|
||||
value = framework.value;
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
value !== framework.value && "text-transparent"
|
||||
)}
|
||||
/>
|
||||
{framework.label}
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.Group>
|
||||
</Command.List>
|
||||
<Command.Input placeholder="Search..." />
|
||||
<Command.List>
|
||||
<Command.Empty>No location found.</Command.Empty>
|
||||
<Command.Group>
|
||||
<Command.Item
|
||||
value="location"
|
||||
onSelect={() => {
|
||||
value = "location";
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
value !== "location" && "text-transparent",
|
||||
)}
|
||||
/>
|
||||
My Location
|
||||
</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Group>
|
||||
{#each frameworks as framework (framework.value)}
|
||||
<Command.Item
|
||||
value={framework.value}
|
||||
onSelect={() => {
|
||||
value = framework.value;
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
value !== framework.value && "text-transparent",
|
||||
)}
|
||||
/>
|
||||
{framework.label}
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.Group>
|
||||
</Command.List>
|
||||
</Command.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
@ -6,7 +6,7 @@
|
||||
let name = $derived(maneuverTypes[maneuver] || "none");
|
||||
</script>
|
||||
|
||||
<img src="/img/maneuver/{name}.svg" alt={name}>
|
||||
<img src="/img/maneuver/{name}.svg" alt={name} />
|
||||
|
||||
<style>
|
||||
img {
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
GeoJSONSource,
|
||||
GeolocateControl,
|
||||
Hash,
|
||||
LineLayer,
|
||||
MapLibre,
|
||||
Marker,
|
||||
@ -11,13 +9,7 @@
|
||||
} from "svelte-maplibre-gl";
|
||||
import { view } from "./sidebar.svelte";
|
||||
import { map, pin } from "./map.svelte";
|
||||
import {
|
||||
drawAllRoutes,
|
||||
fetchRoute,
|
||||
routing,
|
||||
} from "$lib/services/navigation/routing.svelte";
|
||||
import { createValhallaRequest } from "$lib/vehicles/ValhallaVehicles";
|
||||
import { ROUTING_SERVER } from "$lib/services/hosts";
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import { location } from "./location.svelte";
|
||||
|
||||
onMount(() => {
|
||||
@ -33,7 +25,9 @@
|
||||
scheme="tiles"
|
||||
loadFn={async (params) => {
|
||||
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];
|
||||
if (path == "natural_earth") {
|
||||
const t = await fetch("https://tiles.openfreemap.org/" + url);
|
||||
@ -73,7 +67,7 @@
|
||||
}
|
||||
}}
|
||||
onmove={(e) => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error - not typed
|
||||
if (e.reason !== "location") {
|
||||
location.locked = false;
|
||||
}
|
||||
@ -191,7 +185,10 @@
|
||||
|
||||
{#if location.available}
|
||||
<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
|
||||
lnglat={{ lat: location.lat, lng: location.lng }}
|
||||
element={locationDot}
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
import { hasCapability, type Capabilities } from "$lib/services/lnv";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
let { capability, children }: {
|
||||
let {
|
||||
capability,
|
||||
children,
|
||||
}: {
|
||||
capability: Capabilities[number];
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
@ -12,6 +15,6 @@
|
||||
{#if has}
|
||||
{@render children()}
|
||||
{/if}
|
||||
{:catch error}
|
||||
{:catch _error}
|
||||
<!-- user is likely offline -->
|
||||
{/await}
|
||||
@ -1,37 +1,52 @@
|
||||
<script lang="ts">
|
||||
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 ManeuverIcon from "./ManeuverIcon.svelte";
|
||||
|
||||
// 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 toRad = (d: number) => d * Math.PI / 180;
|
||||
const toRad = (d: number) => (d * Math.PI) / 180;
|
||||
const dLat = toRad(b.lat - a.lat);
|
||||
const dLon = toRad(b.lon - a.lon);
|
||||
const lat1 = toRad(a.lat);
|
||||
const lat2 = toRad(b.lat);
|
||||
|
||||
const aVal = Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
||||
const aVal =
|
||||
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));
|
||||
}
|
||||
|
||||
// Helper: Project point onto segment AB, return projected point and distance along segment
|
||||
function projectPointToSegment(p: WorldLocation, a: WorldLocation, b: WorldLocation) {
|
||||
const toRad = (deg: number) => deg * Math.PI / 180;
|
||||
const toDeg = (rad: number) => rad * 180 / Math.PI;
|
||||
function projectPointToSegment(
|
||||
p: WorldLocation,
|
||||
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 lat2 = toRad(b.lat), lon2 = toRad(b.lon);
|
||||
const lat3 = toRad(p.lat), lon3 = toRad(p.lon);
|
||||
const lat1 = toRad(a.lat),
|
||||
lon1 = toRad(a.lon);
|
||||
const lat2 = toRad(b.lat),
|
||||
lon2 = toRad(b.lon);
|
||||
const lat3 = toRad(p.lat),
|
||||
lon3 = toRad(p.lon);
|
||||
|
||||
const dLon = lon2 - lon1;
|
||||
const dLat = lat2 - lat1;
|
||||
|
||||
const t = ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
|
||||
(dLat * dLat + dLon * dLon);
|
||||
const t =
|
||||
((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
|
||||
(dLat * dLat + dLon * dLon);
|
||||
|
||||
// Clamp to [0,1]
|
||||
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 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 distance = $derived.by(() => {
|
||||
const lat = location.lat;
|
||||
const lat = location.lat;
|
||||
const lon = location.lng;
|
||||
if (!shape || shape.length === 0 || !maneuver) return 0;
|
||||
const start = shape[maneuver.begin_shape_index];
|
||||
@ -61,7 +78,7 @@
|
||||
if (!start || !end) return 0;
|
||||
const projected = projectPointToSegment({ lat, lon }, start, end);
|
||||
return projected.distToUser;
|
||||
});
|
||||
});
|
||||
|
||||
const roundDistance = $derived.by(() => {
|
||||
const dist = Math.round(distance);
|
||||
@ -76,24 +93,27 @@
|
||||
} else {
|
||||
return Math.round(dist / 5000) * 5000;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const distanceText = $derived.by(() => {
|
||||
const dist = roundDistance;
|
||||
if (dist < 1000) return `${dist} m`;
|
||||
return `${(dist / 1000)} km`;
|
||||
})
|
||||
return `${dist / 1000} km`;
|
||||
});
|
||||
</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">
|
||||
<ManeuverIcon maneuver={routing.currentTripInfo.currentManeuver?.type ?? 0} />
|
||||
<ManeuverIcon
|
||||
maneuver={routing.currentTripInfo.currentManeuver?.type ?? 0}
|
||||
/>
|
||||
<div class="flex gap-1 flex-col">
|
||||
<span class="text-xl font-bold">{distanceText}</span>
|
||||
<span>{routing.currentTripInfo.currentManeuver?.instruction}</span>
|
||||
</div>
|
||||
</div>
|
||||
<LanesDisplay
|
||||
lanes={routing.currentTripInfo.currentManeuver?.lanes}
|
||||
/>
|
||||
<LanesDisplay lanes={routing.currentTripInfo.currentManeuver?.lanes} />
|
||||
</div>
|
||||
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount, type Component } from "svelte";
|
||||
import { type Component } from "svelte";
|
||||
import InvalidSidebar from "./sidebar/InvalidSidebar.svelte";
|
||||
import { searchbar, view } from "./sidebar.svelte";
|
||||
import MainSidebar from "./sidebar/MainSidebar.svelte";
|
||||
@ -8,18 +8,28 @@
|
||||
import { map } from "./map.svelte";
|
||||
import TripSidebar from "./sidebar/TripSidebar.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 { search, type Feature } from "$lib/services/Search";
|
||||
import SearchSidebar from "./sidebar/SearchSidebar.svelte";
|
||||
import RequiresCapability from "./RequiresCapability.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 { routing } from "$lib/services/navigation/routing.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,
|
||||
info: InfoSidebar,
|
||||
route: RouteSidebar,
|
||||
@ -41,12 +51,14 @@
|
||||
$effect(() => {
|
||||
const newValue = getter(); // read here to subscribe to it
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => value = newValue, delay);
|
||||
timer = setTimeout(() => (value = newValue), delay);
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
return () => value;
|
||||
}
|
||||
|
||||
// TODO: implement loading state
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let loading = $state(false);
|
||||
|
||||
let searchText = $derived.by(debounce(() => searchbar.text, 300));
|
||||
@ -54,20 +66,20 @@
|
||||
let mobileView = $derived(window.innerWidth < 768 || routing.currentTrip);
|
||||
|
||||
$effect(() => {
|
||||
if(!searchText) {
|
||||
if (!searchText) {
|
||||
searchResults = [];
|
||||
if(view.current.type == "search") view.switch("main");
|
||||
if (view.current.type == "search") view.switch("main");
|
||||
return;
|
||||
}
|
||||
if (searchText.length > 0) {
|
||||
loading = true;
|
||||
search(searchText, 0, 0).then(results => {
|
||||
search(searchText, 0, 0).then((results) => {
|
||||
searchResults = results;
|
||||
loading = false;
|
||||
view.switch("search", {
|
||||
results: searchResults,
|
||||
query: searchText
|
||||
})
|
||||
query: searchText,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
searchResults = [];
|
||||
@ -77,37 +89,48 @@
|
||||
|
||||
{#if !routing.currentTrip}
|
||||
<div id="floating-search" class={mobileView ? "mobileView" : ""}>
|
||||
<Input class="h-10"
|
||||
placeholder="Search..." bind:value={searchbar.text} />
|
||||
<Input class="h-10" placeholder="Search..." bind:value={searchbar.text} />
|
||||
</div>
|
||||
{/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}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<div role="button" id="grabber" style="height: 10px; cursor: grab; display: flex; justify-content: center; align-items: center; touch-action: none;" ontouchstart={(e) => {
|
||||
isDragging = true;
|
||||
startY = e.touches[0].clientY;
|
||||
startHeight = sidebarHeight;
|
||||
}} ontouchmove={(e) => {
|
||||
if(!isDragging) return;
|
||||
e.preventDefault();
|
||||
const deltaY = e.touches[0].clientY - startY;
|
||||
let newHeight = Math.max(100, startHeight - deltaY);
|
||||
<div
|
||||
role="button"
|
||||
id="grabber"
|
||||
style="height: 10px; cursor: grab; display: flex; justify-content: center; align-items: center; touch-action: none;"
|
||||
ontouchstart={(e) => {
|
||||
isDragging = true;
|
||||
startY = e.touches[0].clientY;
|
||||
startHeight = sidebarHeight;
|
||||
}}
|
||||
ontouchmove={(e) => {
|
||||
if (!isDragging) return;
|
||||
e.preventDefault();
|
||||
const deltaY = e.touches[0].clientY - startY;
|
||||
let newHeight = Math.max(100, startHeight - deltaY);
|
||||
|
||||
const snapPoint = 200;
|
||||
const snapThreshold = 20;
|
||||
if (Math.abs(newHeight - snapPoint) < snapThreshold) {
|
||||
newHeight = snapPoint;
|
||||
}
|
||||
sidebarHeight = newHeight;
|
||||
const snapPoint = 200;
|
||||
const snapThreshold = 20;
|
||||
if (Math.abs(newHeight - snapPoint) < snapThreshold) {
|
||||
newHeight = snapPoint;
|
||||
}
|
||||
sidebarHeight = newHeight;
|
||||
|
||||
map.updateMapPadding();
|
||||
}} ontouchend={() => {
|
||||
if(!isDragging) return;
|
||||
isDragging = false;
|
||||
}}>
|
||||
<div style="height: 8px; background-color: #acacac; width: 40%; border-radius: 15px;"></div>
|
||||
map.updateMapPadding();
|
||||
}}
|
||||
ontouchend={() => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style="height: 8px; background-color: #acacac; width: 40%; border-radius: 15px;"
|
||||
></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -123,9 +146,11 @@
|
||||
<HomeIcon />
|
||||
</button>
|
||||
<RequiresCapability capability="auth">
|
||||
<button onclick={async () => {
|
||||
view.switch("user");
|
||||
}}>
|
||||
<button
|
||||
onclick={async () => {
|
||||
view.switch("user");
|
||||
}}
|
||||
>
|
||||
<UserIcon />
|
||||
</button>
|
||||
</RequiresCapability>
|
||||
@ -145,22 +170,31 @@
|
||||
</Popover.Trigger>
|
||||
<Popover.Content>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button variant="outline" onclick={() => {
|
||||
location.toggleLock();
|
||||
}}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
location.toggleLock();
|
||||
}}
|
||||
>
|
||||
{location.locked ? "Unlock Location" : "Lock Location"}
|
||||
</Button>
|
||||
{#if location.code}
|
||||
<span>Advertise code: {location.code}</span>
|
||||
{/if}
|
||||
<Button variant="outline" onclick={() => {
|
||||
advertiseRemoteLocation();
|
||||
}}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
advertiseRemoteLocation();
|
||||
}}
|
||||
>
|
||||
Advertise Location
|
||||
</Button>
|
||||
<Button variant="outline" onclick={() => {
|
||||
remoteLocation(prompt("Code?") || "");
|
||||
}}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
remoteLocation(prompt("Code?") || "");
|
||||
}}
|
||||
>
|
||||
Join Remote Location
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
<script lang="ts">
|
||||
import * as Drawer from "$lib/components/ui/drawer/index.js";
|
||||
import { BikeIcon, CarIcon, PlusCircleIcon, TractorIcon, TruckIcon } from "@lucide/svelte";
|
||||
import * as Drawer from "$lib/components/ui/drawer/index.js";
|
||||
import {
|
||||
BikeIcon,
|
||||
CarIcon,
|
||||
PlusCircleIcon,
|
||||
TractorIcon,
|
||||
TruckIcon,
|
||||
} from "@lucide/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";
|
||||
|
||||
let open = $state(false);
|
||||
@ -25,21 +37,32 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer.Root bind:open={open}>
|
||||
<Drawer.Trigger class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}>
|
||||
<Drawer.Root bind:open>
|
||||
<Drawer.Trigger
|
||||
class={buttonVariants({ variant: "secondary", class: "w-full p-5" })}
|
||||
>
|
||||
{@const vehicle = selectedVehicle() ?? DefaultVehicle}
|
||||
{@const Icon = getVehicleIcon(vehicle.type)}
|
||||
<Icon />
|
||||
{vehicle.name}
|
||||
</Drawer.Trigger>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>Vehicle Selector</Drawer.Title>
|
||||
<Drawer.Description>Select your vehicle to customize routing just for you.</Drawer.Description>
|
||||
</Drawer.Header>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Drawer.Title>Vehicle Selector</Drawer.Title>
|
||||
<Drawer.Description
|
||||
>Select your vehicle to customize routing just for you.</Drawer.Description
|
||||
>
|
||||
</Drawer.Header>
|
||||
<div class="p-4 pt-0 flex flex-col gap-2">
|
||||
{#each vehicles as vehicle}
|
||||
<Button variant={selectedVehicle() === vehicle ? "default" : "secondary"} class="w-full p-5" onclick={() => {selectVehicle(vehicle); open = false;}}>
|
||||
{#each vehicles as vehicle (vehicle.name)}
|
||||
<Button
|
||||
variant={selectedVehicle() === vehicle ? "default" : "secondary"}
|
||||
class="w-full p-5"
|
||||
onclick={() => {
|
||||
selectVehicle(vehicle);
|
||||
open = false;
|
||||
}}
|
||||
>
|
||||
{@const Icon = getVehicleIcon(vehicle.type)}
|
||||
<Icon />
|
||||
{vehicle.name}
|
||||
@ -53,5 +76,5 @@
|
||||
</Button>
|
||||
</AddVehicleDrawer>
|
||||
</div>
|
||||
</Drawer.Content>
|
||||
</Drawer.Content>
|
||||
</Drawer.Root>
|
||||
@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||
import { getStations } from "$lib/services/MTSK";
|
||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||
import { getStations } from "$lib/services/MTSK";
|
||||
|
||||
let { tags, lat, lng } = $props();
|
||||
</script>
|
||||
|
||||
<h3 class="text-lg font-bold mt-2">Fuel Types</h3>
|
||||
<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> -->
|
||||
<Badge>
|
||||
{key.replace("fuel:", "")}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Input from "$lib/components/ui/input/input.svelte";
|
||||
import { LNV_SERVER } from "$lib/services/hosts";
|
||||
import { ai } from "$lib/services/lnv";
|
||||
import { SparklesIcon } from "@lucide/svelte";
|
||||
|
||||
@ -12,7 +11,7 @@
|
||||
const chunks = res.split("\n");
|
||||
let text = "";
|
||||
for (const chunk of chunks) {
|
||||
if(chunk.startsWith("0:")) {
|
||||
if (chunk.startsWith("0:")) {
|
||||
text += JSON.parse(chunk.substring(2).trim());
|
||||
}
|
||||
}
|
||||
@ -33,9 +32,11 @@
|
||||
{/await}
|
||||
<Input
|
||||
type="text"
|
||||
value={""}
|
||||
placeholder="Ask a question about this place..." onchange={(e) => {
|
||||
question = (e.target! as any).value;
|
||||
}} />
|
||||
value=""
|
||||
placeholder="Ask a question about this place..."
|
||||
onchange={(e) => {
|
||||
question = (e.target! as HTMLInputElement).value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -2,16 +2,19 @@
|
||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||
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(() => {
|
||||
return new opening_hours(hours, {
|
||||
lat, lon, address: {
|
||||
lat,
|
||||
lon,
|
||||
address: {
|
||||
country_code: "de", // Default to Germany, can be overridden if needed
|
||||
state: "NRW", // Default to North Rhine-Westphalia, can be overridden if needed
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<h3 class="text-lg font-bold mt-2">
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
import { getReviews, postReview } from "$lib/services/lnv";
|
||||
import Stars from "./Stars.svelte";
|
||||
|
||||
let { lat, lng }: { lat: number, lng: number } = $props();
|
||||
let { lat, lng }: { lat: number; lng: number } = $props();
|
||||
</script>
|
||||
|
||||
<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}
|
||||
<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">
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar.Root>
|
||||
@ -27,20 +27,27 @@
|
||||
{:else}
|
||||
<p>No reviews available.</p>
|
||||
{/if}
|
||||
<Button variant="secondary" onclick={async () => {
|
||||
const rating = prompt("Enter your rating (1-5):");
|
||||
const comment = prompt("Enter your review comment:");
|
||||
if (rating && comment) {
|
||||
console.log(`Rating: ${rating}, Comment: ${comment}`);
|
||||
await postReview({ lat, lon: lng }, {
|
||||
rating: parseInt(rating, 10),
|
||||
comment
|
||||
})
|
||||
alert("Thank you for your review!");
|
||||
} else {
|
||||
alert("Review submission cancelled.");
|
||||
}
|
||||
}} disabled>Write a review</Button><br>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={async () => {
|
||||
const rating = prompt("Enter your rating (1-5):");
|
||||
const comment = prompt("Enter your review comment:");
|
||||
if (rating && comment) {
|
||||
console.log(`Rating: ${rating}, Comment: ${comment}`);
|
||||
await postReview(
|
||||
{ lat, lon: lng },
|
||||
{
|
||||
rating: parseInt(rating, 10),
|
||||
comment,
|
||||
},
|
||||
);
|
||||
alert("Thank you for your review!");
|
||||
} else {
|
||||
alert("Review submission cancelled.");
|
||||
}
|
||||
}}
|
||||
disabled>Write a review</Button
|
||||
><br />
|
||||
{:catch error}
|
||||
<p>Error loading reviews: {error.message}</p>
|
||||
{/await}
|
||||
@ -1,6 +1,6 @@
|
||||
import { LNV_SERVER } from "$lib/services/hosts"
|
||||
import { routing } from "$lib/services/navigation/routing.svelte"
|
||||
import { map } from "./map.svelte"
|
||||
import { LNV_SERVER } from "$lib/services/hosts";
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import { map } from "./map.svelte";
|
||||
|
||||
export const location = $state({
|
||||
available: false,
|
||||
@ -12,100 +12,117 @@ export const location = $state({
|
||||
provider: "gps" as "gps" | "remote" | "simulated",
|
||||
locked: true,
|
||||
toggleLock: () => {
|
||||
location.locked = !location.locked
|
||||
console.log("Location lock toggled:", location.locked)
|
||||
location.locked = !location.locked;
|
||||
console.log("Location lock toggled:", location.locked);
|
||||
if (location.locked) {
|
||||
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({
|
||||
map.value?.flyTo(
|
||||
{
|
||||
center: [location.lng, location.lat],
|
||||
zoom: 16,
|
||||
duration: 1000,
|
||||
// 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) {
|
||||
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.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
|
||||
})
|
||||
if (location.locked) {
|
||||
map.value?.flyTo(
|
||||
{
|
||||
center: [location.lng, location.lat],
|
||||
zoom: 16,
|
||||
duration: 1000,
|
||||
// bearing: location.heading !== null ? location.heading : undefined
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
||||
if(!checkRunning) {
|
||||
if (!checkRunning) {
|
||||
setInterval(() => {
|
||||
checkRunning = true;
|
||||
if(location.provider !== "gps") return;
|
||||
if (location.provider !== "gps") return;
|
||||
// If the last update was more than 5 seconds ago, recall watchPosition
|
||||
// console.log("Checking location update status")
|
||||
if (location.lastUpdate && (new Date().getTime() - location.lastUpdate.getTime()) > 10000) {
|
||||
console.warn("Location update is stale, rewatching position")
|
||||
if (
|
||||
location.lastUpdate &&
|
||||
new Date().getTime() - location.lastUpdate.getTime() > 10000
|
||||
) {
|
||||
console.warn("Location update is stale, rewatching position");
|
||||
watchLocation();
|
||||
}
|
||||
}, 1000)
|
||||
}, 1000);
|
||||
checkRunning = true;
|
||||
}
|
||||
|
||||
watchLocation()
|
||||
watchLocation();
|
||||
|
||||
export function advertiseRemoteLocation(code?: string) {
|
||||
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", () => {
|
||||
console.log("WebSocket connection established for remote location advertisement")
|
||||
ws.send(JSON.stringify({ type: "advertise", code }))
|
||||
console.log(
|
||||
"WebSocket connection established for remote location advertisement",
|
||||
);
|
||||
ws.send(JSON.stringify({ type: "advertise", code }));
|
||||
});
|
||||
ws.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
@ -127,40 +144,43 @@ export function remoteLocation(code: string) {
|
||||
// Open websocket connection
|
||||
// Use LNV_SERVER, change to ws or wss based on protocol
|
||||
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", () => {
|
||||
console.log("WebSocket connection established for remote location")
|
||||
ws.send(JSON.stringify({ type: "subscribe", code }))
|
||||
location.provider = "remote"
|
||||
location.code = code
|
||||
})
|
||||
console.log("WebSocket connection established for remote location");
|
||||
ws.send(JSON.stringify({ type: "subscribe", code }));
|
||||
location.provider = "remote";
|
||||
location.code = code;
|
||||
});
|
||||
ws.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data)
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === "location") {
|
||||
console.log("Remote location update:", data.location)
|
||||
location.lat = data.location.lat
|
||||
location.lng = data.location.lng
|
||||
location.accuracy = data.location.accuracy
|
||||
location.speed = data.location.speed || 0
|
||||
location.available = true
|
||||
location.heading = data.location.heading || null
|
||||
routing.currentTrip = data.route.trip || null
|
||||
routing.currentTripInfo = data.route.info || null
|
||||
routing.geojson = data.route.geojson || null
|
||||
console.log("Remote location update:", data.location);
|
||||
location.lat = data.location.lat;
|
||||
location.lng = data.location.lng;
|
||||
location.accuracy = data.location.accuracy;
|
||||
location.speed = data.location.speed || 0;
|
||||
location.available = true;
|
||||
location.heading = data.location.heading || null;
|
||||
routing.currentTrip = data.route.trip || null;
|
||||
routing.currentTripInfo = data.route.info || null;
|
||||
routing.geojson = data.route.geojson || null;
|
||||
|
||||
if (location.locked) {
|
||||
map.value?.flyTo({
|
||||
center: [location.lng, location.lat],
|
||||
zoom: 16,
|
||||
duration: 1000,
|
||||
// bearing: location.heading !== null ? location.heading : undefined
|
||||
}, {
|
||||
reason: "location"
|
||||
})
|
||||
map.value?.flyTo(
|
||||
{
|
||||
center: [location.lng, location.lat],
|
||||
zoom: 16,
|
||||
duration: 1000,
|
||||
// bearing: location.heading !== null ? location.heading : undefined
|
||||
},
|
||||
{
|
||||
reason: "location",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// setInterval(() => {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import { reverseGeocode } from "$lib/services/Search";
|
||||
import { view } from "./sidebar.svelte";
|
||||
|
||||
// export const geolocate = $state({
|
||||
@ -9,27 +8,31 @@ import { view } from "./sidebar.svelte";
|
||||
export const map = $state({
|
||||
value: undefined as maplibregl.Map | undefined,
|
||||
updateMapPadding: () => {
|
||||
if(document.querySelector<HTMLDivElement>("#sidebar") == null) {
|
||||
if (document.querySelector<HTMLDivElement>("#sidebar") == null) {
|
||||
map._setPadding({
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
left: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("Updating map padding");
|
||||
if (window.innerWidth < 768 || routing.currentTrip) {
|
||||
const calculatedSidebarHeight = document.querySelector<HTMLDivElement>("#sidebar")!.getBoundingClientRect().height;
|
||||
const calculatedSidebarHeight = document
|
||||
.querySelector<HTMLDivElement>("#sidebar")!
|
||||
.getBoundingClientRect().height;
|
||||
map._setPadding({
|
||||
top: routing.currentTrip ? 64 : 0,
|
||||
right: 0,
|
||||
bottom: calculatedSidebarHeight,
|
||||
left: 0
|
||||
left: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const calculatedSidebarWidth = document.querySelector<HTMLDivElement>("#sidebar")!.getBoundingClientRect().width;
|
||||
const calculatedSidebarWidth = document
|
||||
.querySelector<HTMLDivElement>("#sidebar")!
|
||||
.getBoundingClientRect().width;
|
||||
map._setPadding({
|
||||
top: 0,
|
||||
right: 0,
|
||||
@ -41,14 +44,19 @@ export const map = $state({
|
||||
top: 0,
|
||||
right: 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;
|
||||
if (map.value) {
|
||||
map.value.setPadding(map.padding);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const pin = $state({
|
||||
@ -66,12 +74,12 @@ export const pin = $state({
|
||||
pin.lng = 0;
|
||||
},
|
||||
showInfo: async () => {
|
||||
if(!pin.isDropped) return;
|
||||
if (!pin.isDropped) return;
|
||||
// const res = await reverseGeocode({ lat: pin.lat, lon: pin.lng });
|
||||
// if(res.length > 0) {
|
||||
// const feature = res[0];
|
||||
// view.switch("info", { feature });
|
||||
// const feature = res[0];
|
||||
// view.switch("info", { feature });
|
||||
// }
|
||||
view.switch("info", { lat: pin.lat, lng: pin.lng });
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export type View = {
|
||||
export interface View {
|
||||
type: string;
|
||||
props?: Record<string, any>;
|
||||
props?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
switch: (to: string, props?: Record<string, any>) => {
|
||||
switch: (to: string, props?: Record<string, unknown>) => {
|
||||
if (view.current.type !== to) {
|
||||
view.history.push(view.current);
|
||||
}
|
||||
view.current = { type: to, props } as View;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const searchbar = $state({
|
||||
text: ""
|
||||
})
|
||||
text: "",
|
||||
});
|
||||
|
||||
@ -1,36 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { decodePolyline, routing, stopNavigation } from "$lib/services/navigation/routing.svelte";
|
||||
import { advertiseRemoteLocation, location } from "../location.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import {
|
||||
decodePolyline,
|
||||
routing,
|
||||
stopNavigation,
|
||||
} from "$lib/services/navigation/routing.svelte";
|
||||
import { advertiseRemoteLocation, location } from "../location.svelte";
|
||||
|
||||
// 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 toRad = (d: number) => d * Math.PI / 180;
|
||||
const toRad = (d: number) => (d * Math.PI) / 180;
|
||||
const dLat = toRad(b.lat - a.lat);
|
||||
const dLon = toRad(b.lon - a.lon);
|
||||
const lat1 = toRad(a.lat);
|
||||
const lat2 = toRad(b.lat);
|
||||
|
||||
const aVal = Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
||||
const aVal =
|
||||
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));
|
||||
}
|
||||
|
||||
// Helper: Project point onto segment AB, return projected point and distance along segment
|
||||
function projectPointToSegment(p: WorldLocation, a: WorldLocation, b: WorldLocation) {
|
||||
const toRad = (deg: number) => deg * Math.PI / 180;
|
||||
const toDeg = (rad: number) => rad * 180 / Math.PI;
|
||||
function projectPointToSegment(
|
||||
p: WorldLocation,
|
||||
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 lat2 = toRad(b.lat), lon2 = toRad(b.lon);
|
||||
const lat3 = toRad(p.lat), lon3 = toRad(p.lon);
|
||||
const lat1 = toRad(a.lat),
|
||||
lon1 = toRad(a.lon);
|
||||
const lat2 = toRad(b.lat),
|
||||
lon2 = toRad(b.lon);
|
||||
const lat3 = toRad(p.lat),
|
||||
lon3 = toRad(p.lon);
|
||||
|
||||
const dLon = lon2 - lon1;
|
||||
const dLat = lat2 - lat1;
|
||||
|
||||
const t = ((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
|
||||
(dLat * dLat + dLon * dLon);
|
||||
const t =
|
||||
((lat3 - lat1) * dLat + (lon3 - lon1) * dLon) /
|
||||
(dLat * dLat + dLon * dLon);
|
||||
|
||||
// Clamp to [0,1]
|
||||
const clampedT = Math.max(0, Math.min(1, t));
|
||||
@ -46,37 +62,43 @@
|
||||
};
|
||||
}
|
||||
|
||||
const shape = $derived(decodePolyline(routing.currentTrip?.legs[0].shape || ""));
|
||||
const maneuver = $derived(routing.currentTripInfo.currentManeuver);
|
||||
const shape = $derived(
|
||||
decodePolyline(routing.currentTrip?.legs[0].shape || ""),
|
||||
);
|
||||
// const maneuver = $derived(routing.currentTripInfo.currentManeuver);
|
||||
|
||||
const fullDistance = $derived.by(() => {
|
||||
const lat = location.lat;
|
||||
const lon = location.lng;
|
||||
if (!shape.length) return 0;
|
||||
const lat = location.lat;
|
||||
const lon = location.lng;
|
||||
if (!shape.length) return 0;
|
||||
|
||||
// 1️⃣ find projection onto any segment of the full shape
|
||||
let best = { idx: 0, proj: shape[0], dist: Infinity };
|
||||
for (let i = 0; i < shape.length - 1; i++) {
|
||||
const a = shape[i];
|
||||
const b = shape[i + 1];
|
||||
const proj = projectPointToSegment({ lat, lon }, a, b);
|
||||
if (proj.distToUser < best.dist) {
|
||||
best = { idx: i, proj: { lat: proj.lat, lon: proj.lon }, dist: proj.distToUser };
|
||||
}
|
||||
}
|
||||
// 1️⃣ find projection onto any segment of the full shape
|
||||
let best = { idx: 0, proj: shape[0], dist: Infinity };
|
||||
for (let i = 0; i < shape.length - 1; i++) {
|
||||
const a = shape[i];
|
||||
const b = shape[i + 1];
|
||||
const proj = projectPointToSegment({ lat, lon }, a, b);
|
||||
if (proj.distToUser < best.dist) {
|
||||
best = {
|
||||
idx: i,
|
||||
proj: { lat: proj.lat, lon: proj.lon },
|
||||
dist: proj.distToUser,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 2️⃣ sum from the projection point to the very last point
|
||||
let total = 0;
|
||||
// from projection → next vertex
|
||||
total += haversine(best.proj, shape[best.idx + 1]);
|
||||
// 2️⃣ sum from the projection point to the very last point
|
||||
let total = 0;
|
||||
// from projection → next vertex
|
||||
total += haversine(best.proj, shape[best.idx + 1]);
|
||||
|
||||
// then each remaining segment
|
||||
for (let j = best.idx + 1; j < shape.length - 1; j++) {
|
||||
total += haversine(shape[j], shape[j + 1]);
|
||||
}
|
||||
// then each remaining segment
|
||||
for (let j = best.idx + 1; j < shape.length - 1; j++) {
|
||||
total += haversine(shape[j], shape[j + 1]);
|
||||
}
|
||||
|
||||
return total;
|
||||
});
|
||||
return total;
|
||||
});
|
||||
|
||||
const roundFullDistance = $derived.by(() => {
|
||||
const dist = Math.round(fullDistance);
|
||||
@ -91,39 +113,49 @@
|
||||
} else {
|
||||
return Math.round(dist / 5000) * 5000;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const fullDistanceText = $derived.by(() => {
|
||||
const dist = roundFullDistance;
|
||||
if (dist < 1000) return `${dist} m`;
|
||||
return `${(dist / 1000)} km`;
|
||||
})
|
||||
return `${dist / 1000} km`;
|
||||
});
|
||||
</script>
|
||||
|
||||
{fullDistanceText} left
|
||||
|
||||
<Button onclick={() => {
|
||||
location.toggleLock();
|
||||
}}>LOCK</Button>
|
||||
<Button
|
||||
onclick={() => {
|
||||
location.toggleLock();
|
||||
}}>LOCK</Button
|
||||
>
|
||||
|
||||
<Button onclick={() => {
|
||||
stopNavigation();
|
||||
}}>End Trip</Button>
|
||||
<Button
|
||||
onclick={() => {
|
||||
stopNavigation();
|
||||
}}>End Trip</Button
|
||||
>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-5">
|
||||
{#if location.code}
|
||||
<span>Share Code: {location.code}</span>
|
||||
<Button variant="secondary" onclick={() => {
|
||||
location.advertiser?.close();
|
||||
location.advertiser = null;
|
||||
location.code = null;
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={() => {
|
||||
location.advertiser?.close();
|
||||
location.advertiser = null;
|
||||
location.code = null;
|
||||
}}
|
||||
>
|
||||
Stop Sharing Location
|
||||
</Button>
|
||||
{:else}
|
||||
<Button variant="secondary" onclick={() => {
|
||||
advertiseRemoteLocation();
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={() => {
|
||||
advertiseRemoteLocation();
|
||||
}}
|
||||
>
|
||||
Share Trip Status & Location
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { POIIcons } from "$lib/POIIcons";
|
||||
import { OVERPASS_SERVER } from "$lib/services/hosts";
|
||||
import { BriefcaseIcon, EllipsisIcon, GlobeIcon, HomeIcon, MailIcon, PhoneIcon, RouteIcon } from "@lucide/svelte";
|
||||
import {
|
||||
BriefcaseIcon,
|
||||
EllipsisIcon,
|
||||
GlobeIcon,
|
||||
HomeIcon,
|
||||
MailIcon,
|
||||
PhoneIcon,
|
||||
RouteIcon,
|
||||
} from "@lucide/svelte";
|
||||
import { pin } from "../map.svelte";
|
||||
import SidebarHeader from "./SidebarHeader.svelte";
|
||||
import { fetchPOI, type OverpassElement } from "$lib/services/Overpass";
|
||||
@ -13,57 +20,78 @@
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import Reviews from "../info/Reviews.svelte";
|
||||
import MapAi from "../info/MapAI.svelte";
|
||||
import { hasCapability } from "$lib/services/lnv";
|
||||
import RequiresCapability from "../RequiresCapability.svelte";
|
||||
|
||||
// let { feature }: { feature: Feature } = $props();
|
||||
|
||||
// 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 {
|
||||
const key = Object.keys(tags).find(k => k.startsWith("amenity") || k.startsWith("shop"));
|
||||
function getIcon(
|
||||
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]]) {
|
||||
return POIIcons[key + "=" + tags[key]];
|
||||
}
|
||||
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 φ1 = lat * Math.PI / 180;
|
||||
const φ2 = aLat * Math.PI / 180;
|
||||
const Δφ = (aLat - lat) * Math.PI / 180;
|
||||
const Δλ = (aLon - lon) * Math.PI / 180;
|
||||
const φ1 = (lat * Math.PI) / 180;
|
||||
const φ2 = (aLat * Math.PI) / 180;
|
||||
const Δφ = ((aLat - lat) * 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));
|
||||
|
||||
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) => {
|
||||
const aLoc = a.center || a;
|
||||
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>
|
||||
|
||||
{#await fetchPOI(lat, lng, 20)}
|
||||
<SidebarHeader onback={() => {
|
||||
pin.liftPin();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
pin.liftPin();
|
||||
}}
|
||||
>
|
||||
Dropped Pin
|
||||
</SidebarHeader>
|
||||
<p>Loading...</p>
|
||||
{:then res}
|
||||
{#if res.elements.length === 0}
|
||||
<SidebarHeader onback={() => {
|
||||
pin.liftPin();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
pin.liftPin();
|
||||
}}
|
||||
>
|
||||
Dropped Pin
|
||||
</SidebarHeader>
|
||||
<span style="color: #acacac;">© OpenStreetMap</span>
|
||||
@ -75,38 +103,57 @@
|
||||
{@const ellat = firstElement.center?.lat || firstElement.lat!}
|
||||
{@const ellng = firstElement.center?.lon || firstElement.lon!}
|
||||
|
||||
<SidebarHeader onback={() => {
|
||||
pin.liftPin();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
pin.liftPin();
|
||||
}}
|
||||
>
|
||||
{#if getIcon(tags)}
|
||||
{@const Icon = getIcon(tags)}
|
||||
<Icon />
|
||||
{/if}
|
||||
{tags.name || (tags["addr:street"] ? (tags["addr:street"] + " " + tags["addr:housenumber"]) : "")}
|
||||
{tags.name ||
|
||||
(tags["addr:street"]
|
||||
? tags["addr:street"] + " " + tags["addr:housenumber"]
|
||||
: "")}
|
||||
</SidebarHeader>
|
||||
<div id="actions">
|
||||
<Button onclick={() => {
|
||||
view.switch("route", {
|
||||
to: lat + "," + lng,
|
||||
})
|
||||
}}>
|
||||
<Button
|
||||
onclick={() => {
|
||||
view.switch("route", {
|
||||
to: lat + "," + lng,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<RouteIcon />
|
||||
Route
|
||||
</Button>
|
||||
{#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 />
|
||||
Email
|
||||
</Button>
|
||||
{/if}
|
||||
{#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 />
|
||||
Website
|
||||
</Button>
|
||||
{/if}
|
||||
{#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 />
|
||||
Call
|
||||
</Button>
|
||||
@ -120,15 +167,27 @@
|
||||
</Popover.Trigger>
|
||||
<Popover.Content>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button variant="outline" onclick={() => {
|
||||
localStorage.setItem("saved.home", JSON.stringify({ lat, lon: lng }));
|
||||
}}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
localStorage.setItem(
|
||||
"saved.home",
|
||||
JSON.stringify({ lat, lon: lng }),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<HomeIcon />
|
||||
Set as Home
|
||||
</Button>
|
||||
<Button variant="outline" onclick={() => {
|
||||
localStorage.setItem("saved.work", JSON.stringify({ lat, lon: lng }));
|
||||
}}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
localStorage.setItem(
|
||||
"saved.work",
|
||||
JSON.stringify({ lat, lon: lng }),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<BriefcaseIcon />
|
||||
Set as Work
|
||||
</Button>
|
||||
@ -160,12 +219,11 @@
|
||||
{/if}
|
||||
|
||||
<!-- 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>
|
||||
<ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
||||
{#each Object.entries(tags).filter(([key]) => key.startsWith("payment:")) as [key, value]}
|
||||
<!-- <li>{key.replace("payment:", "")}: {value}</li> -->
|
||||
<Badge>{key.replace("payment:", "")}</Badge>
|
||||
{#each Object.entries(tags).filter( ([key]) => key.startsWith("payment:"), ) as [key, value] (key)}
|
||||
<Badge>{key.replace("payment:", "")}: {value}</Badge>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
@ -179,9 +237,11 @@
|
||||
<pre>{JSON.stringify(elements, null, 2)}</pre>
|
||||
{/if}
|
||||
{:catch err}
|
||||
<SidebarHeader onback={() => {
|
||||
pin.liftPin();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
pin.liftPin();
|
||||
}}
|
||||
>
|
||||
Dropped Pin
|
||||
</SidebarHeader>
|
||||
<p>Error: {err.message}</p>
|
||||
|
||||
@ -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>
|
||||
@ -6,41 +6,53 @@
|
||||
import { map, pin } from "../map.svelte";
|
||||
import VehicleSelector from "../VehicleSelector.svelte";
|
||||
import Post from "../Post.svelte";
|
||||
import RequiresCapability from "../RequiresCapability.svelte";
|
||||
import RequiresCapability from "../RequiresCapability.svelte";
|
||||
</script>
|
||||
|
||||
<div id="saved" class="mt-2 mb-2" 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
|
||||
});
|
||||
}}>
|
||||
<div
|
||||
id="saved"
|
||||
class="mt-2 mb-2"
|
||||
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 work location saved.");
|
||||
return;
|
||||
}
|
||||
const {lat, lon} = JSON.parse(work);
|
||||
pin.dropPin(lat, lon);
|
||||
pin.showInfo();
|
||||
map.value?.flyTo({
|
||||
center: [lon, lat],
|
||||
zoom: 19
|
||||
});
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="flex-1"
|
||||
onclick={() => {
|
||||
const work = localStorage.getItem("saved.work");
|
||||
if (!work) {
|
||||
alert("No work 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>
|
||||
|
||||
@ -1,19 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { CircleArrowDown, CircleDotIcon, StarIcon } from "@lucide/svelte";
|
||||
import LocationSelect from "../LocationSelect.svelte";
|
||||
import Input from "$lib/components/ui/input/input.svelte";
|
||||
import SidebarHeader from "./SidebarHeader.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
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 { map } from "../map.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 }: {
|
||||
from?: string,
|
||||
to?: string
|
||||
let {
|
||||
from,
|
||||
to,
|
||||
}: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
} = $props();
|
||||
|
||||
let fromLocation = $state(from || "");
|
||||
@ -29,13 +39,18 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<SidebarHeader onback={() => {
|
||||
removeAllRoutes();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
removeAllRoutes();
|
||||
}}
|
||||
>
|
||||
Route
|
||||
</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 gap-2 items-center w-full">
|
||||
<CircleDotIcon />
|
||||
@ -49,39 +64,51 @@
|
||||
<Input bind:value={toLocation} />
|
||||
</div>
|
||||
</div>
|
||||
<Button onclick={async () => {
|
||||
const FROM: WorldLocation = fromLocation == "home" ? JSON.parse(localStorage.getItem("saved.home")!)
|
||||
: fromLocation == "work" ? JSON.parse(localStorage.getItem("saved.work")!)
|
||||
: {
|
||||
lat: parseFloat(fromLocation.split(",")[0]),
|
||||
lon: parseFloat(fromLocation.split(",")[1])
|
||||
};
|
||||
const TO: WorldLocation = toLocation == "home" ? JSON.parse(localStorage.getItem("saved.home")!)
|
||||
: toLocation == "work" ? JSON.parse(localStorage.getItem("saved.work")!)
|
||||
: {
|
||||
lat: parseFloat(toLocation.split(",")[0]),
|
||||
lon: parseFloat(toLocation.split(",")[1])
|
||||
};
|
||||
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);
|
||||
<Button
|
||||
onclick={async () => {
|
||||
const FROM: WorldLocation =
|
||||
fromLocation == "home"
|
||||
? JSON.parse(localStorage.getItem("saved.home")!)
|
||||
: fromLocation == "work"
|
||||
? JSON.parse(localStorage.getItem("saved.work")!)
|
||||
: {
|
||||
lat: parseFloat(fromLocation.split(",")[0]),
|
||||
lon: parseFloat(fromLocation.split(",")[1]),
|
||||
};
|
||||
const TO: WorldLocation =
|
||||
toLocation == "home"
|
||||
? JSON.parse(localStorage.getItem("saved.home")!)
|
||||
: toLocation == "work"
|
||||
? JSON.parse(localStorage.getItem("saved.work")!)
|
||||
: {
|
||||
lat: parseFloat(toLocation.split(",")[0]),
|
||||
lon: parseFloat(toLocation.split(",")[1]),
|
||||
};
|
||||
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);
|
||||
zoomToPoints(FROM, TO, map.value!);
|
||||
}}>Calculate</Button>
|
||||
drawAllRoutes(routes);
|
||||
zoomToPoints(FROM, TO, map.value!);
|
||||
}}>Calculate</Button
|
||||
>
|
||||
|
||||
{#if routes}
|
||||
<div class="mt-2 flex gap-2 flex-col">
|
||||
{#each routes as route, i (route?.summary?.length)}
|
||||
<Button variant="secondary" onclick={() => {
|
||||
view.switch("trip", { route });
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={() => {
|
||||
view.switch("trip", { route });
|
||||
}}
|
||||
>
|
||||
{#if i == 0}
|
||||
<StarIcon />
|
||||
{/if}
|
||||
|
||||
@ -7,28 +7,47 @@
|
||||
import SidebarHeader from "./SidebarHeader.svelte";
|
||||
import { searchbar } from "../sidebar.svelte";
|
||||
|
||||
let { results, query }: {
|
||||
results: Feature[],
|
||||
query: string
|
||||
let {
|
||||
results,
|
||||
query,
|
||||
}: {
|
||||
results: Feature[];
|
||||
query: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SidebarHeader onback={() => {
|
||||
searchbar.text = "";
|
||||
}}>
|
||||
Search Results
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
searchbar.text = "";
|
||||
}}
|
||||
>
|
||||
Search Results for "{query}"
|
||||
</SidebarHeader>
|
||||
<div id="results" class="mt-2" in:fly={{ y: 20, duration: 200, easing: circInOut }}>
|
||||
{#each results 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
|
||||
});
|
||||
}}>
|
||||
<div
|
||||
id="results"
|
||||
class="mt-2"
|
||||
in:fly={{ y: 20, duration: 200, easing: circInOut }}
|
||||
>
|
||||
{#each results as result (result.properties.osm_id)}
|
||||
<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}
|
||||
|
||||
@ -3,18 +3,26 @@
|
||||
import type { Snippet } from "svelte";
|
||||
import { view } from "../sidebar.svelte";
|
||||
|
||||
let { children, onback }: {
|
||||
children: Snippet,
|
||||
onback?: () => void,
|
||||
let {
|
||||
children,
|
||||
onback,
|
||||
}: {
|
||||
children: Snippet;
|
||||
onback?: () => void;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 items-center mb-2">
|
||||
<Button variant="outline" onclick={() => {
|
||||
view.back();
|
||||
if (onback) {
|
||||
onback();
|
||||
}
|
||||
}}><</Button>
|
||||
<h2 class="text-lg font-bold flex gap-2 items-center">{@render children?.()}</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
view.back();
|
||||
if (onback) {
|
||||
onback();
|
||||
}
|
||||
}}><</Button
|
||||
>
|
||||
<h2 class="text-lg font-bold flex gap-2 items-center">
|
||||
{@render children?.()}
|
||||
</h2>
|
||||
</div>
|
||||
@ -1,34 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "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 { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte";
|
||||
import { map } from "../map.svelte";
|
||||
import { map } from "../map.svelte";
|
||||
|
||||
let { route }: {
|
||||
route: Trip
|
||||
let {
|
||||
route,
|
||||
}: {
|
||||
route: Trip;
|
||||
} = $props();
|
||||
|
||||
onMount(() => {
|
||||
removeAllRoutes();
|
||||
drawRoute(route);
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<SidebarHeader onback={() => {
|
||||
removeAllRoutes();
|
||||
}}>
|
||||
<SidebarHeader
|
||||
onback={() => {
|
||||
removeAllRoutes();
|
||||
}}
|
||||
>
|
||||
Trip Details
|
||||
</SidebarHeader>
|
||||
|
||||
<div id="actions" class="flex gap-2">
|
||||
<Button onclick={async () => {
|
||||
await startRoute(route);
|
||||
requestAnimationFrame(() => {
|
||||
map.updateMapPadding();
|
||||
})
|
||||
}}>
|
||||
<Button
|
||||
onclick={async () => {
|
||||
await startRoute(route);
|
||||
requestAnimationFrame(() => {
|
||||
map.updateMapPadding();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<RouteIcon />
|
||||
Start Navigation
|
||||
</Button>
|
||||
@ -43,7 +53,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{maneuver.instruction}
|
||||
</li>
|
||||
|
||||
@ -5,49 +5,60 @@
|
||||
import { getAuthURL, getOIDCUser } from "$lib/services/oidc";
|
||||
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(() => {
|
||||
if(!localStorage.getItem("lnv-token")) {
|
||||
if (!localStorage.getItem("lnv-token")) {
|
||||
user = null;
|
||||
} else {
|
||||
user = JSON.parse(atob((localStorage.getItem("lnv-id") || "").split(".")[1]));
|
||||
user = JSON.parse(
|
||||
atob((localStorage.getItem("lnv-id") || "").split(".")[1]),
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if !user}
|
||||
<SidebarHeader>
|
||||
User
|
||||
</SidebarHeader>
|
||||
<SidebarHeader>User</SidebarHeader>
|
||||
|
||||
<Button onclick={async () => {
|
||||
const auth = await getAuthURL();
|
||||
// localStorage.setItem("lnv-codeVerifier", auth.codeVerifier);
|
||||
// localStorage.setItem("lnv-oidcstate", auth.state);
|
||||
const popup = window.open(auth.url, "Login", "width=500,height=600");
|
||||
window.addEventListener("message", async (e) => {
|
||||
if(e.origin !== window.location.origin) return;
|
||||
<Button
|
||||
onclick={async () => {
|
||||
const auth = await getAuthURL();
|
||||
// localStorage.setItem("lnv-codeVerifier", auth.codeVerifier);
|
||||
// localStorage.setItem("lnv-oidcstate", auth.state);
|
||||
const popup = window.open(auth.url, "Login", "width=500,height=600");
|
||||
window.addEventListener("message", async (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
|
||||
const { code, state } = e.data;
|
||||
console.log("Received data from popup:", e.data);
|
||||
if(!code || !state) {
|
||||
console.error("Invalid response from popup");
|
||||
return;
|
||||
}
|
||||
popup?.close();
|
||||
if(state !== auth.state) {
|
||||
alert("State mismatch. Please try again.");
|
||||
return;
|
||||
}
|
||||
const { code, state } = e.data;
|
||||
console.log("Received data from popup:", e.data);
|
||||
if (!code || !state) {
|
||||
console.error("Invalid response from popup");
|
||||
return;
|
||||
}
|
||||
popup?.close();
|
||||
if (state !== auth.state) {
|
||||
alert("State mismatch. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getOIDCUser(code, auth.codeVerifier);
|
||||
localStorage.setItem("lnv-id", token.id_token);
|
||||
localStorage.setItem("lnv-token", token.access_token);
|
||||
localStorage.setItem("lnv-refresh", token.refresh_token);
|
||||
user = JSON.parse(atob((localStorage.getItem("lnv-id") || "").split(".")[1]));
|
||||
})
|
||||
}}>Login</Button>
|
||||
const token = await getOIDCUser(code, auth.codeVerifier);
|
||||
localStorage.setItem("lnv-id", token.id_token);
|
||||
localStorage.setItem("lnv-token", token.access_token);
|
||||
localStorage.setItem("lnv-refresh", token.refresh_token);
|
||||
user = JSON.parse(
|
||||
atob((localStorage.getItem("lnv-id") || "").split(".")[1]),
|
||||
);
|
||||
});
|
||||
}}>Login</Button
|
||||
>
|
||||
{:else}
|
||||
<SidebarHeader>
|
||||
<Avatar.Root>
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
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}
|
||||
/>
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
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}
|
||||
/>
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
|
||||
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",
|
||||
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: {
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
<script lang="ts" module>
|
||||
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";
|
||||
|
||||
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",
|
||||
variants: {
|
||||
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:
|
||||
"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:
|
||||
"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",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
secondary:
|
||||
"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",
|
||||
},
|
||||
size: {
|
||||
|
||||
@ -13,7 +13,10 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
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}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@ -10,6 +10,11 @@
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</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?.()}
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
data-slot="card-header"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<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 Command from "./command.svelte";
|
||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||
|
||||
@ -11,13 +11,16 @@
|
||||
}: CommandPrimitive.InputProps = $props();
|
||||
</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" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="command-item"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="command-item"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
<CommandPrimitive.List
|
||||
bind:ref
|
||||
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}
|
||||
/>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
data-slot="command"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
data-slot="dialog-content"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -13,7 +13,10 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
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}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="dialog-overlay"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} />
|
||||
|
||||
@ -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=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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -9,4 +9,9 @@
|
||||
}: DrawerPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.NestedRoot {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||
<DrawerPrimitive.NestedRoot
|
||||
{shouldScaleBackground}
|
||||
bind:open
|
||||
bind:activeSnapPoint
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="drawer-overlay"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} />
|
||||
|
||||
@ -9,4 +9,9 @@
|
||||
}: DrawerPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||
<DrawerPrimitive.Root
|
||||
{shouldScaleBackground}
|
||||
bind:open
|
||||
bind:activeSnapPoint
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
<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";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
(
|
||||
| { type: "file"; files?: FileList }
|
||||
| { type?: InputType; files?: undefined }
|
||||
)
|
||||
>;
|
||||
|
||||
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",
|
||||
"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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
type="file"
|
||||
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",
|
||||
"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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
{align}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -23,14 +23,14 @@
|
||||
data-slot="select-content"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
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?.()}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Group data-slot="select-group" {...restProps} />
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
data-slot="select-item"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
data-size={size}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-slot="separator"
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { LNV_SERVER } from "./hosts";
|
||||
import { hasCapability } from "./lnv";
|
||||
|
||||
type Station = {
|
||||
interface Station {
|
||||
id: string;
|
||||
name: string;
|
||||
brand: string;
|
||||
@ -18,13 +18,13 @@ type Station = {
|
||||
postCode: number;
|
||||
}
|
||||
|
||||
type StationsResponse = {
|
||||
interface StationsResponse {
|
||||
ok: boolean;
|
||||
license: "CC BY 4.0 - https:\/\/creativecommons.tankerkoenig.de";
|
||||
license: "CC BY 4.0 - https://creativecommons.tankerkoenig.de";
|
||||
data: "MTS-K";
|
||||
status: string;
|
||||
stations: Station[];
|
||||
};
|
||||
}
|
||||
|
||||
type StationDetails = {
|
||||
openingTimes: StationOpeningTime[];
|
||||
@ -32,37 +32,49 @@ type StationDetails = {
|
||||
wholeDay: boolean;
|
||||
} & Station;
|
||||
|
||||
type StationOpeningTime = {
|
||||
interface StationOpeningTime {
|
||||
text: string;
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
}
|
||||
|
||||
type StationDetailsResponse = {
|
||||
interface StationDetailsResponse {
|
||||
ok: boolean;
|
||||
license: "CC BY 4.0 - https:\/\/creativecommons.tankerkoenig.de";
|
||||
license: "CC BY 4.0 - https://creativecommons.tankerkoenig.de";
|
||||
data: "MTS-K";
|
||||
status: string;
|
||||
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
|
||||
if(!await hasCapability("fuel")) {
|
||||
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/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> {
|
||||
if(!await hasCapability("fuel")) {
|
||||
export async function getPrices(id: string) {
|
||||
// TODO: add type
|
||||
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());
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { OVERPASS_SERVER } from "./hosts";
|
||||
|
||||
export type OverpassResult = {
|
||||
export interface OverpassResult {
|
||||
elements: OverpassElement[];
|
||||
};
|
||||
}
|
||||
|
||||
export type OverpassElement = {
|
||||
export interface OverpassElement {
|
||||
type: "node" | "way" | "relation";
|
||||
id: number;
|
||||
tags: Record<string, string>;
|
||||
@ -15,7 +15,7 @@ export type OverpassElement = {
|
||||
lat: number; // Only for relations
|
||||
lon: number; // Only for relations
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
[out:json];
|
||||
@ -40,11 +40,7 @@ out geom;
|
||||
out geom;
|
||||
*/
|
||||
|
||||
export async function fetchPOI(
|
||||
lat: number,
|
||||
lon: number,
|
||||
radius: number,
|
||||
) {
|
||||
export async function fetchPOI(lat: number, lon: number, radius: number) {
|
||||
return await fetch(OVERPASS_SERVER, {
|
||||
method: "POST",
|
||||
body: `[out:json];
|
||||
@ -60,6 +56,6 @@ export async function fetchPOI(
|
||||
node(around:${radius}, ${lat}, ${lon})["amenity"="parking"];
|
||||
way(around:${radius}, ${lat}, ${lon})["amenity"="parking"];
|
||||
);
|
||||
out center tags;`
|
||||
}).then(res => res.json() as Promise<OverpassResult>);
|
||||
out center tags;`,
|
||||
}).then((res) => res.json() as Promise<OverpassResult>);
|
||||
}
|
||||
@ -2,38 +2,50 @@
|
||||
import { SEARCH_SERVER } from "./hosts";
|
||||
// import { Capacitor } from "@capacitor/core";
|
||||
|
||||
export type Feature = {
|
||||
type: "Feature",
|
||||
export interface Feature {
|
||||
type: "Feature";
|
||||
geometry: {
|
||||
coordinates: [number, number],
|
||||
type: "Point"
|
||||
},
|
||||
coordinates: [number, number];
|
||||
type: "Point";
|
||||
};
|
||||
properties: {
|
||||
osm_key: string;
|
||||
osm_value: string;
|
||||
osm_id: number,
|
||||
city: string,
|
||||
country: string,
|
||||
name: string,
|
||||
street: string,
|
||||
housenumber: string,
|
||||
type: string,
|
||||
osm_id: number;
|
||||
city: string;
|
||||
country: string;
|
||||
name: string;
|
||||
street: string;
|
||||
housenumber: string;
|
||||
type: string;
|
||||
// There is more, but not needed atm
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchPlaces(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());
|
||||
export async function searchPlaces(
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export async function search(query: string, lat: number, lon: number): Promise<Feature[]> {
|
||||
if(query.startsWith("@")) {
|
||||
export async function search(
|
||||
query: string,
|
||||
lat: number,
|
||||
lon: number,
|
||||
): Promise<Feature[]> {
|
||||
if (query.startsWith("@")) {
|
||||
// if(Capacitor.isNativePlatform()) {
|
||||
// return await searchContacts(query, lat, lon);
|
||||
// }
|
||||
|
||||
@ -4,4 +4,7 @@ export const ROUTING_SERVER = "https://valhalla1.openstreetmap.de/";
|
||||
// export const ROUTING_SERVER = "https://routing.map.picoscratch.de";
|
||||
export const SEARCH_SERVER = "https://photon.komoot.io/";
|
||||
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";
|
||||
|
||||
@ -2,7 +2,11 @@ import { LNV_SERVER } from "./hosts";
|
||||
|
||||
export type Capabilities = ("auth" | "reviews" | "ai" | "fuel" | "post")[];
|
||||
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() {
|
||||
const res = await fetch(LNV_SERVER + "/config");
|
||||
@ -10,7 +14,12 @@ export async function fetchConfig() {
|
||||
throw new Error(`Failed to fetch capabilities: ${res.statusText}`);
|
||||
}
|
||||
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() {
|
||||
@ -30,28 +39,32 @@ export async function getOIDCConfig() {
|
||||
oidcConfig = {
|
||||
AUTH_URL: config.oidc.AUTH_URL,
|
||||
CLIENT_ID: config.oidc.CLIENT_ID,
|
||||
TOKEN_URL: config.oidc.TOKEN_URL
|
||||
TOKEN_URL: config.oidc.TOKEN_URL,
|
||||
};
|
||||
}
|
||||
return oidcConfig;
|
||||
}
|
||||
|
||||
export async function hasCapability(capability: Capabilities[number]): Promise<boolean> {
|
||||
export async function hasCapability(
|
||||
capability: Capabilities[number],
|
||||
): Promise<boolean> {
|
||||
const caps = await getCapabilities();
|
||||
return caps.includes(capability);
|
||||
}
|
||||
|
||||
export type Review = {
|
||||
export interface Review {
|
||||
user_id: string;
|
||||
username: string;
|
||||
rating: number;
|
||||
comment: string;
|
||||
}
|
||||
export async function getReviews(location: WorldLocation) {
|
||||
if(!await hasCapability("reviews")) {
|
||||
if (!(await hasCapability("reviews"))) {
|
||||
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) {
|
||||
throw new Error(`Failed to fetch reviews: ${res.statusText}`);
|
||||
}
|
||||
@ -59,8 +72,11 @@ export async function getReviews(location: WorldLocation) {
|
||||
return data as Review[];
|
||||
}
|
||||
|
||||
export async function postReview(location: WorldLocation, review: Omit<Review, 'user_id' | 'username'>) {
|
||||
if(!await hasCapability("reviews")) {
|
||||
export async function postReview(
|
||||
location: WorldLocation,
|
||||
review: Omit<Review, "user_id" | "username">,
|
||||
) {
|
||||
if (!(await hasCapability("reviews"))) {
|
||||
throw new Error("Reviews capability is not available");
|
||||
}
|
||||
const token = localStorage.getItem("lnv-token");
|
||||
@ -71,13 +87,13 @@ export async function postReview(location: WorldLocation, review: Omit<Review, '
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...review,
|
||||
lat: location.lat,
|
||||
lon: location.lon
|
||||
})
|
||||
lon: location.lon,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
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) {
|
||||
if(!await hasCapability("ai")) {
|
||||
if (!(await hasCapability("ai"))) {
|
||||
throw new Error("AI capability is not available");
|
||||
}
|
||||
const res = await fetch(LNV_SERVER + `/ai`, {
|
||||
@ -96,8 +112,8 @@ export async function ai(query: string, location?: WorldLocation) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: query,
|
||||
coords: location
|
||||
})
|
||||
coords: location,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to get AI response: ${res.statusText}`);
|
||||
|
||||
@ -2,26 +2,30 @@
|
||||
let { lane }: { lane: Lane } = $props();
|
||||
const knownDirections = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024];
|
||||
|
||||
async function fetchImage(bit: number) {
|
||||
if (knownDirections.includes(bit)) {
|
||||
return await fetch(`/img/lanes/${bit}.svg`).then(res => res.text());
|
||||
} else {
|
||||
return `<span>${bit}</span>`;
|
||||
}
|
||||
}
|
||||
async function fetchImage(bit: number) {
|
||||
if (knownDirections.includes(bit)) {
|
||||
return await fetch(`/img/lanes/${bit}.svg`).then((res) => res.text());
|
||||
} else {
|
||||
return `<span>${bit}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage(node: HTMLElement, bit: number) {
|
||||
fetchImage(bit).then(img => {
|
||||
node.innerHTML = img;
|
||||
});
|
||||
}
|
||||
function loadImage(node: HTMLElement, bit: number) {
|
||||
fetchImage(bit).then((img) => {
|
||||
node.innerHTML = img;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<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}
|
||||
<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}
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
@ -10,7 +10,7 @@ export async function displayLane(lane: Lane) {
|
||||
// Check if the bit is in the known directions
|
||||
let img = "";
|
||||
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 {
|
||||
img = `<span>${bit}</span>`;
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
{#if lanes}
|
||||
<div id="lanes">
|
||||
{#each lanes as lane}
|
||||
{#each lanes as lane (lane)}
|
||||
<LaneDisplay {lane} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@ -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
|
||||
|
||||
import maplibregl from "maplibre-gl";
|
||||
// import { maneuverTypes } from "./Maneuver";
|
||||
import { hideRouteStatus, updateRouteStatus } from "../../components/routestatus";
|
||||
import { NavigationLayer, removeAllNavigationLayers, updateNavigationLayer } from "./NavigationLayers";
|
||||
import {
|
||||
hideRouteStatus,
|
||||
updateRouteStatus,
|
||||
} from "../../components/routestatus";
|
||||
import {
|
||||
NavigationLayer,
|
||||
removeAllNavigationLayers,
|
||||
updateNavigationLayer,
|
||||
} from "./NavigationLayers";
|
||||
import { updateMapPadding } from "../../main";
|
||||
import say from "../TTS";
|
||||
import { ROUTING_SERVER } from "../servers";
|
||||
import { createValhallaRequest } from "./ValhallaRequest";
|
||||
import { Vehicle } from "../../components/vehicles";
|
||||
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";
|
||||
|
||||
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 = {
|
||||
// locations: [
|
||||
// from,
|
||||
@ -65,7 +84,11 @@ function drawRoute(trip: Trip, name: NavigationLayer) {
|
||||
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()
|
||||
.setLngLat([from.lon, from.lat])
|
||||
.addTo(window.glmap);
|
||||
@ -75,8 +98,8 @@ export async function findRoute(vehicle: Vehicle, from: WorldLocation, to: World
|
||||
const route = await fetchRoute(vehicle, from, to);
|
||||
|
||||
let routes = [route.trip];
|
||||
if(route.alternates) {
|
||||
for(let i = 0; i < route.alternates.length; i++) {
|
||||
if (route.alternates) {
|
||||
for (let i = 0; i < route.alternates.length; i++) {
|
||||
routes.push(route.alternates[i].trip);
|
||||
}
|
||||
}
|
||||
@ -117,7 +140,11 @@ function getUserLocation(): WorldLocation {
|
||||
let pastRoute: WorldLocation[] = [];
|
||||
|
||||
// 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)
|
||||
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;
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
@ -175,9 +205,9 @@ export async function startNavigation(trip: Trip) {
|
||||
|
||||
// @ts-ignore The types are not correct
|
||||
int = setInterval(() => {
|
||||
if(instructionIdx != 0) {
|
||||
if (instructionIdx != 0) {
|
||||
// Only continue if the user location is at the end shape index of the current maneuver
|
||||
if(currentManeuver == null) {
|
||||
if (currentManeuver == null) {
|
||||
return;
|
||||
}
|
||||
const bgi = currentManeuver.begin_shape_index;
|
||||
@ -212,7 +242,7 @@ export async function startNavigation(trip: Trip) {
|
||||
updateRouteStatus({
|
||||
time: trip.summary.time,
|
||||
distance: trip.summary.length,
|
||||
currentManeuver
|
||||
currentManeuver,
|
||||
});
|
||||
}
|
||||
|
||||
@ -228,7 +258,7 @@ export async function startNavigation(trip: Trip) {
|
||||
// pastRoute.push(...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
|
||||
const newShape = polyline.slice(bgi);
|
||||
@ -242,11 +272,14 @@ export async function startNavigation(trip: Trip) {
|
||||
return;
|
||||
}
|
||||
const maneuver = trip.legs[0].maneuvers[instructionIdx];
|
||||
updateRouteStatus({
|
||||
time: trip.summary.time,
|
||||
distance: trip.summary.length,
|
||||
currentManeuver: trip.legs[0].maneuvers[instructionIdx],
|
||||
}, maneuver.lanes);
|
||||
updateRouteStatus(
|
||||
{
|
||||
time: trip.summary.time,
|
||||
distance: trip.summary.length,
|
||||
currentManeuver: trip.legs[0].maneuvers[instructionIdx],
|
||||
},
|
||||
maneuver.lanes,
|
||||
);
|
||||
currentManeuver = maneuver;
|
||||
|
||||
// document.querySelector<HTMLDivElement>("#lanes")!.innerHTML = "";
|
||||
@ -266,22 +299,27 @@ export async function startNavigation(trip: Trip) {
|
||||
if (instructionIdx > 0) {
|
||||
const prevManeuver = trip.legs[0].maneuvers[instructionIdx - 1];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
updateRouteStatus({
|
||||
time: trip.summary.time,
|
||||
distance: trip.summary.length,
|
||||
currentManeuver: trip.legs[0].maneuvers[0],
|
||||
}, trip.legs[0].maneuvers[0].lanes);
|
||||
updateRouteStatus(
|
||||
{
|
||||
time: trip.summary.time,
|
||||
distance: trip.summary.length,
|
||||
currentManeuver: trip.legs[0].maneuvers[0],
|
||||
},
|
||||
trip.legs[0].maneuvers[0].lanes,
|
||||
);
|
||||
currentTrip = trip;
|
||||
}
|
||||
|
||||
export async function stopNavigation() {
|
||||
if(int) clearInterval(int);
|
||||
if (int) clearInterval(int);
|
||||
await KeepAwake.allowSleep();
|
||||
hideRouteStatus();
|
||||
document.querySelector<HTMLBodyElement>("body")!.classList.remove("isInTrip");
|
||||
@ -320,7 +358,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
|
||||
shift += 5;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
let deltaLat = (result & 1) ? ~(result >> 1) : (result >> 1);
|
||||
let deltaLat = result & 1 ? ~(result >> 1) : result >> 1;
|
||||
lat += deltaLat;
|
||||
|
||||
shift = 0;
|
||||
@ -331,7 +369,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
|
||||
shift += 5;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
let deltaLng = (result & 1) ? ~(result >> 1) : (result >> 1);
|
||||
let deltaLng = result & 1 ? ~(result >> 1) : result >> 1;
|
||||
lng += deltaLng;
|
||||
|
||||
// Convert the latitude and longitude to decimal format with six digits of precision
|
||||
|
||||
@ -8,7 +8,7 @@ export type ValhallaCosting =
|
||||
| "motor_scooter"
|
||||
| "multimodal"
|
||||
| "pedestrian";
|
||||
export type ValhallaRequest = {
|
||||
export interface ValhallaRequest {
|
||||
locations: WorldLocation[];
|
||||
costing: ValhallaCosting;
|
||||
units: "miles" | "kilometers";
|
||||
@ -16,8 +16,8 @@ export type ValhallaRequest = {
|
||||
alternates: number;
|
||||
costing_options: ValhallaCostingOptions;
|
||||
turn_lanes: boolean;
|
||||
};
|
||||
export type GeneralCostingOptions = {
|
||||
}
|
||||
export interface GeneralCostingOptions {
|
||||
/**
|
||||
* 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
|
||||
@ -59,7 +59,7 @@ export type GeneralCostingOptions = {
|
||||
* @default 0 for trucks, 15 for cars, buses, motor scooters and motorcycles
|
||||
*/
|
||||
service_penalty?: number;
|
||||
};
|
||||
}
|
||||
export type AutomobileCostingOptions = {
|
||||
/**
|
||||
* A penalty applied when a gate or bollard with access=private is encountered.
|
||||
@ -226,7 +226,7 @@ export type AutomobileCostingOptions = {
|
||||
*/
|
||||
hierarchy_limits?: void;
|
||||
} & GeneralCostingOptions;
|
||||
export type OtherCostingOptions = {
|
||||
export interface OtherCostingOptions {
|
||||
/**
|
||||
* The height of the vehicle (in meters).
|
||||
* @default 1.9 for car, bus, taxi and 4.11 for truck
|
||||
@ -267,7 +267,7 @@ export type OtherCostingOptions = {
|
||||
* @default false
|
||||
*/
|
||||
include_hot?: boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* The type of the bicycle.
|
||||
* 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.
|
||||
*/
|
||||
export type BicycleType = "Road" | "Hybrid" | "City" | "Mountain";
|
||||
export type BicycleCostingOptions = {
|
||||
export interface BicycleCostingOptions {
|
||||
/**
|
||||
* @default "Hybrid"
|
||||
*/
|
||||
@ -367,9 +367,8 @@ export type BicycleCostingOptions = {
|
||||
* @default false
|
||||
*/
|
||||
shortest?: boolean;
|
||||
|
||||
};
|
||||
export type BikeshareCostingOptions = {}; // TODO
|
||||
}
|
||||
export type BikeshareCostingOptions = unknown; // TODO
|
||||
export type MotorScooterCostingOptions = {
|
||||
/**
|
||||
* A rider's propensity to use primary roads.
|
||||
@ -398,9 +397,9 @@ export type MotorScooterCostingOptions = {
|
||||
*/
|
||||
use_hills?: boolean;
|
||||
} & AutomobileCostingOptions;
|
||||
export type MultimodalCostingOptions = {}; // TODO
|
||||
export type PedestrianCostingOptions = {}; // TODO
|
||||
export type TruckCostingOptions = {
|
||||
export type MultimodalCostingOptions = unknown; // TODO
|
||||
export type PedestrianCostingOptions = unknown; // TODO
|
||||
export interface TruckCostingOptions {
|
||||
/**
|
||||
* The length of the truck (in meters).
|
||||
* @default 21.64
|
||||
@ -448,8 +447,8 @@ export type TruckCostingOptions = {
|
||||
* @default 0
|
||||
*/
|
||||
use_truck_route?: boolean;
|
||||
};
|
||||
export type ValhallaCostingOptions = {
|
||||
}
|
||||
export interface ValhallaCostingOptions {
|
||||
auto?: AutomobileCostingOptions & OtherCostingOptions;
|
||||
bicycle?: BicycleCostingOptions;
|
||||
bus?: AutomobileCostingOptions & OtherCostingOptions;
|
||||
@ -459,4 +458,4 @@ export type ValhallaCostingOptions = {
|
||||
motor_scooter?: MotorScooterCostingOptions;
|
||||
multimodal?: MultimodalCostingOptions;
|
||||
pedestrian?: PedestrianCostingOptions;
|
||||
};
|
||||
}
|
||||
|
||||
23
src/lib/services/navigation/navigation.d.ts
vendored
23
src/lib/services/navigation/navigation.d.ts
vendored
@ -1,15 +1,18 @@
|
||||
type Language = "de-DE" | "en-US";
|
||||
type WorldLocation = { lat: number; lon: number };
|
||||
interface WorldLocation {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
type Units = "kilometers" | "miles";
|
||||
|
||||
type RouteResult = {
|
||||
interface RouteResult {
|
||||
alternates?: {
|
||||
trip: Trip;
|
||||
}[];
|
||||
trip: Trip;
|
||||
}
|
||||
|
||||
type Trip = {
|
||||
interface Trip {
|
||||
language: Language;
|
||||
legs: Leg[];
|
||||
status: number;
|
||||
@ -17,16 +20,16 @@ type Trip = {
|
||||
summary: Summary;
|
||||
units: Units;
|
||||
locations: WorldLocation[];
|
||||
};
|
||||
}
|
||||
|
||||
type Leg = {
|
||||
interface Leg {
|
||||
maneuvers: Maneuver[];
|
||||
shape: string;
|
||||
summary: Summary;
|
||||
locations: WorldLocation[];
|
||||
}
|
||||
|
||||
type Summary = {
|
||||
interface Summary {
|
||||
cost: number;
|
||||
has_ferry: boolean;
|
||||
has_highway: boolean;
|
||||
@ -55,13 +58,13 @@ type Summary = {
|
||||
* 512 = MergeToLeft
|
||||
* 1024 = MergeToRight
|
||||
*/
|
||||
type Lane = {
|
||||
interface Lane {
|
||||
directions: number;
|
||||
valid: number;
|
||||
active: number;
|
||||
};
|
||||
}
|
||||
|
||||
type Maneuver = {
|
||||
interface Maneuver {
|
||||
bearing_after: number;
|
||||
begin_shape_index: number;
|
||||
cost: number;
|
||||
@ -78,4 +81,4 @@ type Maneuver = {
|
||||
verbal_pre_transition_instruction: string;
|
||||
verbal_succinct_transition_instruction: string;
|
||||
lanes?: Lane[];
|
||||
};
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ export const routing = $state({
|
||||
int: null as NodeJS.Timeout | null,
|
||||
isOffRoute: false,
|
||||
currentManeuver: null as Maneuver | null,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
export function resetRouting() {
|
||||
routing.geojson.route = null;
|
||||
@ -30,8 +30,9 @@ export function resetRouting() {
|
||||
|
||||
export async function fetchRoute(server: string, request: ValhallaRequest) {
|
||||
try {
|
||||
const res = await fetch(server + "/route?json=" + JSON.stringify(request))
|
||||
.then((res) => res.json());
|
||||
const res = await fetch(
|
||||
server + "/route?json=" + JSON.stringify(request),
|
||||
).then((res) => res.json());
|
||||
|
||||
console.log(res);
|
||||
return res;
|
||||
@ -65,9 +66,9 @@ function geometryToGeoJSON(polyline: WorldLocation[]): GeoJSON.Feature {
|
||||
}
|
||||
|
||||
export function decodePolyline(encoded: string): WorldLocation[] {
|
||||
let points = [];
|
||||
const points = [];
|
||||
let index = 0;
|
||||
let len = encoded.length;
|
||||
const len = encoded.length;
|
||||
let lat = 0;
|
||||
let lng = 0;
|
||||
|
||||
@ -81,7 +82,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
|
||||
shift += 5;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
let deltaLat = (result & 1) ? ~(result >> 1) : (result >> 1);
|
||||
const deltaLat = result & 1 ? ~(result >> 1) : result >> 1;
|
||||
lat += deltaLat;
|
||||
|
||||
shift = 0;
|
||||
@ -92,7 +93,7 @@ export function decodePolyline(encoded: string): WorldLocation[] {
|
||||
shift += 5;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
let deltaLng = (result & 1) ? ~(result >> 1) : (result >> 1);
|
||||
const deltaLng = result & 1 ? ~(result >> 1) : result >> 1;
|
||||
lng += deltaLng;
|
||||
|
||||
// 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[]) {
|
||||
routing.geojson.routePast = null;
|
||||
routing.geojson.route = tripToGeoJSON(trips[0]);
|
||||
if(trips[1]) routing.geojson.al0 = tripToGeoJSON(trips[1]);
|
||||
if(trips[2]) routing.geojson.al1 = tripToGeoJSON(trips[2]);
|
||||
if (trips[1]) routing.geojson.al0 = tripToGeoJSON(trips[1]);
|
||||
if (trips[2]) routing.geojson.al1 = tripToGeoJSON(trips[2]);
|
||||
}
|
||||
|
||||
export function drawRoute(trip: Trip) {
|
||||
@ -119,7 +120,9 @@ export function drawRoute(trip: Trip) {
|
||||
function drawCurrentTrip() {
|
||||
if (!routing.currentTrip) return;
|
||||
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) {
|
||||
@ -131,7 +134,8 @@ export async function startRoute(trip: Trip) {
|
||||
routing.currentTripInfo.isOffRoute = false;
|
||||
|
||||
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);
|
||||
}
|
||||
@ -143,7 +147,8 @@ async function tickRoute() {
|
||||
const info = routing.currentTripInfo;
|
||||
if (!trip) return;
|
||||
|
||||
const currentManeuver = trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx];
|
||||
const currentManeuver =
|
||||
trip.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx];
|
||||
if (!currentManeuver) {
|
||||
// No more maneuvers, stop navigation
|
||||
stopNavigation();
|
||||
@ -155,14 +160,14 @@ async function tickRoute() {
|
||||
const polyline = decodePolyline(trip.legs[0].shape);
|
||||
|
||||
// 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!");
|
||||
stopNavigation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user is on the route
|
||||
if(!isOnShape(location, polyline)) {
|
||||
if (!isOnShape(location, polyline)) {
|
||||
console.log("Off route!");
|
||||
info.isOffRoute = true;
|
||||
// TODO: Implement re-routing logic
|
||||
@ -171,18 +176,24 @@ async function tickRoute() {
|
||||
info.isOffRoute = false;
|
||||
}
|
||||
|
||||
if (currentManeuver.verbal_pre_transition_instruction && !hasAnnouncedPreInstruction) {
|
||||
if (
|
||||
currentManeuver.verbal_pre_transition_instruction &&
|
||||
!hasAnnouncedPreInstruction
|
||||
) {
|
||||
const distanceToEnd = calculateDistance(location, polyline[bgi]);
|
||||
// console.log("Distance to end of current maneuver: ", distanceToEnd, " meters");
|
||||
if (distanceToEnd <= 100) {
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -196,15 +207,25 @@ async function tickRoute() {
|
||||
// announce the "verbal_post_transition_instruction"
|
||||
if (currentManeuver.verbal_post_transition_instruction) {
|
||||
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) {
|
||||
console.log("[Verbal instruction] ", currentManeuver.verbal_post_transition_instruction);
|
||||
console.log(
|
||||
"[Verbal instruction] ",
|
||||
currentManeuver.verbal_post_transition_instruction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to the next maneuver
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@ -228,8 +249,8 @@ function getUserLocation(): WorldLocation {
|
||||
// return geolocate.currentLocation!;
|
||||
return {
|
||||
lat: location.lat,
|
||||
lon: location.lng
|
||||
}
|
||||
lon: location.lng,
|
||||
};
|
||||
// const lnglat = window.geolocate._userLocationDotMarker.getLngLat();
|
||||
// return { lat: lnglat.lat, lon: lnglat.lng };
|
||||
// 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)
|
||||
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;
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
@ -273,7 +301,8 @@ function isOnPoint(location: WorldLocation, point: WorldLocation) {
|
||||
const tolerance = 6 / 111320; // 1 degree latitude ≈ 111.32 km
|
||||
// Calculate the distance from the location to the point
|
||||
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
|
||||
return distance <= tolerance;
|
||||
@ -289,23 +318,36 @@ function isOnShape(location: WorldLocation, shape: WorldLocation[]) {
|
||||
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 lat1 = point1.lat * (Math.PI / 180);
|
||||
const lat2 = point2.lat * (Math.PI / 180);
|
||||
const deltaLat = (point2.lat - point1.lat) * (Math.PI / 180);
|
||||
const deltaLon = (point2.lon - point1.lon) * (Math.PI / 180);
|
||||
|
||||
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) *
|
||||
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
|
||||
const a =
|
||||
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 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));
|
||||
|
||||
return R * c; // Distance in meters
|
||||
}
|
||||
|
||||
export function zoomToPoints(from: WorldLocation, to: WorldLocation, map: maplibregl.Map) {
|
||||
const getBoundingBox = (point1: [number, number], point2: [number, number]): LngLatBoundsLike => {
|
||||
export function zoomToPoints(
|
||||
from: WorldLocation,
|
||||
to: WorldLocation,
|
||||
map: maplibregl.Map,
|
||||
) {
|
||||
const getBoundingBox = (
|
||||
point1: [number, number],
|
||||
point2: [number, number],
|
||||
): LngLatBoundsLike => {
|
||||
const [lng1, lat1] = point1;
|
||||
const [lng2, lat2] = point2;
|
||||
|
||||
@ -315,8 +357,8 @@ export function zoomToPoints(from: WorldLocation, to: WorldLocation, map: maplib
|
||||
return [sw, ne];
|
||||
};
|
||||
|
||||
map.fitBounds(getBoundingBox([from.lon, from.lat], [to.lon, to.lat]), {
|
||||
padding: 40
|
||||
map.fitBounds(getBoundingBox([from.lon, from.lat], [to.lon, to.lat]), {
|
||||
padding: 40,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { getOIDCConfig, hasCapability } from "./lnv";
|
||||
|
||||
export async function getAuthURL() {
|
||||
if(!await hasCapability("auth")) {
|
||||
if (!(await hasCapability("auth"))) {
|
||||
throw new Error("Server does not support OIDC authentication");
|
||||
}
|
||||
const oidcConfig = await getOIDCConfig();
|
||||
@ -17,8 +17,7 @@ export async function getAuthURL() {
|
||||
const state = generateRandomString(16);
|
||||
|
||||
return {
|
||||
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}`,
|
||||
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}`,
|
||||
codeVerifier: pkce.codeVerifier,
|
||||
state,
|
||||
};
|
||||
@ -41,7 +40,9 @@ function generateRandomString(length: number) {
|
||||
|
||||
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)
|
||||
@ -60,7 +61,7 @@ async function sha256(input: string | undefined): Promise<ArrayBuffer> {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
const oidcConfig = await getOIDCConfig();
|
||||
@ -81,8 +82,8 @@ export async function getOIDCUser(code: string, codeVerifier: string) {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: params
|
||||
}).then(res => res.json());
|
||||
body: params,
|
||||
}).then((res) => res.json());
|
||||
|
||||
return res;
|
||||
// return JSON.parse(atob(id_token.split(".")[1]));
|
||||
|
||||
@ -8,6 +8,10 @@ export function cn(...inputs: ClassValue[]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||
// 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 WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
||||
ref?: U | null;
|
||||
};
|
||||
|
||||
@ -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";
|
||||
|
||||
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);
|
||||
let costingOptions: ValhallaCostingOptions = costing == "auto" ? {
|
||||
auto: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed
|
||||
}
|
||||
} : costing == "motor_scooter" ? {
|
||||
motor_scooter: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed
|
||||
}
|
||||
} : costing == "truck" ? {
|
||||
truck: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed,
|
||||
length: vehicle.length,
|
||||
weight: vehicle.weight,
|
||||
axle_load: vehicle.axisLoad
|
||||
}
|
||||
} : costing == "bicycle" ? {
|
||||
bicycle: {
|
||||
cycling_speed: vehicle.actualMaxSpeed
|
||||
}
|
||||
} : {};
|
||||
const costingOptions: ValhallaCostingOptions =
|
||||
costing == "auto"
|
||||
? {
|
||||
auto: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed,
|
||||
},
|
||||
}
|
||||
: costing == "motor_scooter"
|
||||
? {
|
||||
motor_scooter: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed,
|
||||
},
|
||||
}
|
||||
: costing == "truck"
|
||||
? {
|
||||
truck: {
|
||||
top_speed: vehicle.legalMaxSpeed,
|
||||
fixed_speed: vehicle.actualMaxSpeed,
|
||||
length: vehicle.length,
|
||||
weight: vehicle.weight,
|
||||
axle_load: vehicle.axisLoad,
|
||||
},
|
||||
}
|
||||
: costing == "bicycle"
|
||||
? {
|
||||
bicycle: {
|
||||
cycling_speed: vehicle.actualMaxSpeed,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
return {
|
||||
locations,
|
||||
costing,
|
||||
@ -49,6 +65,6 @@ export function createValhallaRequest(vehicle: Vehicle, locations: WorldLocation
|
||||
alternates: 2,
|
||||
language: "de-DE",
|
||||
costing_options: costingOptions,
|
||||
turn_lanes: true
|
||||
}
|
||||
turn_lanes: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -6,19 +6,42 @@ bicycle, prefer cycleways and bicycle lanes = bicycle
|
||||
truck, prioritizes truck routes = truck
|
||||
motor_scooter = motor scooter, moped, lkfz
|
||||
*/
|
||||
export const VehicleTypes = ["car", "truck", "motorcycle", "bicycle", "motor_scooter"] as const;
|
||||
export type VehicleType = typeof VehicleTypes[number];
|
||||
export const FuelTypes = ["petrol", "diesel", "electric"] as const;
|
||||
export type FuelType = typeof FuelTypes[number];
|
||||
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"
|
||||
export const VehicleTypes = [
|
||||
"car",
|
||||
"truck",
|
||||
"motorcycle",
|
||||
"bicycle",
|
||||
"motor_scooter",
|
||||
] 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 type Vehicle = {
|
||||
export type VehicleType = (typeof VehicleTypes)[number];
|
||||
export const FuelTypes = ["petrol", "diesel", "electric"] as const;
|
||||
export type FuelType = (typeof FuelTypes)[number];
|
||||
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;
|
||||
legalMaxSpeed: number;
|
||||
actualMaxSpeed: number;
|
||||
@ -31,7 +54,7 @@ export type Vehicle = {
|
||||
emissionClass: string;
|
||||
fuelType: FuelType;
|
||||
preferredFuel: PreferredFuel;
|
||||
};
|
||||
}
|
||||
|
||||
export const DefaultVehicle: Vehicle = {
|
||||
name: "Default Vehicle",
|
||||
@ -40,49 +63,64 @@ export const DefaultVehicle: Vehicle = {
|
||||
type: "motor_scooter",
|
||||
emissionClass: "Euro 4",
|
||||
fuelType: "diesel",
|
||||
preferredFuel: "Diesel"
|
||||
}
|
||||
preferredFuel: "Diesel",
|
||||
};
|
||||
|
||||
type StateValue<T> = {v: T};
|
||||
export const vehicles: Vehicle[] = $state(localStorage.getItem("vehicles") ? JSON.parse(localStorage.getItem("vehicles")!) : []);
|
||||
interface StateValue<T> {
|
||||
v: T;
|
||||
}
|
||||
export const vehicles: Vehicle[] = $state(
|
||||
localStorage.getItem("vehicles")
|
||||
? JSON.parse(localStorage.getItem("vehicles")!)
|
||||
: [],
|
||||
);
|
||||
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 = () => {
|
||||
return vehicles[selectedVehicleIdx.v !== null ? selectedVehicleIdx.v : 0] || null
|
||||
return (
|
||||
vehicles[selectedVehicleIdx.v !== null ? selectedVehicleIdx.v : 0] || null
|
||||
);
|
||||
};
|
||||
|
||||
export function setVehicles(_vehicles: Vehicle[]) {
|
||||
// vehicles = _vehicles;
|
||||
// Hack to update without reassigning the array
|
||||
vehicles.length = 0;
|
||||
_vehicles.forEach(vehicle => vehicles.push(vehicle));
|
||||
_vehicles.forEach((vehicle) => vehicles.push(vehicle));
|
||||
localStorage.setItem("vehicles", JSON.stringify(vehicles));
|
||||
}
|
||||
|
||||
export function selectVehicle(vehicle: Vehicle | null) {
|
||||
if(vehicle == null) {
|
||||
if (vehicle == null) {
|
||||
selectedVehicleIdx.v = null;
|
||||
} else {
|
||||
selectedVehicleIdx.v = vehicles.findIndex(v => v.name === vehicle.name);
|
||||
if(selectedVehicleIdx.v === -1) {
|
||||
selectedVehicleIdx.v = vehicles.findIndex((v) => v.name === vehicle.name);
|
||||
if (selectedVehicleIdx.v === -1) {
|
||||
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
|
||||
*/
|
||||
export function isValidFuel(vehicle: Vehicle): boolean {
|
||||
if(vehicle.fuelType == "petrol") {
|
||||
return vehicle.preferredFuel == "Super" || vehicle.preferredFuel == "Super E10";
|
||||
if (vehicle.fuelType == "petrol") {
|
||||
return (
|
||||
vehicle.preferredFuel == "Super" || vehicle.preferredFuel == "Super E10"
|
||||
);
|
||||
}
|
||||
if(vehicle.fuelType == "diesel") {
|
||||
if (vehicle.fuelType == "diesel") {
|
||||
return vehicle.preferredFuel == "Diesel";
|
||||
}
|
||||
if(vehicle.fuelType == "electric") {
|
||||
if (vehicle.fuelType == "electric") {
|
||||
return EVConnectors.includes(vehicle.preferredFuel as EVConnector);
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
export function checkWebGL() {
|
||||
if(window.WebGLRenderingContext) {
|
||||
if (window.WebGLRenderingContext) {
|
||||
const canvas = document.createElement("canvas");
|
||||
try {
|
||||
const ctx = canvas.getContext("webgl2") || canvas.getContext("webgl");
|
||||
if(ctx && typeof ctx.getParameter == "function") {
|
||||
if (ctx && typeof ctx.getParameter == "function") {
|
||||
return true;
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (_e) {
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
26
src/main.ts
26
src/main.ts
@ -1,21 +1,25 @@
|
||||
import { mount } from 'svelte'
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
import { mount } from "svelte";
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
|
||||
if(location.href.includes("/login/callback")) {
|
||||
if (location.href.includes("/login/callback")) {
|
||||
const url = new URL(location.href);
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
if(code && state) {
|
||||
window.opener.postMessage({
|
||||
code, state
|
||||
}, window.location.origin);
|
||||
if (code && state) {
|
||||
window.opener.postMessage(
|
||||
{
|
||||
code,
|
||||
state,
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
target: document.getElementById("app")!,
|
||||
});
|
||||
|
||||
export default app
|
||||
export default app;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
compilerOptions: {
|
||||
runes: true
|
||||
}
|
||||
}
|
||||
runes: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"]
|
||||
}
|
||||
}
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), svelte()],
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user