diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b619009b..e6c7f4284 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,30 @@ concurrency: cancel-in-progress: true jobs: + ui: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Dependencies + working-directory: ui + run: npm install -f + + - name: Code Lint + working-directory: ui + run: npm run lint + + - name: Svelte Check + working-directory: ui + run: npm run check + + - name: Build + working-directory: ui + run: npm run build + formatting: runs-on: ubuntu-latest container: ghcr.io/motis-project/docker-cpp-build @@ -110,7 +134,7 @@ jobs: # ==== RESTORE CACHE ==== - name: Restore buildcache Cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: restore-buildcache with: path: ${{ github.workspace }}/.buildcache @@ -121,7 +145,7 @@ jobs: buildcache-${{ matrix.config.preset }}- - name: Dependencies Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ github.workspace }}/deps key: deps-${{ hashFiles('.pkg') }} @@ -145,7 +169,7 @@ jobs: # ==== SAVE CACHE ==== - name: Save buildcache cache if: always() - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: ${{ github.workspace }}/.buildcache key: ${{ steps.restore-buildcache.outputs.cache-primary-key }} diff --git a/assistance.csv b/assistance_times.csv similarity index 100% rename from assistance.csv rename to assistance_times.csv diff --git a/ui/.eslintignore b/ui/.eslintignore index 38972655f..d967be1bb 100644 --- a/ui/.eslintignore +++ b/ui/.eslintignore @@ -11,3 +11,9 @@ node_modules pnpm-lock.yaml package-lock.json yarn.lock + +# Generated code +src/lib/openapi + +# shadcn-svelte +src/lib/components \ No newline at end of file diff --git a/ui/.prettierignore b/ui/.prettierignore index cc41cea9b..af61c7f51 100644 --- a/ui/.prettierignore +++ b/ui/.prettierignore @@ -2,3 +2,5 @@ pnpm-lock.yaml package-lock.json yarn.lock +src/lib/openapi/** +src/lib/components/** \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index 60e3f80cf..7de8a66b8 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -12,6 +12,7 @@ "bits-ui": "^0.21.12", "clsx": "^2.1.1", "colord": "^2.9.3", + "geojson": "^0.5.0", "lucide-svelte": "^0.445.0", "maplibre-gl": "^4.3.2", "polyline": "^0.2.0", @@ -555,14 +556,14 @@ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" }, "node_modules/@hey-api/client-fetch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.3.1.tgz", - "integrity": "sha512-JwhK2f8h4JtSC9aBjx6QvKx9wo24IEv47D/vh3RrKcb3hjoHOvMmSlRzOICApjXQflfKGjbXvdPrrFyxQgGqsQ==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.3.3.tgz", + "integrity": "sha512-KwaKmfltKNfBefeyo19JA7zYwC7BTvWlew1/tfsFi0gIWl2dLVVvhGNgvfUH1kx/bpdC65PXzE9qgkQ/njVPoA==" }, "node_modules/@hey-api/openapi-ts": { - "version": "0.53.2", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.53.2.tgz", - "integrity": "sha512-Z4wM4NeFghRHibukYZHnk3AmDuisIlHYKTNhSIFt7e7njQmJt2xYSJZUmFp10y5jt/V1eIw4L9yc0kfsZPSCXQ==", + "version": "0.53.4", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.53.4.tgz", + "integrity": "sha512-5GJ7lQ8FjRFVTqmsGhikj5IpNujySXwF2pN8XJDBP6UXkKycr7GbZrnI0dAww+15Md6MixsClUZuvodp3qJ20g==", "dev": true, "dependencies": { "@apidevtools/json-schema-ref-parser": "11.7.0", @@ -865,12 +866,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.1.tgz", - "integrity": "sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", + "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", "dev": true, "dependencies": { - "playwright": "1.47.1" + "playwright": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -886,9 +887,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.0.tgz", - "integrity": "sha512-/IZQvg6ZR0tAkEi4tdXOraQoWeJy9gbQ/cx4I7k9dJaCk9qrXEcdouxRVz5kZXt5C2bQ9pILoAA+KB4C/d3pfw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -899,9 +900,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.0.tgz", - "integrity": "sha512-ETHi4bxrYnvOtXeM7d4V4kZWixib2jddFacJjsOjwbgYSRsyXYtZHC4ht134OsslPIcnkqT+TKV4eU8rNBKyyQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -912,9 +913,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.0.tgz", - "integrity": "sha512-ZWgARzhSKE+gVUX7QWaECoRQsPwaD8ZR0Oxb3aUpzdErTvlEadfQpORPXkKSdKbFci9v8MJfkTtoEHnnW9Ulng==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -925,9 +926,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.0.tgz", - "integrity": "sha512-h0ZAtOfHyio8Az6cwIGS+nHUfRMWBDO5jXB8PQCARVF6Na/G6XS2SFxDl8Oem+S5ZsHQgtsI7RT4JQnI1qrlaw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -938,9 +939,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.0.tgz", - "integrity": "sha512-9pxQJSPwFsVi0ttOmqLY4JJ9pg9t1gKhK0JDbV1yUEETSx55fdyCjt39eBQ54OQCzAF0nVGO6LfEH1KnCPvelA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -951,9 +952,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.0.tgz", - "integrity": "sha512-YJ5Ku5BmNJZb58A4qSEo3JlIG4d3G2lWyBi13ABlXzO41SsdnUKi3HQHe83VpwBVG4jHFTW65jOQb8qyoR+qzg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -964,9 +965,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.0.tgz", - "integrity": "sha512-U4G4u7f+QCqHlVg1Nlx+qapZy+QoG+NV6ux+upo/T7arNGwKvKP2kmGM4W5QTbdewWFgudQxi3kDNST9GT1/mg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -977,9 +978,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.0.tgz", - "integrity": "sha512-aQpNlKmx3amwkA3a5J6nlXSahE1ijl0L9KuIjVOUhfOh7uw2S4piR3mtpxpRtbnK809SBtyPsM9q15CPTsY7HQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -990,9 +991,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.0.tgz", - "integrity": "sha512-9fx6Zj/7vve/Fp4iexUFRKb5+RjLCff6YTRQl4CoDhdMfDoobWmhAxQWV3NfShMzQk1Q/iCnageFyGfqnsmeqQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -1003,9 +1004,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.0.tgz", - "integrity": "sha512-VWQiCcN7zBgZYLjndIEh5tamtnKg5TGxyZPWcN9zBtXBwfcGSZ5cHSdQZfQH/GB4uRxk0D3VYbOEe/chJhPGLQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -1016,9 +1017,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.0.tgz", - "integrity": "sha512-EHmPnPWvyYqncObwqrosb/CpH3GOjE76vWVs0g4hWsDRUVhg61hBmlVg5TPXqF+g+PvIbqkC7i3h8wbn4Gp2Fg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -1029,9 +1030,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.0.tgz", - "integrity": "sha512-tsSWy3YQzmpjDKnQ1Vcpy3p9Z+kMFbSIesCdMNgLizDWFhrLZIoN21JSq01g+MZMDFF+Y1+4zxgrlqPjid5ohg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -1042,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.0.tgz", - "integrity": "sha512-anr1Y11uPOQrpuU8XOikY5lH4Qu94oS6j0xrulHk3NkLDq19MlX8Ng/pVipjxBJ9a2l3+F39REZYyWQFkZ4/fw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -1055,9 +1056,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.0.tgz", - "integrity": "sha512-7LB+Bh+Ut7cfmO0m244/asvtIGQr5pG5Rvjz/l1Rnz1kDzM02pSX9jPaS0p+90H5I1x4d1FkCew+B7MOnoatNw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -1068,9 +1069,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.0.tgz", - "integrity": "sha512-+3qZ4rer7t/QsC5JwMpcvCVPRcJt1cJrYS/TMJZzXIJbxWFQEVhrIc26IhB+5Z9fT9umfVc+Es2mOZgl+7jdJQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -1081,9 +1082,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.0.tgz", - "integrity": "sha512-YdicNOSJONVx/vuPkgPTyRoAPx3GbknBZRCOUkK84FJ/YTfs/F0vl/YsMscrB6Y177d+yDRcj+JWMPMCgshwrA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -1688,9 +1689,9 @@ "dev": true }, "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "engines": { "node": ">= 0.4" } @@ -1791,9 +1792,9 @@ } }, "node_modules/bits-ui": { - "version": "0.21.14", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.14.tgz", - "integrity": "sha512-goCDjXy4O7qQZiaOpNtojYtDm0vJA7RBtPQyCqQFmj6RzIvsa0d6JOPNY7IpRZT9Oc3uR0nNb5WqfOwehgtuVw==", + "version": "0.21.15", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.15.tgz", + "integrity": "sha512-+m5WSpJnFdCcNdXSTIVC1WYBozipO03qRh03GFWgrdxoHiolCfwW71EYG4LPCWYPG6KcTZV0Cj6iHSiZ7cdKdg==", "dependencies": { "@internationalized/date": "^3.5.1", "@melt-ui/svelte": "0.76.2", @@ -1842,9 +1843,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -1861,8 +1862,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -1954,9 +1955,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001662", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", - "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -2226,9 +2227,9 @@ } }, "node_modules/devalue": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", - "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "dev": true }, "node_modules/didyoumean": { @@ -2297,9 +2298,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.5.25", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz", - "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==", + "version": "1.5.29", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", + "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", "dev": true }, "node_modules/emoji-regex": { @@ -2865,6 +2866,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/geojson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", + "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/geojson-vt": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", @@ -3515,7 +3524,6 @@ "version": "0.445.0", "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.445.0.tgz", "integrity": "sha512-+4q7aL0fJArdAYw8CzO5DhfH4K5Hx/Z3JX5ggUDVMKjq6UHWWSvQ6wKOe7yNxc5muw22U7x3tzoLbHr/5pYPsg==", - "license": "ISC", "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } @@ -3529,9 +3537,9 @@ } }, "node_modules/maplibre-gl": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.0.tgz", - "integrity": "sha512-hkt7je7NxiMQE8EpCxLWP8t6tkK6SkrMe0hIBjYd4Ar/Q7BOCILxthGmGnU993Mwmkvs2mGiXnVUSOK12DeCzg==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", + "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -3819,9 +3827,9 @@ } }, "node_modules/nypm": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.11.tgz", - "integrity": "sha512-E5GqaAYSnbb6n1qZyik2wjPDZON43FqOJO59+3OkWrnmQtjggrMOVnsyzfjxp/tS6nlYJBA4zRA5jSM2YaadMg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", "dev": true, "dependencies": { "citty": "^0.1.6", @@ -4080,12 +4088,12 @@ } }, "node_modules/playwright": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.1.tgz", - "integrity": "sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", + "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", "dev": true, "dependencies": { - "playwright-core": "1.47.1" + "playwright-core": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -4098,9 +4106,9 @@ } }, "node_modules/playwright-core": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.1.tgz", - "integrity": "sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", + "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -4519,9 +4527,9 @@ } }, "node_modules/rollup": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.0.tgz", - "integrity": "sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -4534,22 +4542,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.0", - "@rollup/rollup-android-arm64": "4.22.0", - "@rollup/rollup-darwin-arm64": "4.22.0", - "@rollup/rollup-darwin-x64": "4.22.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.0", - "@rollup/rollup-linux-arm-musleabihf": "4.22.0", - "@rollup/rollup-linux-arm64-gnu": "4.22.0", - "@rollup/rollup-linux-arm64-musl": "4.22.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.0", - "@rollup/rollup-linux-riscv64-gnu": "4.22.0", - "@rollup/rollup-linux-s390x-gnu": "4.22.0", - "@rollup/rollup-linux-x64-gnu": "4.22.0", - "@rollup/rollup-linux-x64-musl": "4.22.0", - "@rollup/rollup-win32-arm64-msvc": "4.22.0", - "@rollup/rollup-win32-ia32-msvc": "4.22.0", - "@rollup/rollup-win32-x64-msvc": "4.22.0", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, @@ -5043,9 +5051,9 @@ } }, "node_modules/svelte": { - "version": "5.0.0-next.254", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.254.tgz", - "integrity": "sha512-U5xNn38rOqNWa1QYliSQDS2gGDyz+YjIvmMHBU8tlaD4+ni1H3PtOkAyIjfK4a2a4RB7YT7VBldLY7dCzAtGEw==", + "version": "5.0.0-next.259", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.259.tgz", + "integrity": "sha512-trRFSjKD+11KbXerGmBT0Uc+ZSNUhxn0aQ02q9tjtig/FV24dpZlXmCrcZTZliOLS0P8JWjw6xaWgNheZZoYOg==", "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -5212,9 +5220,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", - "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -5586,9 +5594,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "dependencies": { "esbuild": "^0.21.3", diff --git a/ui/package.json b/ui/package.json index e9ed8f3cf..574b2460a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -51,6 +51,7 @@ "polyline": "^0.2.0", "svelte-radix": "^1.1.0", "tailwind-merge": "^2.3.0", - "tailwind-variants": "^0.2.1" + "tailwind-variants": "^0.2.1", + "geojson": "^0.5.0" } -} \ No newline at end of file +} diff --git a/ui/src/app.css b/ui/src/app.css index 83f2661c2..a566b3b53 100644 --- a/ui/src/app.css +++ b/ui/src/app.css @@ -37,7 +37,7 @@ } .dark { - --background: 222.2 84% 4.9%; + --background: 222.2 84% 4%; --foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; diff --git a/ui/src/app.html b/ui/src/app.html index 1f98ca369..bab364d11 100644 --- a/ui/src/app.html +++ b/ui/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/ui/src/lib/AddressTypeahead.svelte b/ui/src/lib/AddressTypeahead.svelte index 85149eb91..84aef986e 100644 --- a/ui/src/lib/AddressTypeahead.svelte +++ b/ui/src/lib/AddressTypeahead.svelte @@ -1,22 +1,28 @@ - -
+ +
{#each items as item (item.value)} - {#if item.value.type == 'STOP'} + {#if item.value.match?.type == 'STOP'} - {:else if item.value.type == 'ADDRESS'} + {:else if item.value.match?.type == 'ADDRESS'} - {:else if item.value.type == 'PLACE'} + {:else if item.value.match?.type == 'PLACE'} {/if} - {item.value.name} + {item.value.match?.name} - {item.area} + {getDisplayArea(item.value.match)} {/each} diff --git a/ui/src/lib/DateInput.svelte b/ui/src/lib/DateInput.svelte index d68fa54c3..7ec8293e7 100644 --- a/ui/src/lib/DateInput.svelte +++ b/ui/src/lib/DateInput.svelte @@ -6,7 +6,7 @@ class: className }: { value: Date; - class: string | undefined; + class?: string; } = $props(); let el: undefined | HTMLInputElement; @@ -30,7 +30,8 @@ )} bind:this={el} onchange={(e) => { - const dateTimeLocalValue = e.target.value; + // @ts-expect-error target exists, value exists + const dateTimeLocalValue = e.target!.value!; const fakeUtcTime = new Date(`${dateTimeLocalValue}Z`); value = new Date(fakeUtcTime.getTime() + fakeUtcTime.getTimezoneOffset() * 60000); }} diff --git a/ui/src/lib/ItineraryToGeoJSON.ts b/ui/src/lib/ItineraryToGeoJSON.ts deleted file mode 100644 index 6a712e8c7..000000000 --- a/ui/src/lib/ItineraryToGeoJSON.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { getColor } from './modeStyle'; -import { type Itinerary } from './openapi/types.gen'; -import polyline from 'polyline'; -import { colord } from 'colord'; - -export function itineraryToGeoJSON(i: Itinerary | null) { - return { - type: 'FeatureCollection', - features: - i === null - ? [] - : i.legs.flatMap((l) => { - if (l.legGeometryWithLevels) { - return l.legGeometryWithLevels.map((p) => { - return { - type: 'Feature', - properties: { - color: '#42a5f5', - outlineColor: '#1966a4', - level: p.from_level, - way: p.osm_way - }, - geometry: { - type: 'LineString', - coordinates: polyline.decode(p.polyline.points, 7).map(([x, y]) => [y, x]) - } - }; - }); - } else { - const color = `#${getColor(l)}`; - const outlineColor = colord(color).darken(0.2).toHex(); - return { - type: 'Feature', - properties: { - outlineColor, - color - }, - geometry: { - type: 'LineString', - coordinates: polyline.decode(l.legGeometry.points, 7).map(([x, y]) => [y, x]) - } - }; - } - }) - }; -} diff --git a/ui/src/lib/Location.ts b/ui/src/lib/Location.ts new file mode 100644 index 000000000..028a671c6 --- /dev/null +++ b/ui/src/lib/Location.ts @@ -0,0 +1,10 @@ +import type { Match } from './openapi'; + +export type Location = { + label?: string; + value: { + match?: Match; + precision?: number; + level?: number; + }; +}; diff --git a/ui/src/lib/PopUp.svelte b/ui/src/lib/PopUp.svelte deleted file mode 100644 index 4224d93a0..000000000 --- a/ui/src/lib/PopUp.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - -
- {@render children()} -
diff --git a/ui/src/lib/Precision.ts b/ui/src/lib/Precision.ts new file mode 100644 index 000000000..bcbb782a1 --- /dev/null +++ b/ui/src/lib/Precision.ts @@ -0,0 +1,25 @@ +export const GEOCODER_PRECISION: number = 50; +export const ZOOM_LEVEL_PRECISION: Array = [ + 9000, // level 0 entire world + 8000, // level 1 entire world + 7000, // level 2 entire world + 6000, // level 3 entire world + 5000, // level 4 entire world + 4000, // level 5 entire world + 3000, // level 6 entire world + 2000, // level 7 entire world + 1600, // level 8 entire world + 800, // level 9 entire world + 400, // level 10 entire world + 200, // level 11 entire world + 100, // level 12 entire world + 50, // level 13 Darmstadt + 50, // level 14 + 50, // level 15 + 50, // level 16 + 20, // level 17 Herrngarten + 10, // level 18 + 5, // level 19 + 5, // level 20 + 5 // level 21 +]; diff --git a/ui/src/lib/ThemeToggle.svelte b/ui/src/lib/ThemeToggle.svelte new file mode 100644 index 000000000..4f4f2edfd --- /dev/null +++ b/ui/src/lib/ThemeToggle.svelte @@ -0,0 +1,23 @@ + + + diff --git a/ui/src/routes/Time.svelte b/ui/src/lib/Time.svelte similarity index 56% rename from ui/src/routes/Time.svelte rename to ui/src/lib/Time.svelte index b201afec4..a6eca7541 100644 --- a/ui/src/routes/Time.svelte +++ b/ui/src/lib/Time.svelte @@ -1,11 +1,21 @@ -
+
{#if delay === undefined || (delay !== undefined && delay !== 0)} {pad(d.getHours())}:{pad(d.getMinutes())} {/if} diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts deleted file mode 100644 index 7ba22f872..000000000 --- a/ui/src/lib/api.ts +++ /dev/null @@ -1,99 +0,0 @@ -import maplibregl from 'maplibre-gl'; - -const baseUrl = 'http://localhost:7999'; - -export class Location { - lat!: number; - lng!: number; - level!: number; - zoom!: number; -} - -export class Id { - name!: string; - id!: string; - src!: number; -} - -export class Footpath { - id!: Id; - loc!: Location; - default?: number; - foot?: number; - wheelchair?: number; - wheelchair_uses_elevator?: boolean; -} - -export class Footpaths { - loc!: Location; - id!: Id; - footpaths!: Array; -} - -export class RoutingQuery { - start!: Location; - destination!: Location; - profile!: string; - direction!: string; -} - -export type Elevator = { - id: string; - desc: string; - state: 'ACTIVE' | 'INACTIVE'; - outOfService: Array<[Date, Date]>; -}; - -const post = async (path: string, req: any) => { - console.log(`FETCH ${path}: ${JSON.stringify(req)}`); - const response = await fetch(`${baseUrl}${path}`, { - method: 'POST', - mode: 'cors', - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(req) - }); - return await response.json(); -}; - -export const getPlatforms = async (bounds: maplibregl.LngLatBounds, level: number) => { - return await post('/api/platforms', { - level: level, - waypoints: bounds.toArray().flat() - }); -}; - -export const getRoute = async (query: RoutingQuery) => { - return await post('/api/route', query); -}; - -export const getGraph = async (bounds: maplibregl.LngLatBounds, level: number) => { - return await post('/api/graph', { - level: level, - waypoints: bounds.toArray().flat() - }); -}; - -export const getLevels = async (bounds: maplibregl.LngLatBounds) => { - return await post('/api/levels', { - waypoints: bounds.toArray().flat() - }); -}; - -export const getMatches = async (bounds: maplibregl.LngLatBounds) => { - return await post('/api/matches', bounds.toArray().flat()); -}; - -export const getElevators = async (bounds: maplibregl.LngLatBounds) => { - return await post('/api/elevators', bounds.toArray().flat()); -}; - -export const getFootpaths = async (station: { id: string; src: number }): Promise => { - return await post('/api/footpaths', station); -}; - -export const updateElevator = async (elevator: Elevator) => { - return await post('/api/update_elevator', elevator); -}; diff --git a/ui/src/lib/components/ui/button/index.ts b/ui/src/lib/components/ui/button/index.ts index 86b4f18de..534fa42e8 100644 --- a/ui/src/lib/components/ui/button/index.ts +++ b/ui/src/lib/components/ui/button/index.ts @@ -6,7 +6,7 @@ const buttonVariants = tv({ base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', variants: { variant: { - default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + default: 'bg-foreground text-primary-foreground shadow hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', diff --git a/ui/src/lib/index.ts b/ui/src/lib/index.ts deleted file mode 100644 index 856f2b6c3..000000000 --- a/ui/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/ui/src/lib/Control.svelte b/ui/src/lib/map/Control.svelte similarity index 56% rename from ui/src/lib/Control.svelte rename to ui/src/lib/map/Control.svelte index ba4372693..57a2d4552 100644 --- a/ui/src/lib/Control.svelte +++ b/ui/src/lib/map/Control.svelte @@ -1,21 +1,22 @@ -
- {@render children()} +
+ {#if children} + {@render children()} + {/if}
- - diff --git a/ui/src/lib/GeoJSON.svelte b/ui/src/lib/map/GeoJSON.svelte similarity index 78% rename from ui/src/lib/GeoJSON.svelte rename to ui/src/lib/map/GeoJSON.svelte index 83e4d92e8..ba6178a48 100644 --- a/ui/src/lib/GeoJSON.svelte +++ b/ui/src/lib/map/GeoJSON.svelte @@ -1,10 +1,12 @@ diff --git a/ui/src/lib/Map.svelte b/ui/src/lib/map/Map.svelte similarity index 65% rename from ui/src/lib/Map.svelte rename to ui/src/lib/map/Map.svelte index ced7c1f14..fe43e1c00 100644 --- a/ui/src/lib/Map.svelte +++ b/ui/src/lib/map/Map.svelte @@ -7,24 +7,26 @@ let { map = $bindable(), zoom = $bindable(), + // eslint-disable-next-line @typescript-eslint/no-unused-vars bounds = $bindable(), style, transformRequest, center, children, - ...props + class: className }: { - map: maplibregl.Map | null; + map?: maplibregl.Map; style: maplibregl.StyleSpecification; transformRequest: maplibregl.RequestTransformFunction; center: maplibregl.LngLatLike; - bounds: maplibregl.LngLatBoundsLike | undefined; + bounds?: maplibregl.LngLatBoundsLike | undefined; zoom: number; - children: Snippet; + children?: Snippet; + class: string; } = $props(); let currStyle: maplibregl.StyleSpecification | null = null; - let ctx = $state<{ map: maplibregl.Map | null }>({ map: null }); + let ctx = $state<{ map: maplibregl.Map | undefined }>({ map: undefined }); setContext('map', ctx); $effect(() => { @@ -33,6 +35,7 @@ } }); + let currentCenter = center; const createMap = (container: HTMLElement) => { map = new maplibregl.Map({ container, zoom, center, style, transformRequest }); @@ -44,6 +47,14 @@ }) ); + map.addImage( + 'shield-dark', + ...createShield({ + fill: 'hsl(0, 0%, 16%)', + stroke: 'hsl(0, 0%, 30%)' + }) + ); + map.on('load', () => { currStyle = style; ctx.map = map; @@ -58,12 +69,21 @@ return { destroy() { ctx.map?.remove(); - ctx.map = null; + ctx.map = undefined; } }; }; + + $effect(() => { + if (center != currentCenter) { + map?.setCenter(center); + currentCenter = center; + } + }); -
- {@render children()} +
+ {#if children} + {@render children()} + {/if}
diff --git a/ui/src/lib/shield.ts b/ui/src/lib/map/shield.ts similarity index 89% rename from ui/src/lib/shield.ts rename to ui/src/lib/map/shield.ts index 0d477e9a4..df5b4ea94 100644 --- a/ui/src/lib/shield.ts +++ b/ui/src/lib/map/shield.ts @@ -1,11 +1,12 @@ import { browser } from '$app/environment'; +import type { StyleImageMetadata } from 'maplibre-gl'; class ShieldOptions { fill!: string; stroke!: string; } -export function createShield(opt: ShieldOptions): [ImageData, Object] { +export function createShield(opt: ShieldOptions): [ImageData, Partial] { if (!browser) { throw 'not supported'; } @@ -29,7 +30,7 @@ export function createShield(opt: ShieldOptions): [ImageData, Object] { const lp_front = l_front + 1; const lp_back = l_back - 1; - let p = new Path2D(); + const p = new Path2D(); p.moveTo(lr_front, l_front); // top line diff --git a/ui/src/lib/style.ts b/ui/src/lib/map/style.ts similarity index 74% rename from ui/src/lib/style.ts rename to ui/src/lib/map/style.ts index 78315518f..afd396a3e 100644 --- a/ui/src/lib/style.ts +++ b/ui/src/lib/map/style.ts @@ -1,16 +1,122 @@ import type { StyleSpecification } from 'maplibre-gl'; -const water = '#99ddff'; -const rail = '#a8a8a8'; -const pedestrian = '#e8e7eb'; +const colors = { + light: { + background: '#f8f4f0', -const sport = '#d0f4be'; -const sport_outline = '#b3e998'; + water: '#99ddff', + rail: '#a8a8a8', + pedestrian: '#e8e7eb', -const building = '#ded7d3'; -const building_outline = '#cfc8c4'; + sport: '#d0f4be', + sportOutline: '#b3e998', -export const getStyle = (level: number): StyleSpecification => { + building: '#ded7d3', + buildingOutline: '#cfc8c4', + + landuseComplex: '#f0e6d1', + landuseCommercial: 'hsla(0, 60%, 87%, 0.23)', + landuseIndustrial: '#e0e2eb', + landuseResidential: '#ece7e4', + landuseRetail: 'hsla(0, 60%, 87%, 0.23)', + landuseConstruction: '#aaa69d', + + landusePark: '#b8ebad', + landuseNatureLight: '#ddecd7', + landuseNatureHeavy: '#a3e39c', + landuseCemetery: '#e0e4dd', + landuseBeach: '#fffcd3', + + indoorCorridor: '#fdfcfa', + indoor: '#d4edff', + indoorOutline: '#808080', + indoorText: '#333333', + + publicTransport: 'rgba(218,140,140,0.3)', + + footway: '#fff', + steps: '#ff4524', + + elevatorOutline: '#808080', + elevator: '#bcf1ba', + + roadBackResidential: '#ffffff', + roadBackNonResidential: '#ffffff', + + motorway: '#ffb366', + motorwayLink: '#f7e06e', + primarySecondary: '#fffbf8', + linkTertiary: '#ffffff', + residential: '#ffffff', + road: '#ffffff', + + text: '#333333', + textHalo: 'white', + citiesText: '#111111', + citiesTextHalo: 'white', + + shield: 'shield' + }, + dark: { + background: '#292929', + + water: '#1F1F1F', + rail: '#232323', + pedestrian: '#292929', + + sport: '#272525', + sportOutline: '#272525', + + building: '#1F1F1F', + buildingOutline: '#1A1A1A', + + landuseComplex: '#292929', + landuseCommercial: '#292929', + landuseIndustrial: '#353538', + landuseResidential: '#292929', + landuseRetail: '#292929', + landuseConstruction: 'red', + + landusePark: '#272525', + landuseNatureLight: '#272525', + landuseNatureHeavy: '#272525', + landuseCemetery: '#272525', + landuseBeach: '#4c4b3e', + + indoorCorridor: '#494949', + indoor: '#1a1a1a', + indoorOutline: '#0d0d0d', + indoorText: '#eeeeee', + + publicTransport: 'rgba(65, 48, 48, 0.3)', + + footway: '#3D3D3D', + steps: '#70504b', + + elevatorOutline: '#808080', + elevator: '#3b423b', + + roadBackResidential: '#414141', + roadBackNonResidential: '#414141', + + motorway: '#414141', + motorwayLink: '#414141', + primarySecondary: '#414141', + linkTertiary: '#414141', + residential: '#414141', + road: '#414141', + + text: '#787878', + textHalo: '#151515', + citiesText: '#A8A8A8', + citiesTextHalo: '#1A1A1A', + + shield: 'shield-dark' + } +}; + +export const getStyle = (theme: 'light' | 'dark', level: number): StyleSpecification => { + const c = colors[theme]; return { version: 8, sources: { @@ -26,14 +132,14 @@ export const getStyle = (level: number): StyleSpecification => { { id: 'background', type: 'background', - paint: { 'background-color': '#f8f4f0' } + paint: { 'background-color': c.background } }, { id: 'coastline', type: 'fill', source: 'osm', 'source-layer': 'coastline', - paint: { 'fill-color': water } + paint: { 'fill-color': c.water } }, { id: 'landuse_park', @@ -42,8 +148,7 @@ export const getStyle = (level: number): StyleSpecification => { 'source-layer': 'landuse', filter: ['==', ['get', 'landuse'], 'park'], paint: { - 'fill-color': '#b8ebad' - // "fill-outline-color": "rgba(95, 208, 100, 1)" + 'fill-color': c.landusePark } }, { @@ -57,26 +162,26 @@ export const getStyle = (level: number): StyleSpecification => { 'match', ['get', 'landuse'], 'complex', - '#f0e6d1', + c.landuseComplex, 'commercial', - 'hsla(0, 60%, 87%, 0.23)', + c.landuseCommercial, 'industrial', - '#e0e2eb', + c.landuseIndustrial, 'residential', - '#ece7e4', + c.landuseResidential, 'retail', - 'hsla(0, 60%, 87%, 0.23)', + c.landuseRetail, 'construction', - '#aaa69d', + c.landuseConstruction, 'nature_light', - '#ddecd7', + c.landuseNatureLight, 'nature_heavy', - '#a3e39c', + c.landuseNatureHeavy, 'cemetery', - '#e0e4dd', + c.landuseCemetery, 'beach', - '#fffcd3', + c.landuseBeach, 'magenta' ] @@ -87,7 +192,7 @@ export const getStyle = (level: number): StyleSpecification => { type: 'fill', source: 'osm', 'source-layer': 'water', - paint: { 'fill-color': water } + paint: { 'fill-color': c.water } }, { id: 'sport', @@ -95,8 +200,8 @@ export const getStyle = (level: number): StyleSpecification => { source: 'osm', 'source-layer': 'sport', paint: { - 'fill-color': sport, - 'fill-outline-color': sport_outline + 'fill-color': c.sport, + 'fill-outline-color': c.sportOutline } }, { @@ -104,14 +209,14 @@ export const getStyle = (level: number): StyleSpecification => { type: 'fill', source: 'osm', 'source-layer': 'pedestrian', - paint: { 'fill-color': pedestrian } + paint: { 'fill-color': c.pedestrian } }, { id: 'waterway', type: 'line', source: 'osm', 'source-layer': 'waterway', - paint: { 'line-color': water } + paint: { 'line-color': c.water } }, { id: 'building', @@ -119,8 +224,8 @@ export const getStyle = (level: number): StyleSpecification => { source: 'osm', 'source-layer': 'building', paint: { - 'fill-color': building, - 'fill-outline-color': building_outline, + 'fill-color': c.building, + 'fill-outline-color': c.buildingOutline, 'fill-opacity': ['interpolate', ['linear'], ['zoom'], 14, 0, 16, 0.8] } }, @@ -131,7 +236,7 @@ export const getStyle = (level: number): StyleSpecification => { 'source-layer': 'indoor', filter: ['all', ['==', 'indoor', 'corridor'], ['==', 'level', level]], paint: { - 'fill-color': '#fdfcfa', + 'fill-color': c.indoorCorridor, 'fill-opacity': 0.8 } }, @@ -142,7 +247,7 @@ export const getStyle = (level: number): StyleSpecification => { 'source-layer': 'indoor', filter: ['all', ['!in', 'indoor', 'corridor', 'wall', 'elevator'], ['==', 'level', level]], paint: { - 'fill-color': '#d4edff', + 'fill-color': c.indoor, 'fill-opacity': 0.8 } }, @@ -154,7 +259,7 @@ export const getStyle = (level: number): StyleSpecification => { filter: ['all', ['!in', 'indoor', 'corridor', 'wall', 'elevator'], ['==', 'level', level]], minzoom: 18, paint: { - 'line-color': '#808080', + 'line-color': c.indoorOutline, 'line-width': 2 } }, @@ -172,7 +277,7 @@ export const getStyle = (level: number): StyleSpecification => { 'text-size': 12 }, paint: { - 'text-color': '#333333' + 'text-color': c.indoorText } }, { @@ -186,7 +291,7 @@ export const getStyle = (level: number): StyleSpecification => { ['any', ['!has', 'level'], ['==', 'level', level]] ], paint: { - 'fill-color': 'rgba(218,140,140,0.3)' + 'fill-color': c.publicTransport } }, { @@ -205,7 +310,7 @@ export const getStyle = (level: number): StyleSpecification => { minzoom: 14, paint: { 'line-dasharray': [0.75, 1.5], - 'line-color': '#fff', + 'line-color': c.footway, 'line-opacity': 0.5, 'line-width': [ 'let', @@ -248,7 +353,7 @@ export const getStyle = (level: number): StyleSpecification => { ], paint: { 'line-dasharray': [0.5, 0.5], - 'line-color': '#ff4524', + 'line-color': c.steps, 'line-opacity': 1, 'line-width': [ 'let', @@ -285,7 +390,7 @@ export const getStyle = (level: number): StyleSpecification => { ['>=', 'to_level', level] ], paint: { - 'circle-color': '#808080', + 'circle-color': c.elevatorOutline, 'circle-radius': 16 } }, @@ -302,7 +407,7 @@ export const getStyle = (level: number): StyleSpecification => { ['>=', 'to_level', level] ], paint: { - 'circle-color': '#bcf1ba', + 'circle-color': c.elevator, 'circle-radius': 14 } }, @@ -333,7 +438,7 @@ export const getStyle = (level: number): StyleSpecification => { 'line-cap': 'round' }, paint: { - 'line-color': '#ffffff', + 'line-color': c.roadBackResidential, 'line-width': ['interpolate', ['linear'], ['zoom'], 5, 0, 9, 0.5, 12, 1, 16, 4, 20, 20], 'line-opacity': ['interpolate', ['linear'], ['zoom'], 12, 0.4, 15, 1] } @@ -359,7 +464,7 @@ export const getStyle = (level: number): StyleSpecification => { 'line-cap': 'round' }, paint: { - 'line-color': '#ffffff', + 'line-color': c.roadBackNonResidential, 'line-width': [ 'let', 'base', @@ -418,16 +523,16 @@ export const getStyle = (level: number): StyleSpecification => { 'match', ['get', 'highway'], 'motorway', - '#ffb366', + c.motorway, ['trunk', 'motorway_link'], - '#f7e06e', + c.motorwayLink, ['primary', 'secondary', 'aeroway', 'trunk_link'], - '#fffbf8', + c.primarySecondary, ['primary_link', 'secondary_link', 'tertiary', 'tertiary_link'], - '#ffffff', + c.linkTertiary, 'residential', - '#ffffff', - '#ffffff' + c.residential, + c.road ], 'line-width': [ 'let', @@ -472,7 +577,7 @@ export const getStyle = (level: number): StyleSpecification => { 'source-layer': 'rail', filter: ['==', 'rail', 'old'], paint: { - 'line-color': rail + 'line-color': c.rail } }, { @@ -482,7 +587,7 @@ export const getStyle = (level: number): StyleSpecification => { 'source-layer': 'rail', filter: ['==', 'rail', 'detail'], paint: { - 'line-color': rail + 'line-color': c.rail } }, { @@ -496,7 +601,7 @@ export const getStyle = (level: number): StyleSpecification => { ['any', ['!has', 'level'], ['==', 'level', level]] ], paint: { - 'line-color': rail, + 'line-color': c.rail, 'line-width': 1.15 } }, @@ -511,7 +616,7 @@ export const getStyle = (level: number): StyleSpecification => { ['any', ['!has', 'level'], ['==', 'level', level]] ], paint: { - 'line-color': rail, + 'line-color': c.rail, 'line-width': 1.3 } }, @@ -540,14 +645,14 @@ export const getStyle = (level: number): StyleSpecification => { 'text-justify': 'center', 'text-rotation-alignment': 'viewport', 'text-pitch-alignment': 'viewport', - 'icon-image': 'shield', + 'icon-image': c.shield, 'icon-text-fit': 'both', 'icon-text-fit-padding': [0.5, 4, 0.5, 4], 'icon-rotation-alignment': 'viewport', 'icon-pitch-alignment': 'viewport' }, paint: { - 'text-color': '#333333' + 'text-color': c.text } }, { @@ -564,8 +669,8 @@ export const getStyle = (level: number): StyleSpecification => { }, paint: { 'text-halo-width': 11, - 'text-halo-color': 'white', - 'text-color': '#333333' + 'text-halo-color': c.textHalo, + 'text-color': c.text } }, { @@ -582,8 +687,8 @@ export const getStyle = (level: number): StyleSpecification => { }, paint: { 'text-halo-width': 1, - 'text-halo-color': 'white', - 'text-color': '#333333' + 'text-halo-color': c.textHalo, + 'text-color': c.text } }, { @@ -600,8 +705,8 @@ export const getStyle = (level: number): StyleSpecification => { }, paint: { 'text-halo-width': 2, - 'text-halo-color': 'white', - 'text-color': '#111111' + 'text-halo-color': c.citiesTextHalo, + 'text-color': c.citiesText } } ] diff --git a/ui/src/lib/modeStyle.ts b/ui/src/lib/modeStyle.ts index 81edf1d7f..2dd46d8c5 100644 --- a/ui/src/lib/modeStyle.ts +++ b/ui/src/lib/modeStyle.ts @@ -4,13 +4,13 @@ export const getModeStyle = (mode: Mode): [string, string] => { switch (mode) { case 'WALK': case 'FLEXIBLE': - return ['walk', '333']; + return ['walk', 'hsl(var(--foreground) / 1)']; case 'BIKE': case 'BIKE_TO_PARK': case 'BIKE_RENTAL': case 'SCOOTER_RENTAL': - return ['bike', '333']; + return ['bike', '#333']; case 'CAR': case 'CAR_TO_PARK': @@ -18,56 +18,56 @@ export const getModeStyle = (mode: Mode): [string, string] => { case 'CAR_SHARING': case 'CAR_PICKUP': case 'CAR_RENTAL': - return ['car', '333333']; + return ['car', '#333']; case 'TRANSIT': case 'BUS': - return ['bus', 'ff9800']; + return ['bus', '#ff9800']; case 'COACH': - return ['bus', '9ccc65']; + return ['bus', '#9ccc65']; case 'TRAM': - return ['tram', 'ff9800']; + return ['tram', '#ff9800']; case 'METRO': - return ['sbahn', '4caf50']; + return ['sbahn', '#4caf50']; case 'SUBWAY': - return ['ubahn', '3f51b5']; + return ['ubahn', '#3f51b5']; case 'FERRY': - return ['ferry', '00acc1']; + return ['ferry', '#00acc1']; case 'AIRPLANE': - return ['plane', '90a4ae']; + return ['plane', '#90a4ae']; case 'HIGHSPEED_RAIL': - return ['train', '9c27b0']; + return ['train', '#9c27b0']; case 'LONG_DISTANCE': - return ['train', 'e91e63']; + return ['train', '#e91e63']; case 'NIGHT_RAIL': - return ['train', '1a237e']; + return ['train', '#1a237e']; case 'REGIONAL_FAST_RAIL': case 'REGIONAL_RAIL': case 'RAIL': - return ['train', 'f44336']; + return ['train', '#f44336']; } - return ['train', '000000']; + return ['train', '#000000']; }; export const getColor = (l: Leg): string => { const defaultColor = getModeStyle(l.mode)[1]; - return !l.routeColor || l.routeColor === '000000' ? defaultColor || '000000' : l.routeColor; + return !l.routeColor || l.routeColor === '000000' ? defaultColor : '#' + l.routeColor; }; export const routeBorderColor = (l: Leg) => { - return `border-color: #${getColor(l)}`; + return `border-color: ${getColor(l)}`; }; export const routeColor = (l: Leg) => { - return `background-color: #${getColor(l)}; color: #FFF;`; + return `background-color: ${getColor(l)}; color: #FFF;`; }; diff --git a/ui/src/lib/openapi/core/ApiError.ts b/ui/src/lib/openapi/core/ApiError.ts index 36675d288..9cf094969 100644 --- a/ui/src/lib/openapi/core/ApiError.ts +++ b/ui/src/lib/openapi/core/ApiError.ts @@ -18,4 +18,4 @@ export class ApiError extends Error { this.body = response.body; this.request = request; } -} \ No newline at end of file +} diff --git a/ui/src/lib/openapi/core/ApiRequestOptions.ts b/ui/src/lib/openapi/core/ApiRequestOptions.ts index 1758d98c4..2c83c55dd 100644 --- a/ui/src/lib/openapi/core/ApiRequestOptions.ts +++ b/ui/src/lib/openapi/core/ApiRequestOptions.ts @@ -11,4 +11,4 @@ export type ApiRequestOptions = { readonly responseHeader?: string; readonly responseTransformer?: (data: unknown) => Promise; readonly errors?: Record; -}; \ No newline at end of file +}; diff --git a/ui/src/lib/openapi/core/ApiResult.ts b/ui/src/lib/openapi/core/ApiResult.ts index 4c58e3913..84b9f9da7 100644 --- a/ui/src/lib/openapi/core/ApiResult.ts +++ b/ui/src/lib/openapi/core/ApiResult.ts @@ -4,4 +4,4 @@ export type ApiResult = { readonly status: number; readonly statusText: string; readonly url: string; -}; \ No newline at end of file +}; diff --git a/ui/src/lib/openapi/core/CancelablePromise.ts b/ui/src/lib/openapi/core/CancelablePromise.ts index ccc082e8f..f0face199 100644 --- a/ui/src/lib/openapi/core/CancelablePromise.ts +++ b/ui/src/lib/openapi/core/CancelablePromise.ts @@ -65,15 +65,15 @@ export class CancelablePromise implements Promise { }; Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this._isResolved, + get: (): boolean => this._isResolved }); Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this._isRejected, + get: (): boolean => this._isRejected }); Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this._isCancelled, + get: (): boolean => this._isCancelled }); return executor(onResolve, onReject, onCancel as OnCancel); @@ -81,7 +81,7 @@ export class CancelablePromise implements Promise { } get [Symbol.toStringTag]() { - return "Cancellable Promise"; + return 'Cancellable Promise'; } public then( @@ -123,4 +123,4 @@ export class CancelablePromise implements Promise { public get isCancelled(): boolean { return this._isCancelled; } -} \ No newline at end of file +} diff --git a/ui/src/lib/openapi/core/OpenAPI.ts b/ui/src/lib/openapi/core/OpenAPI.ts index c9a1b0acb..8f77483d0 100644 --- a/ui/src/lib/openapi/core/OpenAPI.ts +++ b/ui/src/lib/openapi/core/OpenAPI.ts @@ -5,22 +5,22 @@ type Middleware = (value: T) => T | Promise; type Resolver = (options: ApiRequestOptions) => Promise; export class Interceptors { - _fns: Middleware[]; + _fns: Middleware[]; - constructor() { - this._fns = []; - } + constructor() { + this._fns = []; + } - eject(fn: Middleware): void { - const index = this._fns.indexOf(fn); - if (index !== -1) { - this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; - } - } + eject(fn: Middleware): void { + const index = this._fns.indexOf(fn); + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; + } + } - use(fn: Middleware): void { - this._fns = [...this._fns, fn]; - } + use(fn: Middleware): void { + this._fns = [...this._fns, fn]; + } } export type OpenAPIConfig = { @@ -51,6 +51,6 @@ export const OpenAPI: OpenAPIConfig = { WITH_CREDENTIALS: false, interceptors: { request: new Interceptors(), - response: new Interceptors(), - }, -}; \ No newline at end of file + response: new Interceptors() + } +}; diff --git a/ui/src/lib/openapi/core/request.ts b/ui/src/lib/openapi/core/request.ts index 5458a2899..3d09b2f48 100644 --- a/ui/src/lib/openapi/core/request.ts +++ b/ui/src/lib/openapi/core/request.ts @@ -45,7 +45,7 @@ export const getQueryString = (params: Record): string => { if (value instanceof Date) { append(key, value.toISOString()); } else if (Array.isArray(value)) { - value.forEach(v => encodePair(key, v)); + value.forEach((v) => encodePair(key, v)); } else if (typeof value === 'object') { Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); } else { @@ -90,7 +90,7 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined => .filter(([, value]) => value !== undefined && value !== null) .forEach(([key, value]) => { if (Array.isArray(value)) { - value.forEach(v => process(key, v)); + value.forEach((v) => process(key, v)); } else { process(key, value); } @@ -103,14 +103,20 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined => type Resolver = (options: ApiRequestOptions) => Promise; -export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver +): Promise => { if (typeof resolver === 'function') { return (resolver as Resolver)(options); } return resolver; }; -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions +): Promise => { const [token, username, password, additionalHeaders] = await Promise.all([ // @ts-ignore resolve(options, config.TOKEN), @@ -119,19 +125,22 @@ export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOp // @ts-ignore resolve(options, config.PASSWORD), // @ts-ignore - resolve(options, config.HEADERS), + resolve(options, config.HEADERS) ]); const headers = Object.entries({ Accept: 'application/json', ...additionalHeaders, - ...options.headers, + ...options.headers }) .filter(([, value]) => value !== undefined && value !== null) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record); + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value) + }), + {} as Record + ); if (isStringWithValue(token)) { headers['Authorization'] = `Bearer ${token}`; @@ -185,7 +194,7 @@ export const sendRequest = async ( headers, body: body ?? formData, method: options.method, - signal: controller.signal, + signal: controller.signal }; if (config.WITH_CREDENTIALS) { @@ -201,7 +210,10 @@ export const sendRequest = async ( return await fetch(url, request); }; -export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { +export const getResponseHeader = ( + response: Response, + responseHeader?: string +): string | undefined => { if (responseHeader) { const content = response.headers.get(responseHeader); if (isString(content)) { @@ -216,10 +228,17 @@ export const getResponseBody = async (response: Response): Promise => { try { const contentType = response.headers.get('Content-Type'); if (contentType) { - const binaryTypes = ['application/octet-stream', 'application/pdf', 'application/zip', 'audio/', 'image/', 'video/']; + const binaryTypes = [ + 'application/octet-stream', + 'application/pdf', + 'application/zip', + 'audio/', + 'image/', + 'video/' + ]; if (contentType.includes('application/json') || contentType.includes('+json')) { return await response.json(); - } else if (binaryTypes.some(type => contentType.includes(type))) { + } else if (binaryTypes.some((type) => contentType.includes(type))) { return await response.blob(); } else if (contentType.includes('multipart/form-data')) { return await response.formData(); @@ -276,8 +295,8 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): 508: 'Loop Detected', 510: 'Not Extended', 511: 'Network Authentication Required', - ...options.errors, - } + ...options.errors + }; const error = errors[result.status]; if (error) { @@ -295,7 +314,9 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): } })(); - throw new ApiError(options, result, + throw new ApiError( + options, + result, `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` ); } @@ -308,7 +329,10 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): * @returns CancelablePromise * @throws ApiError */ -export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions +): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(config, options); @@ -328,7 +352,7 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions) let transformedBody = responseBody; if (options.responseTransformer && response.ok) { - transformedBody = await options.responseTransformer(responseBody) + transformedBody = await options.responseTransformer(responseBody); } const result: ApiResult = { @@ -336,7 +360,7 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions) ok: response.ok, status: response.status, statusText: response.statusText, - body: responseHeader ?? transformedBody, + body: responseHeader ?? transformedBody }; catchErrorCodes(options, result); @@ -347,4 +371,4 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions) reject(error); } }); -}; \ No newline at end of file +}; diff --git a/ui/src/lib/openapi/services.gen.ts b/ui/src/lib/openapi/services.gen.ts index ca7879e03..112ed6a79 100644 --- a/ui/src/lib/openapi/services.gen.ts +++ b/ui/src/lib/openapi/services.gen.ts @@ -1,10 +1,18 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { GeocodeData, GeocodeError, GeocodeResponse, PlanData, PlanError, PlanResponse } from './types.gen'; +import type { ReverseGeocodeData, ReverseGeocodeError, ReverseGeocodeResponse, GeocodeData, GeocodeError, GeocodeResponse, PlanData, PlanError, PlanResponse } from './types.gen'; export const client = createClient(createConfig()); +/** + * Translate coordinates to the closest address(es)/places/stops. + */ +export const reverseGeocode = (options: Options) => { return (options?.client ?? client).get({ + ...options, + url: '/api/v1/reverse-geocode' +}); }; + /** * Autocompletion & geocoding that resolves user input addresses including coordinates */ diff --git a/ui/src/lib/openapi/types.gen.ts b/ui/src/lib/openapi/types.gen.ts index e5bbadd8a..b467c372f 100644 --- a/ui/src/lib/openapi/types.gen.ts +++ b/ui/src/lib/openapi/types.gen.ts @@ -375,6 +375,19 @@ export type Itinerary = { legs: Array; }; +export type ReverseGeocodeData = { + query: { + /** + * latitude, longitude in degrees + */ + place: string; + }; +}; + +export type ReverseGeocodeResponse = (Array); + +export type ReverseGeocodeError = unknown; + export type GeocodeData = { query: { /** diff --git a/ui/src/lib/toTable.ts b/ui/src/lib/toTable.ts deleted file mode 100644 index 586a80bda..000000000 --- a/ui/src/lib/toTable.ts +++ /dev/null @@ -1,38 +0,0 @@ -export const toTable = (properties: Object) => { - const table = document.createElement('table'); - table.classList.add('routing-graph', 'properties'); - for (const key in properties) { - const value = properties[key]; - const row = document.createElement('tr'); - const keyCell = document.createElement('td'); - keyCell.innerText = key; - keyCell.className = 'key'; - const valueCell = document.createElement('td'); - if (key == 'osm_way_id' && value != 0) { - const osmLink = document.createElement('a'); - osmLink.href = 'https://www.openstreetmap.org/way/' + Math.abs(value); - osmLink.target = '_blank'; - osmLink.innerText = value; - valueCell.appendChild(osmLink); - } else if (key == 'osm_relation_id' && value != 0) { - const osmLink = document.createElement('a'); - osmLink.href = 'https://www.openstreetmap.org/relation/' + Math.abs(value); - osmLink.target = '_blank'; - osmLink.innerText = value; - valueCell.appendChild(osmLink); - } else if (key == 'osm_node_id' && value != 0) { - const osmLink = document.createElement('a'); - osmLink.href = 'https://www.openstreetmap.org/node/' + Math.abs(value); - osmLink.target = '_blank'; - osmLink.innerText = value; - valueCell.appendChild(osmLink); - } else { - valueCell.innerText = value; - } - valueCell.className = 'value'; - row.appendChild(keyCell); - row.appendChild(valueCell); - table.appendChild(row); - } - return table; -}; diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index 2e511e004..f87b35e58 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -1,4 +1,4 @@ - diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte index a3b3c9287..eb3ec87fd 100644 --- a/ui/src/routes/+page.svelte +++ b/ui/src/routes/+page.svelte @@ -1,962 +1,139 @@ { + bind:zoom + transformRequest={(url: string) => { if (url.startsWith('/')) { return { url: `https://europe.motis-project.de/tiles${url}` }; } }} - center={[8.563351200419433, 50]} - zoom={10} - class="h-screen" - style={getStyle(level)} + center={[8.652235, 49.876908]} + class={cn('h-screen', theme)} + style={getStyle(theme, level)} > - {#if elevator} - - -
-

- Fahrstuhl {elevator.desc} - - {elevator.id} - -

- -
- - - - Nicht verfügbar von - bis - - - - - {#each elevator.outOfService as _, i} - - - - - - - - - - - - {/each} - -
-
- - -
+ + + + + + + + + + + {#if !selectedItinerary && baseQuery && routingResponses.length !== 0} + + + {/if} - - - {#if itinerary !== null} -
-

Journey Details

+ {#if selectedItinerary} + + +
+

Journey Details

-
- -
- {/if} - -
-
- | undefined) => { - if (match) { - start.lng = match.value.lon; - start.lat = match.value.lat; - startMarker.setLngLat([start.lng, start.lat]); - } - }} - /> - | undefined) => { - if (match) { - destination.lng = match.value.lon; - destination.lat = match.value.lat; - destinationMarker.setLngLat([destination.lng, destination.lat]); - } - }} - /> -
-
- - - - - -
-
- -
-
-
-
- {#each routingResponses as routingResponse, rI} - {#await routingResponse} -
- -
- {:then r} - {#if rI === 0} -
- - - -
- {/if} - {#each r.itineraries as it, i} - {@const date = new Date(it.startTime).toLocaleDateString()} - {@const predDate = new Date( - r.itineraries[i == 0 ? 0 : i - 1].startTime - ).toLocaleDateString()} - { - itinerary = it; - }} - > - -
-
-
Departure Time
-
- -
-
Arrival Time
-
- -
-
Transfers
-
{it.transfers}
-
- -
-
Duration
-
- {formatDurationSec(it.duration)} -
-
-
- -
- {#each it.legs.filter((l) => l.routeShortName) as l} -
- - - - {l.routeShortName} -
- {/each} -
-
-
- {/each} - {#if rI === routingResponses.length - 1} -
- - - -
- {/if} - {:catch e} -
Error: {e}
- {/await} - {/each} -
-
-
-
- - {#if footpaths} - -
-
-

- {footpaths.id.name} - - {footpaths.id.id} - -

- -
-
- - - - Station - Default - Foot - Wheelchair - - - - {#each footpaths.footpaths as f} - - {f.id.name} - - {#if f.default !== undefined} - - {/if} - - - {#if f.foot !== undefined} - - {/if} - - - {#if f.wheelchair !== undefined} - - {/if} - - - {/each} - -
+
+
-
+ - {/if} - - -
- - G - -
-
- -
- - M - -
-
- -
- - P - -
-
- -
- - E - -
-
- -
- -
-
- - {#if levels} - {#await levels then lvls} - {#each lvls as l} - -
- { - level = l; - }} - > - {l} - -
-
- {/each} - {/await} - {/if} - - {#if platforms !== null} - {#await platforms then p} - - - - - {/await} - {/if} - - {#await route then r} - {#if r.type == 'FeatureCollection'} - - - - - {/if} - {/await} - - -
- {#await footRoute} - Loading... - {:then r} - {#if r.metadata} - {formatDurationSec(r.metadata.duration)} -
- {Math.round(r.metadata.distance)} m - {#if r.metadata.uses_elevator} -
mit Fahrstuhl
- {/if} - {:else} - No foot route found. - {/if} - {/await} -
-
- - {#await footRoute then r} - {#if itinerary == null && r.type == 'FeatureCollection'} - - - - - {/if} - {/await} - - {#if graph != null} - - - - - - {/if} - - {#if matches != null} - - - - - {/if} - - {#if elevators != null} - - - - + {/if} diff --git a/ui/src/routes/ConnectionDetail.svelte b/ui/src/routes/ConnectionDetail.svelte index 2193d2acd..e67a980f9 100644 --- a/ui/src/routes/ConnectionDetail.svelte +++ b/ui/src/routes/ConnectionDetail.svelte @@ -1,17 +1,12 @@ @@ -90,8 +85,8 @@ {#each l.intermediateStops! as s}
-
{/each} @@ -109,7 +104,7 @@ {:else if !(isLast && l.duration === 0) && ((i == 0 && l.duration !== 0) || !next || !next.routeShortName)}
- +
diff --git a/ui/src/routes/ItineraryGeoJSON.svelte b/ui/src/routes/ItineraryGeoJSON.svelte new file mode 100644 index 000000000..0f8c8376b --- /dev/null +++ b/ui/src/routes/ItineraryGeoJSON.svelte @@ -0,0 +1,90 @@ + + + + + + diff --git a/ui/src/routes/ItineraryList.svelte b/ui/src/routes/ItineraryList.svelte new file mode 100644 index 000000000..c039ea4e8 --- /dev/null +++ b/ui/src/routes/ItineraryList.svelte @@ -0,0 +1,117 @@ + + +
+ {#each routingResponses as r, rI} + {#await r} +
+ +
+ {:then r} + {#if rI === 0} +
+ + + +
+ {/if} + {#each r.itineraries as it} + + {/each} + {#if rI === routingResponses.length - 1} +
+ + + +
+ {/if} + {:catch e} +
Error: {e}
+ {/await} + {/each} +
diff --git a/ui/src/routes/SearchMask.svelte b/ui/src/routes/SearchMask.svelte new file mode 100644 index 000000000..1ff49bd7a --- /dev/null +++ b/ui/src/routes/SearchMask.svelte @@ -0,0 +1,97 @@ + + +
+ + + +
+ + + + + +
+ + + + + + Wheelchair + Foot + Bike + Car + + +
+
+