diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c61e351e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +dist/build.tar +dist/output diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd73ce31..76f226cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,11 +13,11 @@ jobs: build-and-package: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: latest + + - name: Setup pnpm + uses: pnpm/action-setup@v4 - name: Install Dependencies run: pnpm install diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 870ca6fe..0e3f324a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -11,8 +11,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: latest - name: Install Dependencies run: pnpm install diff --git a/Containerfile b/Containerfile index 261f7346..5857dd2d 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,9 @@ -FROM registry.access.redhat.com/ubi9/nginx-122:1-45 +FROM nginx:1.27.2-alpine + +RUN rm -r /usr/share/nginx/html \ + && mkdir /usr/share/nginx/html + +WORKDIR /usr/share/nginx/html ADD dist . diff --git a/README.md b/README.md index ba64a5b5..ec46a087 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted ## Self-host The client can be self hosted using the precompiled container images with an OCI compatible runtime such as [Docker](https://www.docker.com/) or [Podman](https://podman.io/). -The base image used is [UBI9 Nginx 1.22](https://catalog.redhat.com/software/containers/ubi9/nginx-122/63f7653b9b0ca19f84f7e9a1) +The base image used is [Nginx 1.27](https://hub.docker.com/_/nginx) ```bash # With Docker -docker run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web +docker run -d -p 8080:80 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web #With Podman -podman run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web +podman run -d -p 8080:80 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web ``` ## Development & Building diff --git a/package.json b/package.json index e4c7d449..2946c813 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "description": "Meshtastic web client", "license": "GPL-3.0-only", "scripts": { - "dev": "vite --host", - "build": "tsc && pnpm check && vite build ", + "build": "rsbuild build", "check": "biome check .", "check:fix": "pnpm check --write", - "preview": "vite preview", + "dev": "rsbuild dev --open", + "format": "biome format --write", + "preview": "rsbuild preview", "package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)" }, "repository": { @@ -48,6 +49,7 @@ "cmdk": "^1.0.0", "crypto-random-string": "^5.0.0", "immer": "^10.1.1", + "js-cookie": "^3.0.5", "lucide-react": "^0.363.0", "mapbox-gl": "^3.6.0", "maplibre-gl": "4.1.2", @@ -66,13 +68,15 @@ "devDependencies": { "@biomejs/biome": "^1.8.2", "@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240906232734-3da561588c55.1", + "@rsbuild/core": "^1.0.10", + "@rsbuild/plugin-react": "^1.0.3", "@types/chrome": "^0.0.263", + "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/w3c-web-serial": "^1.0.6", "@types/web-bluetooth": "^0.0.20", - "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.19", "gzipper": "^7.2.0", "postcss": "^8.4.38", @@ -80,8 +84,7 @@ "tailwindcss": "^3.4.4", "tar": "^6.2.1", "tslib": "^2.6.3", - "typescript": "^5.5.2", - "vite": "^5.3.1", - "vite-plugin-environment": "^1.1.3" - } + "typescript": "^5.5.2" + }, + "packageManager": "pnpm@9.15.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e91a4c5..79d6d5ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: immer: specifier: ^10.1.1 version: 10.1.1 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.363.0 version: 0.363.0(react@18.3.1) @@ -127,7 +130,7 @@ importers: version: 3.0.6(react@18.3.1) vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)) + version: 0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -138,9 +141,18 @@ importers: '@buf/meshtastic_protobufs.bufbuild_es': specifier: 1.10.0-20240906232734-3da561588c55.1 version: 1.10.0-20240906232734-3da561588c55.1(@bufbuild/protobuf@1.10.0) + '@rsbuild/core': + specifier: ^1.0.10 + version: 1.0.10 + '@rsbuild/plugin-react': + specifier: ^1.0.3 + version: 1.0.3(@rsbuild/core@1.0.10) '@types/chrome': specifier: ^0.0.263 version: 0.0.263 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/node': specifier: ^20.14.9 version: 20.14.9 @@ -156,9 +168,6 @@ importers: '@types/web-bluetooth': specifier: ^0.0.20 version: 0.0.20 - '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.1(vite@5.3.1(@types/node@20.14.9)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -170,7 +179,7 @@ importers: version: 8.4.38 rollup-plugin-visualizer: specifier: ^5.12.0 - version: 5.12.0(rollup@4.18.0) + version: 5.12.0(rollup@4.29.1) tailwindcss: specifier: ^3.4.4 version: 3.4.4 @@ -183,12 +192,6 @@ importers: typescript: specifier: ^5.5.2 version: 5.5.2 - vite: - specifier: ^5.3.1 - version: 5.3.1(@types/node@20.14.9) - vite-plugin-environment: - specifier: ^1.1.3 - version: 1.1.3(vite@5.3.1(@types/node@20.14.9)) packages: @@ -196,117 +199,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.24.7': - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.24.7': - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.24.7': - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-environment-visitor@7.24.7': - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-function-name@7.24.7': - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-hoist-variables@7.24.7': - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.24.7': - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.24.7': - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-split-export-declaration@7.24.7': - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.24.7': - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.24.7': - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/runtime@7.24.7': resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.24.7': - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} - engines: {node: '>=6.9.0'} - '@biomejs/biome@1.8.2': resolution: {integrity: sha512-XafCzLgs0xbH0bCjYKxQ63ig2V86fZQMq1jiy5pyLToWk9aHxA8GAUxyBtklPHtPYZPGEPOYglQHj4jyfUp+Iw==} engines: {node: '>=14.21.3'} @@ -596,6 +492,18 @@ packages: '@meshtastic/js@2.3.7-5': resolution: {integrity: sha512-77wYoCl83PgRLkvWE8ko0YFm5LbolrfFPqoBkwLd2AFgZOHGsHTlUwA7cj82yhZM3f4mf7yTFxl+8CBawEAXRA==} + '@module-federation/runtime-tools@0.5.1': + resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} + + '@module-federation/runtime@0.5.1': + resolution: {integrity: sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==} + + '@module-federation/sdk@0.5.1': + resolution: {integrity: sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==} + + '@module-federation/webpack-bundler-runtime@0.5.1': + resolution: {integrity: sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==} + '@noble/curves@1.5.0': resolution: {integrity: sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==} @@ -1263,86 +1171,180 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@rollup/rollup-android-arm-eabi@4.29.1': + resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.29.1': + resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.29.1': + resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.29.1': + resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-freebsd-arm64@4.29.1': + resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.29.1': + resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.29.1': + resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.29.1': + resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.29.1': + resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.29.1': + resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.29.1': + resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.29.1': + resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.29.1': + resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.29.1': + resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.29.1': + resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==} cpu: [x64] os: [win32] + '@rsbuild/core@1.0.10': + resolution: {integrity: sha512-617N8YzDeH5vymeeOyCqK0toO9yt7s2yey49OIvW9jZqBo0IvgbkFNQf34LDLsxVzy+cpf1nGrcYWjPKhVuGfQ==} + engines: {node: '>=16.7.0'} + hasBin: true + + '@rsbuild/plugin-react@1.0.3': + resolution: {integrity: sha512-HVfPiKINmDsIcLLs7YWAYQgzytVZOydBuPOFg5EoJiMHkFVjH0Rg3QViS3Hn6k3INqdc6ylpcYyOHHYItEIkWA==} + peerDependencies: + '@rsbuild/core': 1.x || ^1.0.1-rc.0 + + '@rspack/binding-darwin-arm64@1.0.8': + resolution: {integrity: sha512-1l8/eg3HNz53DHQO3fy5O5QKdYh8hSMZaWGtm3NR5IfdrTm2TaLL9tuR8oL2iHHtd87LEvVKHXdjlcuLV5IPNQ==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.0.8': + resolution: {integrity: sha512-7BbG8gXVWjtqJegDpsObzM/B90Eig1piEtcahvPdvlC92uZz3/IwtKPpMaywGBrf5RSI3U0nQMSekwz0cO1SOw==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.0.8': + resolution: {integrity: sha512-QnqCL0wmwYqT/IFx5q0aw7DsIOr8oYUa4+7JI8iiqRf3RuuRJExesVW9VuWr0jS2UvChKgmb8PvRtDy/0tshFw==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.0.8': + resolution: {integrity: sha512-Ns9TsE7zdUjimW5HURRW08BaMyAh16MDh97PPsGEMeRPx9plnRO9aXvuUG6t+0gy4KwlQdeq3BvUsbBpIo5Tow==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.0.8': + resolution: {integrity: sha512-lfqUuKCoyRN/gGeokhX/oNYqB6OpbtgQb57b0QuD8IaiH2a1ee0TtEVvRbyQNEDwht6lW4RTNg0RfMYu52LgXg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.0.8': + resolution: {integrity: sha512-MgbHJWV5utVa1/U9skrXClydZ/eZw001++v4B6nb8myU6Ck1D02aMl9ESefb/sSA8TatLLxEXQ2VENG9stnPwQ==} + cpu: [x64] + os: [linux] + + '@rspack/binding-win32-arm64-msvc@1.0.8': + resolution: {integrity: sha512-3NN5VisnSOzhgqX77O/7NvcjPUueg1oIdMKoc5vElJCEu5FEXPqDhwZmr1PpBovaXshAcgExF3j54+20pwdg5g==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.0.8': + resolution: {integrity: sha512-17VQNC7PSygzsipSVoukDM/SOcVueVNsk9bZiB0Swl20BaqrlBts2Dvlmo+L+ZGsxOYI97WvA/zomMDv860usg==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.0.8': + resolution: {integrity: sha512-Vtjt74Soh09XUsV5Nw0YjZVSk/qtsjtPnzbSZluncSAVUs8l+X1ALcM6n1Jrt3TLTfcqf7a+VIsWOXAMqkCGUg==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.0.8': + resolution: {integrity: sha512-abRirbrjobcllLAamyeiWxT6Rb0wELUnITynQdqRbSweWm2lvnhm9YBv4BcOjvJBzhJtvRJo5JBtbKXjDTarug==} + + '@rspack/core@1.0.8': + resolution: {integrity: sha512-pbXwXYb4WQwb0l35P5v3l/NpDJXy1WiVE4IcQ/6LxZYU5NyZuqtsK0trR88xIVRZb9qU0JUeCdQq7Xa6Q+c3Xw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.0.1': + resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} + engines: {node: '>=16.0.0'} + + '@rspack/plugin-react-refresh@1.0.0': + resolution: {integrity: sha512-WvXkLewW5G0Mlo5H1b251yDh5FFiH4NDAbYlFpvFjcuXX2AchZRf9zdw57BDE/ADyWsJgA8kixN/zZWBTN3iYA==} + peerDependencies: + react-refresh: '>=0.10.0 <1.0.0' + peerDependenciesMeta: + react-refresh: + optional: true + '@stablelib/binary@1.0.1': resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} @@ -1358,6 +1360,9 @@ packages: '@stablelib/wipe@1.0.1': resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==} + '@swc/helpers@0.5.13': + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@turf/along@6.5.0': resolution: {integrity: sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==} @@ -1679,24 +1684,15 @@ packages: '@turf/voronoi@6.5.0': resolution: {integrity: sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/chrome@0.0.263': resolution: {integrity: sha512-As0vzv99ov3M6ZR7R6VzhMWFZXkPMrFrCEXXVrMN576Cm70fTkj7Df2CF+qEo170JepX50pd11cX6O4DSAtl2Q==} '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -1715,6 +1711,9 @@ packages: '@types/har-format@1.2.15': resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/mapbox-gl@3.1.0': resolution: {integrity: sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==} @@ -1751,12 +1750,6 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1765,10 +1758,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1899,12 +1888,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001638: - resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} cheap-ruler@4.0.0: resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==} @@ -1947,16 +1932,10 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1980,8 +1959,8 @@ packages: constants-browserify@1.0.0: resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2007,8 +1986,8 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-browserify@3.12.0: @@ -2038,15 +2017,6 @@ packages: d3-voronoi@1.1.2: resolution: {integrity: sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - deep-equal@1.1.2: resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} engines: {node: '>= 0.4'} @@ -2107,8 +2077,8 @@ packages: electron-to-chromium@1.4.812: resolution: {integrity: sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==} - elliptic@6.5.7: - resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} + elliptic@6.6.0: + resolution: {integrity: sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2119,6 +2089,9 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -2139,10 +2112,6 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -2204,10 +2173,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - geojson-equality@0.1.6: resolution: {integrity: sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==} @@ -2260,10 +2225,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -2278,10 +2239,6 @@ packages: has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -2315,6 +2272,9 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-entities@2.5.2: + resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} @@ -2481,28 +2441,22 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - json-stringify-pretty-compact@3.0.0: resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==} json-stringify-pretty-compact@4.0.0: resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - kdbush@4.0.2: resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} @@ -2539,9 +2493,6 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.363.0: resolution: {integrity: sha512-AlsfPCsXQyQx7wwsIgzcKOL9LwC498LIMAo+c0Es5PkHJa33xwmYAkkSoKoJWWWSYQEStqu58/jT4tL2gi32uQ==} peerDependencies: @@ -2564,8 +2515,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} miller-rabin@4.0.1: @@ -2606,17 +2557,14 @@ packages: engines: {node: '>=10'} hasBin: true - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - murmurhash-js@1.0.0: resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2723,6 +2671,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2790,6 +2741,10 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} @@ -2996,8 +2951,8 @@ packages: rollup: optional: true - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.29.1: + resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3016,10 +2971,6 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - serialize-to-js@3.1.2: resolution: {integrity: sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==} engines: {node: '>=4.0.0'} @@ -3081,6 +3032,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} @@ -3092,6 +3047,9 @@ packages: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + ste-core@3.0.11: resolution: {integrity: sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg==} engines: {node: '>=4.2.4'} @@ -3143,10 +3101,6 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3203,10 +3157,6 @@ packages: tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3313,18 +3263,13 @@ packages: resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} - vite-plugin-environment@1.1.3: - resolution: {integrity: sha512-9LBhB0lx+2lXVBEWxFZC+WO7PKEyE/ykJ7EPWCq95NEcCpblxamTbs5Dm3DLBGzwODpJMEnzQywJU8fw6XGGGA==} - peerDependencies: - vite: '>= 2.7' - vite-plugin-node-polyfills@0.22.0: resolution: {integrity: sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==} peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 - vite@5.3.1: - resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} + vite@5.3.6: + resolution: {integrity: sha512-es78AlrylO8mTVBygC0gTC0FENv0C6T496vvd33ydbjF/mIi9q3XQ9A3NWo5qLGFKywvz10J26813OkLvcQleA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -3396,9 +3341,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -3438,160 +3380,10 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 - - '@babel/compat-data@7.24.7': {} - - '@babel/core@7.24.7': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - convert-source-map: 2.0.0 - debug: 4.3.5 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.24.7': - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - '@babel/helper-compilation-targets@7.24.7': - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-environment-visitor@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.24.7': {} - - '@babel/helper-simple-access@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-split-export-declaration@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-option@7.24.7': {} - - '@babel/helpers@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/traverse@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.5 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.24.7': - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@biomejs/biome@1.8.2': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.8.2 @@ -3810,6 +3602,22 @@ snapshots: transitivePeerDependencies: - buffer + '@module-federation/runtime-tools@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/webpack-bundler-runtime': 0.5.1 + + '@module-federation/runtime@0.5.1': + dependencies: + '@module-federation/sdk': 0.5.1 + + '@module-federation/sdk@0.5.1': {} + + '@module-federation/webpack-bundler-runtime@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/sdk': 0.5.1 + '@noble/curves@1.5.0': dependencies: '@noble/hashes': 1.4.0 @@ -4478,70 +4286,151 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@rollup/plugin-inject@5.0.5(rollup@4.18.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.29.1)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.29.1) estree-walker: 2.0.2 magic-string: 0.30.11 optionalDependencies: - rollup: 4.18.0 + rollup: 4.29.1 - '@rollup/pluginutils@5.1.0(rollup@4.18.0)': + '@rollup/pluginutils@5.1.0(rollup@4.29.1)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.18.0 + rollup: 4.29.1 + + '@rollup/rollup-android-arm-eabi@4.29.1': + optional: true + + '@rollup/rollup-android-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-x64@4.29.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.29.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.29.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.29.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.29.1': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.29.1': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.29.1': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-linux-x64-musl@4.29.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-win32-arm64-msvc@4.29.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-win32-ia32-msvc@4.29.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-win32-x64-msvc@4.29.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rsbuild/core@1.0.10': + dependencies: + '@rspack/core': 1.0.8(@swc/helpers@0.5.13) + '@rspack/lite-tapable': 1.0.1 + '@swc/helpers': 0.5.13 + core-js: 3.38.1 + optionalDependencies: + fsevents: 2.3.3 + + '@rsbuild/plugin-react@1.0.3(@rsbuild/core@1.0.10)': + dependencies: + '@rsbuild/core': 1.0.10 + '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.14.2) + react-refresh: 0.14.2 + + '@rspack/binding-darwin-arm64@1.0.8': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rspack/binding-darwin-x64@1.0.8': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rspack/binding-linux-arm64-gnu@1.0.8': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rspack/binding-linux-arm64-musl@1.0.8': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rspack/binding-linux-x64-gnu@1.0.8': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rspack/binding-linux-x64-musl@1.0.8': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rspack/binding-win32-arm64-msvc@1.0.8': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rspack/binding-win32-ia32-msvc@1.0.8': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rspack/binding-win32-x64-msvc@1.0.8': optional: true + '@rspack/binding@1.0.8': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.0.8 + '@rspack/binding-darwin-x64': 1.0.8 + '@rspack/binding-linux-arm64-gnu': 1.0.8 + '@rspack/binding-linux-arm64-musl': 1.0.8 + '@rspack/binding-linux-x64-gnu': 1.0.8 + '@rspack/binding-linux-x64-musl': 1.0.8 + '@rspack/binding-win32-arm64-msvc': 1.0.8 + '@rspack/binding-win32-ia32-msvc': 1.0.8 + '@rspack/binding-win32-x64-msvc': 1.0.8 + + '@rspack/core@1.0.8(@swc/helpers@0.5.13)': + dependencies: + '@module-federation/runtime-tools': 0.5.1 + '@rspack/binding': 1.0.8 + '@rspack/lite-tapable': 1.0.1 + caniuse-lite: 1.0.30001690 + optionalDependencies: + '@swc/helpers': 0.5.13 + + '@rspack/lite-tapable@1.0.1': {} + + '@rspack/plugin-react-refresh@1.0.0(react-refresh@0.14.2)': + dependencies: + error-stack-parser: 2.1.4 + html-entities: 2.5.2 + optionalDependencies: + react-refresh: 0.14.2 + '@stablelib/binary@1.0.1': dependencies: '@stablelib/int': 1.0.1 @@ -4558,6 +4447,10 @@ snapshots: '@stablelib/wipe@1.0.1': {} + '@swc/helpers@0.5.13': + dependencies: + tslib: 2.6.3 + '@turf/along@6.5.0': dependencies: '@turf/bearing': 6.5.0 @@ -5374,27 +5267,6 @@ snapshots: '@turf/invariant': 6.5.0 d3-voronoi: 1.1.2 - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.24.7 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.24.7 - '@types/chrome@0.0.263': dependencies: '@types/filesystem': 0.0.36 @@ -5402,6 +5274,8 @@ snapshots: '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/filesystem@0.0.36': dependencies: '@types/filewriter': 0.0.33 @@ -5418,6 +5292,8 @@ snapshots: '@types/har-format@1.2.15': {} + '@types/js-cookie@3.0.6': {} + '@types/mapbox-gl@3.1.0': dependencies: '@types/geojson': 7946.0.14 @@ -5457,25 +5333,10 @@ snapshots: '@types/web-bluetooth@0.0.20': {} - '@vitejs/plugin-react@4.3.1(vite@5.3.1(@types/node@20.14.9))': - dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.3.1(@types/node@20.14.9) - transitivePeerDependencies: - - supports-color - ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5521,7 +5382,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.1 - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -5589,7 +5450,7 @@ snapshots: browserify-rsa: 4.1.0 create-hash: 1.2.0 create-hmac: 1.1.7 - elliptic: 6.5.7 + elliptic: 6.6.0 hash-base: 3.0.4 inherits: 2.0.4 parse-asn1: 5.1.7 @@ -5602,7 +5463,7 @@ snapshots: browserslist@4.23.1: dependencies: - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 electron-to-chromium: 1.4.812 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) @@ -5637,13 +5498,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001638: {} - - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + caniuse-lite@1.0.30001690: {} cheap-ruler@4.0.0: {} @@ -5698,16 +5553,10 @@ snapshots: - '@types/react' - '@types/react-dom' - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} commander@2.20.3: {} @@ -5727,7 +5576,7 @@ snapshots: constants-browserify@1.0.0: {} - convert-source-map@2.0.0: {} + core-js@3.38.1: {} core-util-is@1.0.3: {} @@ -5736,7 +5585,7 @@ snapshots: create-ecdh@4.0.4: dependencies: bn.js: 4.12.0 - elliptic: 6.5.7 + elliptic: 6.6.0 create-hash@1.2.0: dependencies: @@ -5757,7 +5606,7 @@ snapshots: create-require@1.1.1: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -5795,10 +5644,6 @@ snapshots: d3-voronoi@1.1.2: {} - debug@4.3.5: - dependencies: - ms: 2.1.2 - deep-equal@1.1.2: dependencies: is-arguments: 1.1.1 @@ -5881,7 +5726,7 @@ snapshots: electron-to-chromium@1.4.812: {} - elliptic@6.5.7: + elliptic@6.6.0: dependencies: bn.js: 4.12.0 brorand: 1.1.0 @@ -5899,6 +5744,10 @@ snapshots: dependencies: once: 1.4.0 + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -5945,8 +5794,6 @@ snapshots: escalade@3.1.2: {} - escape-string-regexp@1.0.5: {} - estree-walker@2.0.2: {} events@3.3.0: {} @@ -5971,7 +5818,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fastq@1.17.1: dependencies: @@ -5994,7 +5841,7 @@ snapshots: foreground-child@3.2.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 fraction.js@4.3.7: {} @@ -6010,8 +5857,6 @@ snapshots: functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} - geojson-equality@0.1.6: dependencies: deep-equal: 1.1.2 @@ -6069,8 +5914,6 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globals@11.12.0: {} - gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -6087,8 +5930,6 @@ snapshots: has-bigints@1.0.2: {} - has-flag@3.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 @@ -6127,6 +5968,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-entities@2.5.2: {} + https-browserify@1.0.0: {} ieee754@1.2.1: {} @@ -6271,18 +6114,16 @@ snapshots: jiti@1.21.6: {} + js-cookie@3.0.5: {} + js-sha3@0.8.0: {} js-tokens@4.0.0: {} - jsesc@2.5.2: {} - json-stringify-pretty-compact@3.0.0: {} json-stringify-pretty-compact@4.0.0: {} - json5@2.2.3: {} - kdbush@4.0.2: {} kind-of@6.0.3: {} @@ -6307,10 +6148,6 @@ snapshots: lru-cache@10.2.2: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - lucide-react@0.363.0(react@18.3.1): dependencies: react: 18.3.1 @@ -6386,7 +6223,7 @@ snapshots: merge2@1.4.1: {} - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -6421,8 +6258,6 @@ snapshots: mkdirp@1.0.4: {} - ms@2.1.2: {} - murmurhash-js@1.0.0: {} mz@2.7.0: @@ -6431,7 +6266,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} node-releases@2.0.14: {} @@ -6558,6 +6393,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} pify@2.3.0: {} @@ -6610,10 +6447,16 @@ snapshots: postcss@8.4.38: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + potpack@2.0.0: {} process-nextick-args@2.0.1: {} @@ -6818,35 +6661,38 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@5.12.0(rollup@4.18.0): + rollup-plugin-visualizer@5.12.0(rollup@4.29.1): dependencies: open: 8.4.2 picomatch: 2.3.1 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.18.0 + rollup: 4.29.1 - rollup@4.18.0: + rollup@4.29.1: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.29.1 + '@rollup/rollup-android-arm64': 4.29.1 + '@rollup/rollup-darwin-arm64': 4.29.1 + '@rollup/rollup-darwin-x64': 4.29.1 + '@rollup/rollup-freebsd-arm64': 4.29.1 + '@rollup/rollup-freebsd-x64': 4.29.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.29.1 + '@rollup/rollup-linux-arm-musleabihf': 4.29.1 + '@rollup/rollup-linux-arm64-gnu': 4.29.1 + '@rollup/rollup-linux-arm64-musl': 4.29.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.29.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.29.1 + '@rollup/rollup-linux-riscv64-gnu': 4.29.1 + '@rollup/rollup-linux-s390x-gnu': 4.29.1 + '@rollup/rollup-linux-x64-gnu': 4.29.1 + '@rollup/rollup-linux-x64-musl': 4.29.1 + '@rollup/rollup-win32-arm64-msvc': 4.29.1 + '@rollup/rollup-win32-ia32-msvc': 4.29.1 + '@rollup/rollup-win32-x64-msvc': 4.29.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -6863,8 +6709,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - semver@6.3.1: {} - serialize-to-js@3.1.2: {} set-function-length@1.2.2: @@ -6936,6 +6780,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map@0.7.4: {} splaytree@3.1.2: {} @@ -6944,6 +6790,8 @@ snapshots: dependencies: extend-shallow: 3.0.2 + stackframe@1.3.4: {} + ste-core@3.0.11: {} ste-simple-events@3.0.11: @@ -7010,10 +6858,6 @@ snapshots: dependencies: kdbush: 4.0.2 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-preserve-symlinks-flag@1.0.0: {} tailwind-merge@2.3.0: @@ -7036,7 +6880,7 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 @@ -7097,8 +6941,6 @@ snapshots: tinyqueue@3.0.0: {} - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7188,23 +7030,19 @@ snapshots: validator@13.12.0: {} - vite-plugin-environment@1.1.3(vite@5.3.1(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)): dependencies: - vite: 5.3.1(@types/node@20.14.9) - - vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)): - dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.18.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.29.1) node-stdlib-browser: 1.2.0 - vite: 5.3.1(@types/node@20.14.9) + vite: 5.3.6(@types/node@20.14.9) transitivePeerDependencies: - rollup - vite@5.3.1(@types/node@20.14.9): + vite@5.3.6(@types/node@20.14.9): dependencies: esbuild: 0.21.5 - postcss: 8.4.38 - rollup: 4.18.0 + postcss: 8.4.49 + rollup: 4.29.1 optionalDependencies: '@types/node': 20.14.9 fsevents: 2.3.3 @@ -7266,8 +7104,6 @@ snapshots: y18n@5.0.8: {} - yallist@3.1.1: {} - yallist@4.0.0: {} yaml@2.4.5: {} diff --git a/rsbuild.config.ts b/rsbuild.config.ts new file mode 100644 index 00000000..3191db5c --- /dev/null +++ b/rsbuild.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { execSync } from "node:child_process"; + +let hash = ""; + +try { + hash = execSync("git rev-parse --short HEAD").toString().trim(); +} catch (error) { + hash = "DEV"; +} + +export default defineConfig({ + plugins: [pluginReact()], + source: { + define: { + "process.env.COMMIT_HASH": JSON.stringify(hash), + }, + alias: { + "@app": "./src", + "@pages": "./src/pages", + "@components": "./src/components", + "@core": "./src/core", + "@layouts": "./src/layouts", + }, + }, + html: { + title: "Meshtastic Web", + }, +}); diff --git a/src/App.tsx b/src/App.tsx index 48a806f4..8c15dc97 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,17 @@ -import { DeviceWrapper } from "@app/DeviceWrapper.js"; -import { PageRouter } from "@app/PageRouter.js"; -import { CommandPalette } from "@components/CommandPalette.js"; -import { DeviceSelector } from "@components/DeviceSelector.js"; -import { DialogManager } from "@components/Dialog/DialogManager.js"; -import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js"; -import { Toaster } from "@components/Toaster.js"; -import Footer from "@components/UI/Footer.js"; -import { ThemeController } from "@components/generic/ThemeController.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { Dashboard } from "@pages/Dashboard/index.js"; +import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; +import { PageRouter } from "@app/PageRouter.tsx"; +import { CommandPalette } from "@components/CommandPalette.tsx"; +import { DeviceSelector } from "@components/DeviceSelector.tsx"; +import { DialogManager } from "@components/Dialog/DialogManager"; +import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; +import { Toaster } from "@components/Toaster.tsx"; +import Footer from "@components/UI/Footer.tsx"; +import { ThemeController } from "@components/generic/ThemeController.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { Dashboard } from "@pages/Dashboard/index.tsx"; import { MapProvider } from "react-map-gl"; +import { KeyBackupReminder } from "@components/KeyBackupReminder"; export const App = (): JSX.Element => { const { getDevice } = useDeviceStore(); @@ -37,6 +38,7 @@ export const App = (): JSX.Element => { {device ? (
+
diff --git a/src/DeviceWrapper.tsx b/src/DeviceWrapper.tsx index 9384c303..d2420352 100644 --- a/src/DeviceWrapper.tsx +++ b/src/DeviceWrapper.tsx @@ -1,5 +1,5 @@ -import { DeviceContext } from "@core/stores/deviceStore.js"; -import type { Device } from "@core/stores/deviceStore.js"; +import { DeviceContext } from "@core/stores/deviceStore.ts"; +import type { Device } from "@core/stores/deviceStore.ts"; import type { ReactNode } from "react"; export interface DeviceWrapperProps { diff --git a/src/PageRouter.tsx b/src/PageRouter.tsx index 1fe44dfa..2bec9fdc 100644 --- a/src/PageRouter.tsx +++ b/src/PageRouter.tsx @@ -1,9 +1,9 @@ -import { useDevice } from "@core/stores/deviceStore.js"; -import { ChannelsPage } from "@pages/Channels.js"; -import { ConfigPage } from "@pages/Config/index.js"; -import { MapPage } from "@pages/Map.js"; -import { MessagesPage } from "@pages/Messages.js"; -import { NodesPage } from "@pages/Nodes.js"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import { ChannelsPage } from "@pages/Channels.tsx"; +import { ConfigPage } from "@pages/Config/index.tsx"; +import { MapPage } from "@pages/Map.tsx"; +import { MessagesPage } from "@pages/Messages.tsx"; +import { NodesPage } from "@pages/Nodes.tsx"; export const PageRouter = (): JSX.Element => { const { activePage } = useDevice(); diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 2062b740..efcd3954 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -5,9 +5,9 @@ import { CommandInput, CommandItem, CommandList, -} from "@components/UI/Command.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDevice, useDeviceStore } from "@core/stores/deviceStore.js"; +} from "@components/UI/Command.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { useCommandState } from "cmdk"; import { diff --git a/src/components/DeviceSelector.tsx b/src/components/DeviceSelector.tsx index 1465c52b..42be3ecf 100644 --- a/src/components/DeviceSelector.tsx +++ b/src/components/DeviceSelector.tsx @@ -1,8 +1,8 @@ -import { DeviceSelectorButton } from "@components/DeviceSelectorButton.js"; -import { Separator } from "@components/UI/Seperator.js"; -import { Code } from "@components/UI/Typography/Code.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; +import { DeviceSelectorButton } from "@components/DeviceSelectorButton.tsx"; +import { Separator } from "@components/UI/Seperator.tsx"; +import { Code } from "@components/UI/Typography/Code.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { HomeIcon, diff --git a/src/components/Dialog/DeviceNameDialog.tsx b/src/components/Dialog/DeviceNameDialog.tsx index 403f4e8c..3fd6fd2b 100644 --- a/src/components/Dialog/DeviceNameDialog.tsx +++ b/src/components/Dialog/DeviceNameDialog.tsx @@ -1,5 +1,5 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -7,9 +7,9 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; import { Protobuf } from "@meshtastic/js"; import { useForm } from "react-hook-form"; diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 53e2509b..afebdf5d 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -1,10 +1,11 @@ -import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.js"; -import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.js"; -import { ImportDialog } from "@components/Dialog/ImportDialog.js"; -import { QRDialog } from "@components/Dialog/QRDialog.js"; -import { RebootDialog } from "@components/Dialog/RebootDialog.js"; -import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.tsx"; +import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx"; +import { ImportDialog } from "@components/Dialog/ImportDialog.tsx"; +import { QRDialog } from "@components/Dialog/QRDialog.tsx"; +import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; +import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); @@ -49,6 +50,12 @@ export const DialogManager = (): JSX.Element => { setDialogOpen("nodeRemoval", open); }} /> + { + setDialogOpen("pkiBackup", open); + }} + /> ); }; diff --git a/src/components/Dialog/ImportDialog.tsx b/src/components/Dialog/ImportDialog.tsx index 37327096..c331911b 100644 --- a/src/components/Dialog/ImportDialog.tsx +++ b/src/components/Dialog/ImportDialog.tsx @@ -1,5 +1,5 @@ -import { Button } from "@components/UI/Button.js"; -import { Checkbox } from "@components/UI/Checkbox.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Checkbox } from "@components/UI/Checkbox.tsx"; import { Dialog, DialogContent, @@ -7,11 +7,11 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; -import { Switch } from "@components/UI/Switch.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { toByteArray } from "base64-js"; import { useEffect, useState } from "react"; diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index d3277884..e3330cbf 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,20 +1,26 @@ -import { BLE } from "@components/PageComponents/Connect/BLE.js"; -import { HTTP } from "@components/PageComponents/Connect/HTTP.js"; -import { Serial } from "@components/PageComponents/Connect/Serial.js"; +import { + type BrowserFeature, + useBrowserFeatureDetection, +} from "@app/core/hooks/useBrowserFeatureDetection"; +import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; +import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; +import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; import { Dialog, DialogContent, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; +} from "@components/UI/Dialog.tsx"; +import { AlertCircle, InfoIcon, } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; -import { Link } from "@components/UI/Typography/Link.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +} from "@components/UI/Tabs.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; +import { Link } from "../UI/Typography/Link"; +import { Fragment } from "react/jsx-runtime"; export interface TabElementProps { closeDialog: () => void; @@ -23,44 +29,114 @@ export interface TabElementProps { export interface TabManifest { label: string; element: React.FC; - disabled: boolean; - disabledMessage: string; - disabledLink?: string; + isDisabled: boolean; } -const tabs: TabManifest[] = [ - { - label: "HTTP", - element: HTTP, - disabled: false, - disabledMessage: "Unsuported connection method", - }, - { - label: "Bluetooth", - element: BLE, - disabled: !navigator.bluetooth, - disabledMessage: - "Web Bluetooth is currently only supported by Chromium-based browsers", - disabledLink: - "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", - }, - { - label: "Serial", - element: Serial, - disabled: !navigator.serial, - disabledMessage: - "WebSerial is currently only supported by Chromium based browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", - }, -]; export interface NewDeviceProps { open: boolean; onOpenChange: (open: boolean) => void; } +interface FeatureErrorProps { + missingFeatures: BrowserFeature[]; +} + +const links: { [key: string]: string } = { + "Web Bluetooth": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", + "Web Serial": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + "Secure Context": + "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts", +}; + +const listFormatter = new Intl.ListFormat('en', { + style: 'long', + type: 'conjunction' +}); + +const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => { + if (missingFeatures.length === 0) return null; + + const browserFeatures = missingFeatures.filter(feature => feature !== "Secure Context"); + const needsSecureContext = missingFeatures.includes("Secure Context"); + + const formatFeatureList = (features: string[]) => { + const parts = listFormatter.formatToParts(features); + return parts.map((part) => { + if (part.type === 'element') { + return ( + + {part.value} + + ); + } + return {part.value}; + }); + }; + + return ( + +
+ +
+

+ {browserFeatures.length > 0 && ( + <> + This application requires {formatFeatureList(browserFeatures)}. + Please use a Chromium-based browser like Chrome or Edge. + + )} + {needsSecureContext && ( + <> + {browserFeatures.length > 0 && " Additionally, it"} + {browserFeatures.length === 0 && "This application"} requires a{" "} + + secure context + + . Please connect using HTTPS or localhost. + + )} +

+
+
+
+ ); +}; + export const NewDeviceDialog = ({ open, onOpenChange, }: NewDeviceProps): JSX.Element => { + const { unsupported } = useBrowserFeatureDetection(); + + const tabs: TabManifest[] = [ + { + label: "HTTP", + element: HTTP, + isDisabled: false, + }, + { + label: "Bluetooth", + element: BLE, + isDisabled: + unsupported.includes("Web Bluetooth") || + unsupported.includes("Secure Context"), + }, + { + label: "Serial", + element: Serial, + isDisabled: + unsupported.includes("Web Serial") || + unsupported.includes("Secure Context"), + }, + ]; + return ( @@ -73,7 +149,6 @@ export const NewDeviceDialog = ({ {tab.label} @@ -81,35 +156,13 @@ export const NewDeviceDialog = ({ {tabs.map((tab) => ( - {tab.disabled ? ( -

- {tab.disabledMessage} -

- ) : ( +
+ {tab.isDisabled ? : null} onOpenChange(false)} /> - )} +
))} - - {(!navigator.bluetooth || !navigator.serial) && ( - <> - - Web Bluetooth and Web Serial are currently only supported by - Chromium-based browsers. - - - Read more:  - - Web Bluetooth - -   - - Web Serial - - - - )}
); diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx new file mode 100644 index 00000000..9b6c18d8 --- /dev/null +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -0,0 +1,134 @@ +import { useDevice } from "@app/core/stores/deviceStore"; +import { Button } from "@components/UI/Button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { fromByteArray } from "base64-js"; +import { DownloadIcon, PrinterIcon } from "lucide-react"; +import React from "react"; + +export interface PkiBackupDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const PkiBackupDialog = ({ + open, + onOpenChange, +}: PkiBackupDialogProps) => { + const { config, setDialogOpen } = useDevice(); + const privateKey = config.security?.privateKey; + const publicKey = config.security?.publicKey; + + const decodeKeyData = React.useCallback( + (key: Uint8Array) => { + if (!key) return ""; + return fromByteArray(key ?? new Uint8Array(0)); + }, + [], + ); + + const closeDialog = React.useCallback(() => { + setDialogOpen("pkiBackup", false); + }, [setDialogOpen]); + + const renderPrintWindow = React.useCallback(() => { + if (!privateKey || !publicKey) return; + + const printWindow = window.open("", "_blank"); + if (printWindow) { + printWindow.document.write(` + + + === MESHTASTIC KEYS === + + + +

=== MESHTASTIC KEYS ===

+
+

Public Key:

+

${decodeKeyData(publicKey)}

+

Private Key:

+

${decodeKeyData(privateKey)}

+
+

=== END OF KEYS ===

+ + + `); + printWindow.document.close(); + printWindow.print(); + closeDialog(); + } + }, [decodeKeyData, privateKey, publicKey, closeDialog]); + + const createDownloadKeyFile = React.useCallback(() => { + if (!privateKey || !publicKey) return; + + const decodedPrivateKey = decodeKeyData(privateKey); + const decodedPublicKey = decodeKeyData(publicKey); + + const formattedContent = [ + "=== MESHTASTIC KEYS ===\n\n", + "Private Key:\n", + decodedPrivateKey, + "\n\nPublic Key:\n", + decodedPublicKey, + "\n\n=== END OF KEYS ===", + ].join(""); + + const blob = new Blob([formattedContent], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.download = "meshtastic_keys.txt"; + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + closeDialog(); + URL.revokeObjectURL(url); + }, [decodeKeyData, privateKey, publicKey, closeDialog]); + + return ( + + + + Backup Keys + + Its important to backup your public and private keys and store your + backup securely! + + + + If you lose your keys, you will need to reset your device. + + + + + + + + + + ); +}; diff --git a/src/components/Dialog/PkiRegenerateDialog.tsx b/src/components/Dialog/PkiRegenerateDialog.tsx index 3edc221a..818e7207 100644 --- a/src/components/Dialog/PkiRegenerateDialog.tsx +++ b/src/components/Dialog/PkiRegenerateDialog.tsx @@ -1,4 +1,4 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -6,7 +6,7 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; +} from "@components/UI/Dialog.tsx"; export interface PkiRegenerateDialogProps { open: boolean; diff --git a/src/components/Dialog/QRDialog.tsx b/src/components/Dialog/QRDialog.tsx index 9a8853fa..9bd29ed2 100644 --- a/src/components/Dialog/QRDialog.tsx +++ b/src/components/Dialog/QRDialog.tsx @@ -1,4 +1,4 @@ -import { Checkbox } from "@components/UI/Checkbox.js"; +import { Checkbox } from "@components/UI/Checkbox.tsx"; import { Dialog, DialogContent, @@ -6,9 +6,9 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; import { Protobuf, type Types } from "@meshtastic/js"; import { fromByteArray } from "base64-js"; import { ClipboardIcon } from "lucide-react"; diff --git a/src/components/Dialog/RebootDialog.tsx b/src/components/Dialog/RebootDialog.tsx index 79ff86bd..3dbcb268 100644 --- a/src/components/Dialog/RebootDialog.tsx +++ b/src/components/Dialog/RebootDialog.tsx @@ -1,13 +1,13 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { ClockIcon, RefreshCwIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/components/Dialog/RemoveNodeDialog.tsx b/src/components/Dialog/RemoveNodeDialog.tsx index b2db5d56..81bfcd04 100644 --- a/src/components/Dialog/RemoveNodeDialog.tsx +++ b/src/components/Dialog/RemoveNodeDialog.tsx @@ -1,6 +1,6 @@ import { useAppStore } from "@app/core/stores/appStore"; -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -8,8 +8,8 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Label } from "@components/UI/Label.tsx"; export interface RemoveNodeDialogProps { open: boolean; diff --git a/src/components/Dialog/ShutdownDialog.tsx b/src/components/Dialog/ShutdownDialog.tsx index cf34fee5..deb77770 100644 --- a/src/components/Dialog/ShutdownDialog.tsx +++ b/src/components/Dialog/ShutdownDialog.tsx @@ -1,13 +1,13 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { ClockIcon, PowerIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx index f957e7f0..c9456b40 100644 --- a/src/components/Form/DynamicForm.tsx +++ b/src/components/Form/DynamicForm.tsx @@ -1,11 +1,11 @@ import { DynamicFormField, type FieldProps, -} from "@components/Form/DynamicFormField.js"; -import { FieldWrapper } from "@components/Form/FormWrapper.js"; -import { Button } from "@components/UI/Button.js"; -import { H4 } from "@components/UI/Typography/H4.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +} from "@components/Form/DynamicFormField.tsx"; +import { FieldWrapper } from "@components/Form/FormWrapper.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { H4 } from "@components/UI/Typography/H4.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { type Control, type DefaultValues, diff --git a/src/components/Form/DynamicFormField.tsx b/src/components/Form/DynamicFormField.tsx index f66b24c1..388836d4 100644 --- a/src/components/Form/DynamicFormField.tsx +++ b/src/components/Form/DynamicFormField.tsx @@ -1,19 +1,19 @@ import { GenericInput, type InputFieldProps, -} from "@components/Form/FormInput.js"; +} from "@components/Form/FormInput.tsx"; import { PasswordGenerator, type PasswordGeneratorProps, -} from "@components/Form/FormPasswordGenerator.js"; +} from "@components/Form/FormPasswordGenerator.tsx"; import { type SelectFieldProps, SelectInput, -} from "@components/Form/FormSelect.js"; +} from "@components/Form/FormSelect.tsx"; import { type ToggleFieldProps, ToggleInput, -} from "@components/Form/FormToggle.js"; +} from "@components/Form/FormToggle.tsx"; import type { Control, FieldValues } from "react-hook-form"; export type FieldProps = diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index 13f77260..5fef2794 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -1,10 +1,12 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Input } from "@components/UI/Input.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Input } from "@components/UI/Input.tsx"; import type { LucideIcon } from "lucide-react"; +import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler } from "react"; +import { useState } from "react"; import { Controller, type FieldValues } from "react-hook-form"; export interface InputFieldProps extends BaseFormBuilderProps { @@ -27,13 +29,28 @@ export function GenericInput({ disabled, field, }: GenericFormElementProps>) { + const [passwordShown, setPasswordShown] = useState(false); + const togglePasswordVisiblity = () => { + setPasswordShown(!passwordShown); + }; + return ( ( { diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index cf05f806..784086fb 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -1,10 +1,13 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Generator } from "@components/UI/Generator.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Generator } from "@components/UI/Generator.tsx"; +import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler, MouseEventHandler } from "react"; +import { useState } from "react"; import { Controller, type FieldValues } from "react-hook-form"; +import type { ButtonVariant } from "@components/UI/Button"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { type: "passwordGenerator"; @@ -13,7 +16,12 @@ export interface PasswordGeneratorProps extends BaseFormBuilderProps { devicePSKBitCount: number; inputChange: ChangeEventHandler; selectChange: (event: string) => void; - buttonClick: MouseEventHandler; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; } export function PasswordGenerator({ @@ -21,21 +29,33 @@ export function PasswordGenerator({ field, disabled, }: GenericFormElementProps>) { + const [passwordShown, setPasswordShown] = useState(false); + const togglePasswordVisiblity = () => { + setPasswordShown(!passwordShown); + }; + return ( ( extends BaseFormBuilderProps { diff --git a/src/components/Form/FormToggle.tsx b/src/components/Form/FormToggle.tsx index 7a461147..e79bac76 100644 --- a/src/components/Form/FormToggle.tsx +++ b/src/components/Form/FormToggle.tsx @@ -1,8 +1,8 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Switch } from "@components/UI/Switch.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; import type { ChangeEvent } from "react"; import { Controller, type FieldValues } from "react-hook-form"; diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 87f8aec2..7625946f 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -1,4 +1,4 @@ -import { Label } from "@components/UI/Label.js"; +import { Label } from "@components/UI/Label.tsx"; export interface FieldWrapperProps { label: string; diff --git a/src/components/KeyBackupReminder.tsx b/src/components/KeyBackupReminder.tsx new file mode 100644 index 00000000..7463d85f --- /dev/null +++ b/src/components/KeyBackupReminder.tsx @@ -0,0 +1,19 @@ +import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder"; +import { useDevice } from "@app/core/stores/deviceStore"; + +export const KeyBackupReminder = (): JSX.Element => { + const { setDialogOpen } = useDevice(); + + useBackupReminder({ + reminderInDays: 7, + message: + "We recommend backing up your key data regularly. Would you like to back up now?", + onAccept: () => setDialogOpen("pkiBackup", true), + enabled: true, + cookieOptions: { + secure: true, + sameSite: "strict", + }, + }); + return <>; +}; diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index 293a5cad..ef316c91 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -1,11 +1,12 @@ -import type { ChannelValidation } from "@app/validation/channel.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { ChannelValidation } from "@app/validation/channel.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import cryptoRandomString from "crypto-random-string"; import { useState } from "react"; +import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog"; export interface SettingsPanelProps { channel: Protobuf.Channel.Channel; @@ -22,6 +23,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { channel?.settings?.psk.length ?? 16, ); const [validationText, setValidationText] = useState(); + const [preSharedDialogOpen, setPreSharedDialogOpen] = useState(false); const onSubmit = (data: ChannelValidation) => { const channel = new Protobuf.Channel.Channel({ @@ -46,7 +48,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }); }; - const clickEvent = () => { + const preSharedKeyRegenerate = () => { setPass( btoa( cryptoRandomString({ @@ -56,6 +58,11 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { ), ); setValidationText(undefined); + setPreSharedDialogOpen(false); + }; + + const preSharedClickEvent = () => { + setPreSharedDialogOpen(true); }; const validatePass = (input: string, count: number) => { @@ -79,103 +86,105 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }; return ( - - onSubmit={onSubmit} - submitType="onSubmit" - hasSubmitButton={true} - defaultValues={{ - ...channel, - ...{ - settings: { - ...channel?.settings, - psk: pass, - positionEnabled: - channel?.settings?.moduleSettings?.positionPrecision !== + <> + + onSubmit={onSubmit} + submitType="onSubmit" + hasSubmitButton={true} + defaultValues={{ + ...channel, + ...{ + settings: { + ...channel?.settings, + psk: pass, + positionEnabled: + channel?.settings?.moduleSettings?.positionPrecision !== undefined && - channel?.settings?.moduleSettings?.positionPrecision > 0, - preciseLocation: - channel?.settings?.moduleSettings?.positionPrecision === 32, - positionPrecision: - channel?.settings?.moduleSettings?.positionPrecision === undefined - ? 10 - : channel?.settings?.moduleSettings?.positionPrecision, + channel?.settings?.moduleSettings?.positionPrecision > 0, + preciseLocation: + channel?.settings?.moduleSettings?.positionPrecision === 32, + positionPrecision: + channel?.settings?.moduleSettings?.positionPrecision === undefined + ? 10 + : channel?.settings?.moduleSettings?.positionPrecision, + }, }, - }, - }} - fieldGroups={[ - { - label: "Channel Settings", - description: "Crypto, MQTT & misc settings", - fields: [ - { - type: "select", - name: "role", - label: "Role", - disabled: channel.index === 0, - description: - "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - properties: { - enumValue: - channel.index === 0 - ? { PRIMARY: 1 } - : { DISABLED: 0, SECONDARY: 2 }, + }} + fieldGroups={[ + { + label: "Channel Settings", + description: "Crypto, MQTT & misc settings", + fields: [ + { + type: "select", + name: "role", + label: "Role", + disabled: channel.index === 0, + description: + "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + properties: { + enumValue: + channel.index === 0 + ? { PRIMARY: 1 } + : { DISABLED: 0, SECONDARY: 2 }, + }, }, - }, - { - type: "passwordGenerator", - name: "settings.psk", - label: "pre-Shared Key", - description: "256, 128, or 8 bit PSKs allowed", - validationText: validationText, - devicePSKBitCount: bitCount ?? 0, - inputChange: inputChangeEvent, - selectChange: selectChangeEvent, - buttonClick: clickEvent, - properties: { - value: pass, + { + type: "passwordGenerator", + name: "settings.psk", + label: "Pre-Shared Key", + description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + validationText: validationText, + devicePSKBitCount: bitCount ?? 0, + inputChange: inputChangeEvent, + selectChange: selectChangeEvent, + actionButtons: [{ text: 'Generate', variant: 'success', onClick: preSharedClickEvent }], + hide: true, + properties: { + value: pass, + }, }, - }, - { - type: "text", - name: "settings.name", - label: "Name", - description: - "A unique name for the channel <12 bytes, leave blank for default", - }, - { - type: "toggle", - name: "settings.uplinkEnabled", - label: "Uplink Enabled", - description: "Send messages from the local mesh to MQTT", - }, - { - type: "toggle", - name: "settings.downlinkEnabled", - label: "Downlink Enabled", - description: "Send messages from MQTT to the local mesh", - }, - { - type: "toggle", - name: "settings.positionEnabled", - label: "Allow Position Requests", - description: "Send position to channel", - }, - { - type: "toggle", - name: "settings.preciseLocation", - label: "Precise Location", - description: "Send precise location to channel", - }, - { - type: "select", - name: "settings.positionPrecision", - label: "Approximate Location", - description: - "If not sharing precise location, position shared on channel will be accurate within this distance", - properties: { - enumValue: - config.display?.units === 0 - ? { + { + type: "text", + name: "settings.name", + label: "Name", + description: + "A unique name for the channel <12 bytes, leave blank for default", + }, + { + type: "toggle", + name: "settings.uplinkEnabled", + label: "Uplink Enabled", + description: "Send messages from the local mesh to MQTT", + }, + { + type: "toggle", + name: "settings.downlinkEnabled", + label: "Downlink Enabled", + description: "Send messages from MQTT to the local mesh", + }, + { + type: "toggle", + name: "settings.positionEnabled", + label: "Allow Position Requests", + description: "Send position to channel", + }, + { + type: "toggle", + name: "settings.preciseLocation", + label: "Precise Location", + description: "Send precise location to channel", + }, + { + type: "select", + name: "settings.positionPrecision", + label: "Approximate Location", + description: + "If not sharing precise location, position shared on channel will be accurate within this distance", + properties: { + enumValue: + config.display?.units === 0 + ? { "Within 23 km": 10, "Within 12 km": 11, "Within 5.8 km": 12, @@ -187,7 +196,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { "Within 90 m": 18, "Within 50 m": 19, } - : { + : { "Within 15 miles": 10, "Within 7.3 miles": 11, "Within 3.6 miles": 12, @@ -199,11 +208,17 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { "Within 300 feet": 18, "Within 150 feet": 19, }, + }, }, - }, - ], - }, - ]} - /> + ], + }, + ]} + /> + setPreSharedDialogOpen(false)} + onSubmit={() => preSharedKeyRegenerate()} + /> + ); }; diff --git a/src/components/PageComponents/Config/Bluetooth.tsx b/src/components/PageComponents/Config/Bluetooth.tsx index 797a6dff..b01d3c69 100644 --- a/src/components/PageComponents/Config/Bluetooth.tsx +++ b/src/components/PageComponents/Config/Bluetooth.tsx @@ -1,10 +1,25 @@ -import type { BluetoothValidation } from "@app/validation/config/bluetooth.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; +import { useState } from "react"; export const Bluetooth = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); + const [bluetoothValidationText, setBluetoothValidationText] = useState(); + + const bluetoothPinChangeEvent = ( + e: React.ChangeEvent, + ) => { + if (e.target.value[0] == "0") + { + setBluetoothValidationText("Bluetooth Pin cannot start with 0."); + } + else + { + setBluetoothValidationText(""); + } + } const onSubmit = (data: BluetoothValidation) => { setWorkingConfig( @@ -52,6 +67,8 @@ export const Bluetooth = (): JSX.Element => { name: "fixedPin", label: "Pin", description: "Pin to use when pairing", + validationText: bluetoothValidationText, + inputChange: bluetoothPinChangeEvent, disabledBy: [ { fieldName: "mode", diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index ce2b5ce1..9ef11904 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -1,6 +1,6 @@ -import type { DeviceValidation } from "@app/validation/config/device.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { DeviceValidation } from "@app/validation/config/device.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Device = (): JSX.Element => { @@ -32,7 +32,22 @@ export const Device = (): JSX.Element => { label: "Role", description: "What role the device performs on the mesh", properties: { - enumValue: Protobuf.Config.Config_DeviceConfig_Role, + enumValue: { + Client: Protobuf.Config.Config_DeviceConfig_Role.CLIENT, + "Client Mute": + Protobuf.Config.Config_DeviceConfig_Role.CLIENT_MUTE, + Router: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + Repeater: Protobuf.Config.Config_DeviceConfig_Role.REPEATER, + Tracker: Protobuf.Config.Config_DeviceConfig_Role.TRACKER, + Sensor: Protobuf.Config.Config_DeviceConfig_Role.SENSOR, + TAK: Protobuf.Config.Config_DeviceConfig_Role.TAK, + "Client Hidden": + Protobuf.Config.Config_DeviceConfig_Role.CLIENT_HIDDEN, + "Lost and Found": + Protobuf.Config.Config_DeviceConfig_Role.LOST_AND_FOUND, + "TAK Tracker": + Protobuf.Config.Config_DeviceConfig_Role.SENSOR, + }, formatEnumName: true, }, }, @@ -79,6 +94,12 @@ export const Device = (): JSX.Element => { label: "Disable Triple Click", description: "Disable triple click", }, + { + type: "toggle", + name: "ledHeartbeatDisabled", + label: "LED Heartbeat Disabled", + description: "Disable default blinking LED", + }, ], }, ]} diff --git a/src/components/PageComponents/Config/Display.tsx b/src/components/PageComponents/Config/Display.tsx index 5d0d0813..5589a8a7 100644 --- a/src/components/PageComponents/Config/Display.tsx +++ b/src/components/PageComponents/Config/Display.tsx @@ -1,6 +1,6 @@ -import type { DisplayValidation } from "@app/validation/config/display.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { DisplayValidation } from "@app/validation/config/display.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Display = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/LoRa.tsx b/src/components/PageComponents/Config/LoRa.tsx index 79e7b810..80665f6c 100644 --- a/src/components/PageComponents/Config/LoRa.tsx +++ b/src/components/PageComponents/Config/LoRa.tsx @@ -1,6 +1,6 @@ -import type { LoRaValidation } from "@app/validation/config/lora.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { LoRaValidation } from "@app/validation/config/lora.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const LoRa = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Network.tsx b/src/components/PageComponents/Config/Network.tsx index 00426104..afda4eb5 100644 --- a/src/components/PageComponents/Config/Network.tsx +++ b/src/components/PageComponents/Config/Network.tsx @@ -1,10 +1,10 @@ -import type { NetworkValidation } from "@app/validation/config/network.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { NetworkValidation } from "@app/validation/config/network.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { convertIntToIpAddress, convertIpAddressToInt, -} from "@core/utils/ip.js"; +} from "@core/utils/ip.ts"; import { Protobuf } from "@meshtastic/js"; export const Network = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Position.tsx b/src/components/PageComponents/Config/Position.tsx index e8a93c31..798da2e9 100644 --- a/src/components/PageComponents/Config/Position.tsx +++ b/src/components/PageComponents/Config/Position.tsx @@ -1,6 +1,6 @@ -import type { PositionValidation } from "@app/validation/config/position.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PositionValidation } from "@app/validation/config/position.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Position = (): JSX.Element => { @@ -77,12 +77,6 @@ export const Position = (): JSX.Element => { label: "Enable Pin", description: "GPS module enable pin override", }, - { - type: "number", - name: "channelPrecision", - label: "Channel Precision", - description: "GPS channel precision", - }, ], }, { diff --git a/src/components/PageComponents/Config/Power.tsx b/src/components/PageComponents/Config/Power.tsx index 0cf685a3..4f52be5f 100644 --- a/src/components/PageComponents/Config/Power.tsx +++ b/src/components/PageComponents/Config/Power.tsx @@ -1,6 +1,6 @@ -import type { PowerValidation } from "@app/validation/config/power.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PowerValidation } from "@app/validation/config/power.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Power = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 9848f491..d6e76cc9 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,18 +1,18 @@ import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog"; -import { DynamicForm } from "@app/components/Form/DynamicForm.js"; +import { DynamicForm } from "@app/components/Form/DynamicForm.tsx"; import { getX25519PrivateKey, getX25519PublicKey, } from "@app/core/utils/x25519"; -import type { SecurityValidation } from "@app/validation/config/security.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { SecurityValidation } from "@app/validation/config/security.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; export const Security = (): JSX.Element => { - const { config, nodes, hardware, setWorkingConfig } = useDevice(); + const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice(); const [privateKey, setPrivateKey] = useState( fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), @@ -31,7 +31,7 @@ export const Security = (): JSX.Element => { ); const [adminKeyValidationText, setAdminKeyValidationText] = useState(); - const [dialogOpen, setDialogOpen] = useState(false); + const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState(false); const onSubmit = (data: SecurityValidation) => { if (privateKeyValidationText || adminKeyValidationText) return; @@ -71,9 +71,13 @@ export const Security = (): JSX.Element => { }; const privateKeyClickEvent = () => { - setDialogOpen(true); + setPrivateKeyDialogOpen(true); }; + const pkiBackupClickEvent = () => { + setDialogOpen("pkiBackup", true); + } + const pkiRegenerate = () => { const privateKey = getX25519PrivateKey(); const publicKey = getX25519PublicKey(privateKey); @@ -86,7 +90,7 @@ export const Security = (): JSX.Element => { setPrivateKeyValidationText, ); - setDialogOpen(false); + setPrivateKeyDialogOpen(false); }; const privateKeyInputChangeEvent = ( @@ -149,7 +153,18 @@ export const Security = (): JSX.Element => { inputChange: privateKeyInputChangeEvent, selectChange: privateKeySelectChangeEvent, hide: !privateKeyVisible, - buttonClick: privateKeyClickEvent, + actionButtons: [ + { + text: "Generate", + onClick: privateKeyClickEvent, + variant: "success", + }, + { + text: "Backup Key", + onClick: pkiBackupClickEvent, + variant: "subtle", + }, + ], properties: { value: privateKey, action: { @@ -187,7 +202,7 @@ export const Security = (): JSX.Element => { name: "isManaged", label: "Managed", description: - 'If true, device is considered to be "managed" by a mesh administrator via admin messages', + 'If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.', }, { type: "text", @@ -228,8 +243,8 @@ export const Security = (): JSX.Element => { ]} /> setDialogOpen(false)} + open={privateKeyDialogOpen} + onOpenChange={() => setPrivateKeyDialogOpen(false)} onSubmit={() => pkiRegenerate()} /> diff --git a/src/components/PageComponents/Connect/BLE.tsx b/src/components/PageComponents/Connect/BLE.tsx index 0d78b4e1..82d8dd41 100644 --- a/src/components/PageComponents/Connect/BLE.tsx +++ b/src/components/PageComponents/Connect/BLE.tsx @@ -1,10 +1,10 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { BleConnection, Constants } from "@meshtastic/js"; import { useCallback, useEffect, useState } from "react"; diff --git a/src/components/PageComponents/Connect/HTTP.tsx b/src/components/PageComponents/Connect/HTTP.tsx index 1eb7ca5c..de776327 100644 --- a/src/components/PageComponents/Connect/HTTP.tsx +++ b/src/components/PageComponents/Connect/HTTP.tsx @@ -1,12 +1,12 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; -import { Switch } from "@components/UI/Switch.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { HttpConnection } from "@meshtastic/js"; import { useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx index 68ba70d6..53c623cb 100644 --- a/src/components/PageComponents/Connect/Serial.tsx +++ b/src/components/PageComponents/Connect/Serial.tsx @@ -1,10 +1,10 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { SerialConnection } from "@meshtastic/js"; import { useCallback, useEffect, useState } from "react"; @@ -14,13 +14,13 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { const { setSelectedDevice } = useAppStore(); const updateSerialPortList = useCallback(async () => { - setSerialPorts(await navigator.serial.getPorts()); + setSerialPorts(await navigator?.serial.getPorts()); }, []); - navigator.serial.addEventListener("connect", () => { + navigator?.serial?.addEventListener("connect", () => { updateSerialPortList(); }); - navigator.serial.addEventListener("disconnect", () => { + navigator?.serial?.addEventListener("disconnect", () => { updateSerialPortList(); }); useEffect(() => { @@ -58,9 +58,8 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { await onConnect(port); }} > - {`# ${index} - ${usbVendorId ?? "UNK"} - ${ - usbProductId ?? "UNK" - }`} + {`# ${index} - ${usbVendorId ?? "UNK"} - ${usbProductId ?? "UNK" + }`} ); })} diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx new file mode 100644 index 00000000..d1cbd8a9 --- /dev/null +++ b/src/components/PageComponents/Map/NodeDetail.tsx @@ -0,0 +1,171 @@ +import { Mono } from "@components/generic/Mono.tsx"; +import { H5 } from "@app/components/UI/Typography/H5.tsx"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; +import { Separator } from "@app/components/UI/Seperator"; +import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx"; +import { Hashicon } from "@emeraldpay/hashicon-react"; +import { Protobuf } from "@meshtastic/js"; +import type { Protobuf as ProtobufType } from "@meshtastic/js"; +import { + BatteryChargingIcon, + BatteryFullIcon, + BatteryLowIcon, + BatteryMediumIcon, + Dot, + LockIcon, + LockOpenIcon, + MountainSnow, + Star, +} from "lucide-react"; +import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; + +export interface NodeDetailProps { + node: ProtobufType.Mesh.NodeInfo; +} + +export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => { + const name = node.user?.longName || `!${numberToHexUnpadded(node.num)}`; + const hardwareType = Protobuf.Mesh.HardwareModel[ + node.user?.hwModel ?? 0 + ].replaceAll("_", " "); + + return ( +
+
+
+ + +
+ {node.user?.publicKey && node.user?.publicKey.length > 0 ? ( + + ) : ( + + )} +
+ + +
+ +
+
{name}
+ + {hardwareType !== "UNSET" && {hardwareType}} + + {!!node.deviceMetrics?.batteryLevel && ( +
+ {node.deviceMetrics?.batteryLevel > 100 ? ( + + ) : node.deviceMetrics?.batteryLevel > 80 ? ( + + ) : node.deviceMetrics?.batteryLevel > 20 ? ( + + ) : ( + + )} + + {node.deviceMetrics?.batteryLevel > 100 + ? "Charging" + : node.deviceMetrics?.batteryLevel + "%"} + +
+ )} + +
+ {node.user?.shortName &&
"{node.user?.shortName}"
} + {node.user?.id &&
{node.user?.id}
} +
+ +
+
+ {node.lastHeard > 0 && ( +
+ Heard +
+ )} +
+ {node.viaMqtt && ( +
+ MQTT +
+ )} +
+
+
+ + + +
+
+
+ {isNaN(node.hopsAway) ? "?" : node.hopsAway} +
+
{node.hopsAway === 1 ? "Hop" : "Hops"}
+
+ {node.position?.altitude && ( +
+ +
{node.position?.altitude} ft
+
+ )} +
+ +
+ {!!node.deviceMetrics?.channelUtilization && ( +
+
Channel Util
+ + {node.deviceMetrics?.channelUtilization.toPrecision(3)}% + +
+ )} + {!!node.deviceMetrics?.airUtilTx && ( +
+
Airtime Util
+ {node.deviceMetrics?.airUtilTx.toPrecision(3)}% +
+ )} +
+ + {node.snr !== 0 && ( +
+
SNR
+ + {node.snr}db + + {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}% + + {(node.snr + 10) * 5}raw + +
+ )} +
+ ); +}; diff --git a/src/components/PageComponents/Messages/ChannelChat.tsx b/src/components/PageComponents/Messages/ChannelChat.tsx index 7507de97..79950bf8 100644 --- a/src/components/PageComponents/Messages/ChannelChat.tsx +++ b/src/components/PageComponents/Messages/ChannelChat.tsx @@ -1,11 +1,11 @@ -import { Subtle } from "@app/components/UI/Typography/Subtle.js"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { type MessageWithState, useDevice, -} from "@app/core/stores/deviceStore.js"; -import { Message } from "@components/PageComponents/Messages/Message.js"; -import { MessageInput } from "@components/PageComponents/Messages/MessageInput.js"; -import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.js"; +} from "@app/core/stores/deviceStore.ts"; +import { Message } from "@components/PageComponents/Messages/Message.tsx"; +import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; +import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx"; import type { Protobuf, Types } from "@meshtastic/js"; import { InboxIcon } from "lucide-react"; diff --git a/src/components/PageComponents/Messages/Message.tsx b/src/components/PageComponents/Messages/Message.tsx index b958b803..22ed52b2 100644 --- a/src/components/PageComponents/Messages/Message.tsx +++ b/src/components/PageComponents/Messages/Message.tsx @@ -1,4 +1,4 @@ -import type { MessageWithState } from "@app/core/stores/deviceStore.js"; +import type { MessageWithState } from "@app/core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import type { Protobuf } from "@meshtastic/js"; import { @@ -44,6 +44,9 @@ export const Message = ({ {sender?.user?.longName ?? "UNK"} + + {message.rxTime.toLocaleDateString()} + {message.rxTime.toLocaleTimeString(undefined, { hour: "2-digit", diff --git a/src/components/PageComponents/Messages/MessageInput.tsx b/src/components/PageComponents/Messages/MessageInput.tsx index 42a7fb6a..9a7c865a 100644 --- a/src/components/PageComponents/Messages/MessageInput.tsx +++ b/src/components/PageComponents/Messages/MessageInput.tsx @@ -1,6 +1,6 @@ -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import type { Types } from "@meshtastic/js"; import { SendIcon } from "lucide-react"; diff --git a/src/components/PageComponents/Messages/TraceRoute.tsx b/src/components/PageComponents/Messages/TraceRoute.tsx index cdc20760..9d4c53e6 100644 --- a/src/components/PageComponents/Messages/TraceRoute.tsx +++ b/src/components/PageComponents/Messages/TraceRoute.tsx @@ -1,4 +1,4 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; import type { Protobuf } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; diff --git a/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx b/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx index 275487a8..978ef633 100644 --- a/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx +++ b/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const AmbientLighting = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Audio.tsx b/src/components/PageComponents/ModuleConfig/Audio.tsx index 807218fb..c68bd9bf 100644 --- a/src/components/PageComponents/ModuleConfig/Audio.tsx +++ b/src/components/PageComponents/ModuleConfig/Audio.tsx @@ -1,6 +1,6 @@ -import type { AudioValidation } from "@app/validation/moduleConfig/audio.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { AudioValidation } from "@app/validation/moduleConfig/audio.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Audio = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx index 1ab3da31..5fb1046a 100644 --- a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx +++ b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx @@ -1,6 +1,6 @@ -import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const CannedMessage = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx b/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx index 8bf49d42..c7ec3c98 100644 --- a/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx +++ b/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const DetectionSensor = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx index 44abc6c1..f6b52662 100644 --- a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx +++ b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx @@ -1,6 +1,6 @@ -import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const ExternalNotification = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/MQTT.tsx b/src/components/PageComponents/ModuleConfig/MQTT.tsx index 1dc324bb..bb9b1527 100644 --- a/src/components/PageComponents/ModuleConfig/MQTT.tsx +++ b/src/components/PageComponents/ModuleConfig/MQTT.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const MQTT = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx b/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx index 596638cc..9c878911 100644 --- a/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx +++ b/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const NeighborInfo = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Paxcounter.tsx b/src/components/PageComponents/ModuleConfig/Paxcounter.tsx index 42be1b14..e31a298c 100644 --- a/src/components/PageComponents/ModuleConfig/Paxcounter.tsx +++ b/src/components/PageComponents/ModuleConfig/Paxcounter.tsx @@ -1,6 +1,6 @@ -import type { PaxcounterValidation } from "@app/validation/moduleConfig/paxcounter.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PaxcounterValidation } from "@app/validation/moduleConfig/paxcounter.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Paxcounter = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/RangeTest.tsx b/src/components/PageComponents/ModuleConfig/RangeTest.tsx index 4fb2abb8..a661805d 100644 --- a/src/components/PageComponents/ModuleConfig/RangeTest.tsx +++ b/src/components/PageComponents/ModuleConfig/RangeTest.tsx @@ -1,6 +1,6 @@ -import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const RangeTest = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Serial.tsx b/src/components/PageComponents/ModuleConfig/Serial.tsx index 7fd40571..92db0d9a 100644 --- a/src/components/PageComponents/ModuleConfig/Serial.tsx +++ b/src/components/PageComponents/ModuleConfig/Serial.tsx @@ -1,6 +1,6 @@ -import type { SerialValidation } from "@app/validation/moduleConfig/serial.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { SerialValidation } from "@app/validation/moduleConfig/serial.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Serial = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/StoreForward.tsx b/src/components/PageComponents/ModuleConfig/StoreForward.tsx index 994f6051..6fe5c585 100644 --- a/src/components/PageComponents/ModuleConfig/StoreForward.tsx +++ b/src/components/PageComponents/ModuleConfig/StoreForward.tsx @@ -1,6 +1,6 @@ -import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.ts"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const StoreForward = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Telemetry.tsx b/src/components/PageComponents/ModuleConfig/Telemetry.tsx index 5af2f2b5..24074f48 100644 --- a/src/components/PageComponents/ModuleConfig/Telemetry.tsx +++ b/src/components/PageComponents/ModuleConfig/Telemetry.tsx @@ -1,6 +1,6 @@ -import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Telemetry = (): JSX.Element => { diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx index f6698b97..79495bdc 100644 --- a/src/components/PageLayout.tsx +++ b/src/components/PageLayout.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; import { AlignLeftIcon, type LucideIcon } from "lucide-react"; import Footer from "./UI/Footer"; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 116b6647..d4ada2bd 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,8 +1,8 @@ -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; -import { useDevice } from "@core/stores/deviceStore.js"; -import type { Page } from "@core/stores/deviceStore.js"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import type { Page } from "@core/stores/deviceStore.ts"; import { BatteryMediumIcon, CpuIcon, @@ -12,9 +12,12 @@ import { MapIcon, MessageSquareIcon, SettingsIcon, + SidebarCloseIcon, + SidebarOpenIcon, UsersIcon, ZapIcon, } from "lucide-react"; +import { useState } from "react"; export interface SidebarProps { children?: React.ReactNode; @@ -25,6 +28,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { const myNode = nodes.get(hardware.myNodeNum); const myMetadata = metadata.get(0); const { activePage, setActivePage, setDialogOpen } = useDevice(); + const [showSidebar, setShowSidebar] = useState(true); interface NavLink { name: string; @@ -60,7 +64,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { }, ]; - return ( + return showSidebar ? (
@@ -76,11 +80,20 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { > +
- {myNode?.deviceMetrics?.batteryLevel ?? "UNK"}% + + {myNode?.deviceMetrics?.batteryLevel + ? myNode?.deviceMetrics?.batteryLevel > 100 + ? "Charging" + : myNode?.deviceMetrics?.batteryLevel + "%" + : "UNK"} +
@@ -109,5 +122,11 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { {children}
+ ) : ( +
+ +
); }; diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index 4aa31845..f5531bba 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -1,5 +1,3 @@ -import { useToast } from "@core/hooks/useToast.js"; - import { Toast, ToastClose, @@ -7,24 +5,25 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@components/UI/Toast.js"; +} from "@components/UI/Toast"; +import { useToast } from "@core/hooks/useToast"; export function Toaster() { const { toasts } = useToast(); return ( - {toasts.map(({ id, title, description, action, ...props }) => ( - + {toasts.map(({ id, title, description, action, duration, ...props }) => ( +
- {title && ( - {title} - )} - {description && ( - - {description} - - )} + {title && {title}} + {description && {description}}
{action} @@ -33,4 +32,4 @@ export function Toaster() {
); -} +} \ No newline at end of file diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx index f903f25e..e1b2d704 100644 --- a/src/components/UI/Button.tsx +++ b/src/components/UI/Button.tsx @@ -1,7 +1,7 @@ import { type VariantProps, cva } from "class-variance-authority"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800", @@ -35,9 +35,11 @@ const buttonVariants = cva( }, ); +export type ButtonVariant = VariantProps["variant"]; + export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps {} + VariantProps { } const Button = React.forwardRef( ({ className, variant, size, ...props }, ref) => { diff --git a/src/components/UI/Checkbox.tsx b/src/components/UI/Checkbox.tsx index 38bc7873..7c0eaa9b 100644 --- a/src/components/UI/Checkbox.tsx +++ b/src/components/UI/Checkbox.tsx @@ -2,7 +2,7 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { Check } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Checkbox = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Command.tsx b/src/components/UI/Command.tsx index 00c51b61..63d77d28 100644 --- a/src/components/UI/Command.tsx +++ b/src/components/UI/Command.tsx @@ -3,8 +3,8 @@ import { Command as CommandPrimitive } from "cmdk"; import { Search } from "lucide-react"; import * as React from "react"; -import { Dialog, DialogContent } from "@components/UI/Dialog.js"; -import { cn } from "@core/utils/cn.js"; +import { Dialog, DialogContent } from "@components/UI/Dialog.tsx"; +import { cn } from "@core/utils/cn.ts"; const Command = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Dialog.tsx b/src/components/UI/Dialog.tsx index 5ab0c342..019b8142 100644 --- a/src/components/UI/Dialog.tsx +++ b/src/components/UI/Dialog.tsx @@ -2,7 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Dialog = DialogPrimitive.Root; diff --git a/src/components/UI/DropdownMenu.tsx b/src/components/UI/DropdownMenu.tsx index 02f3eb45..26ccdbe9 100644 --- a/src/components/UI/DropdownMenu.tsx +++ b/src/components/UI/DropdownMenu.tsx @@ -2,7 +2,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index 7d589be4..b365d7c9 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -1,26 +1,30 @@ import * as React from "react"; -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; +import { Button, type ButtonVariant } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@components/UI/Select.js"; +} from "@components/UI/Select.tsx"; import type { LucideIcon } from "lucide-react"; export interface GeneratorProps extends React.BaseHTMLAttributes { - hide?: boolean; + type: "text" | "password"; devicePSKBitCount?: number; value: string; variant: "default" | "invalid"; - buttonText?: string; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; bits?: { text: string; value: string; key: string }[]; selectChange: (event: string) => void; inputChange: (event: React.ChangeEvent) => void; - buttonClick: React.MouseEventHandler; action?: { icon: LucideIcon; onClick: () => void; @@ -31,19 +35,19 @@ export interface GeneratorProps extends React.BaseHTMLAttributes { const Generator = React.forwardRef( ( { - hide = true, + type, devicePSKBitCount, variant, value, - buttonText, + actionButtons, bits = [ { text: "256 bit", value: "32", key: "bit256" }, { text: "128 bit", value: "16", key: "bit128" }, { text: "8 bit", value: "1", key: "bit8" }, + { text: "Empty", value: "0", key: "empty" }, ], selectChange, inputChange, - buttonClick, action, disabled, ...props @@ -68,7 +72,7 @@ const Generator = React.forwardRef( return ( <> ( ))} - +
+ {actionButtons?.map(({ text, onClick, variant, className }) => ( + + ))} +
); }, diff --git a/src/components/UI/Input.tsx b/src/components/UI/Input.tsx index 630a6364..a397cef8 100644 --- a/src/components/UI/Input.tsx +++ b/src/components/UI/Input.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; import { type VariantProps, cva } from "class-variance-authority"; import type { LucideIcon } from "lucide-react"; diff --git a/src/components/UI/Label.tsx b/src/components/UI/Label.tsx index 9cef3b16..cb78b532 100644 --- a/src/components/UI/Label.tsx +++ b/src/components/UI/Label.tsx @@ -1,7 +1,7 @@ import * as LabelPrimitive from "@radix-ui/react-label"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Label = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Menubar.tsx b/src/components/UI/Menubar.tsx index 729665b3..bcf4d7ae 100644 --- a/src/components/UI/Menubar.tsx +++ b/src/components/UI/Menubar.tsx @@ -2,7 +2,7 @@ import * as MenubarPrimitive from "@radix-ui/react-menubar"; import { Check, ChevronRight, Circle } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const MenubarMenu = MenubarPrimitive.Menu; diff --git a/src/components/UI/Popover.tsx b/src/components/UI/Popover.tsx index 95641230..43977f2f 100644 --- a/src/components/UI/Popover.tsx +++ b/src/components/UI/Popover.tsx @@ -1,7 +1,7 @@ import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Popover = PopoverPrimitive.Root; diff --git a/src/components/UI/ScrollArea.tsx b/src/components/UI/ScrollArea.tsx index 44f69e26..7415b64e 100644 --- a/src/components/UI/ScrollArea.tsx +++ b/src/components/UI/ScrollArea.tsx @@ -1,7 +1,7 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const ScrollArea = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Select.tsx b/src/components/UI/Select.tsx index 59f5a9cd..32a0a8cb 100644 --- a/src/components/UI/Select.tsx +++ b/src/components/UI/Select.tsx @@ -2,7 +2,7 @@ import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Select = SelectPrimitive.Root; diff --git a/src/components/UI/Seperator.tsx b/src/components/UI/Seperator.tsx index 6b5a9692..20a5511b 100644 --- a/src/components/UI/Seperator.tsx +++ b/src/components/UI/Seperator.tsx @@ -1,7 +1,7 @@ import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Separator = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Sidebar/SidebarSection.tsx b/src/components/UI/Sidebar/SidebarSection.tsx index ed6cb767..acff2dab 100644 --- a/src/components/UI/Sidebar/SidebarSection.tsx +++ b/src/components/UI/Sidebar/SidebarSection.tsx @@ -1,4 +1,4 @@ -import { H4 } from "@components/UI/Typography/H4.js"; +import { H4 } from "@components/UI/Typography/H4.tsx"; export interface SidebarSectionProps { label: string; diff --git a/src/components/UI/Sidebar/sidebarButton.tsx b/src/components/UI/Sidebar/sidebarButton.tsx index ef8f3ff6..c3f30e79 100644 --- a/src/components/UI/Sidebar/sidebarButton.tsx +++ b/src/components/UI/Sidebar/sidebarButton.tsx @@ -1,4 +1,4 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import type { LucideIcon } from "lucide-react"; export interface SidebarButtonProps { diff --git a/src/components/UI/Switch.tsx b/src/components/UI/Switch.tsx index c3e97fdc..c395547d 100644 --- a/src/components/UI/Switch.tsx +++ b/src/components/UI/Switch.tsx @@ -1,7 +1,7 @@ import * as SwitchPrimitives from "@radix-ui/react-switch"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Switch = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Tabs.tsx b/src/components/UI/Tabs.tsx index f8c83e6d..66f126da 100644 --- a/src/components/UI/Tabs.tsx +++ b/src/components/UI/Tabs.tsx @@ -1,7 +1,7 @@ import * as TabsPrimitive from "@radix-ui/react-tabs"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Tabs = TabsPrimitive.Root; diff --git a/src/components/UI/Toast.tsx b/src/components/UI/Toast.tsx index a3080fbf..d40b294a 100644 --- a/src/components/UI/Toast.tsx +++ b/src/components/UI/Toast.tsx @@ -1,11 +1,11 @@ -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { type VariantProps, cva } from "class-variance-authority"; -import { X } from "lucide-react"; -import * as React from "react"; +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from 'lucide-react' -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn" -const ToastProvider = ToastPrimitives.Provider; +const ToastProvider = ToastPrimitives.Provider const ToastViewport = React.forwardRef< React.ElementRef, @@ -14,35 +14,34 @@ const ToastViewport = React.forwardRef< -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName const toastVariants = cva( - "data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4", + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { - default: - "bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700", + default: "border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50", destructive: - "group destructive bg-red-600 text-white border-red-600 dark:border-red-600", + "group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50" }, }, defaultVariants: { variant: "default", }, - }, -); + } +) const Toast = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & - VariantProps + VariantProps >(({ className, variant, ...props }, ref) => { return ( - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName const ToastAction = React.forwardRef< React.ElementRef, @@ -61,13 +60,13 @@ const ToastAction = React.forwardRef< -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; +)) +ToastAction.displayName = ToastPrimitives.Action.displayName const ToastClose = React.forwardRef< React.ElementRef, @@ -76,16 +75,16 @@ const ToastClose = React.forwardRef< -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; +)) +ToastClose.displayName = ToastPrimitives.Close.displayName const ToastTitle = React.forwardRef< React.ElementRef, @@ -96,8 +95,8 @@ const ToastTitle = React.forwardRef< className={cn("text-sm font-semibold", className)} {...props} /> -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName const ToastDescription = React.forwardRef< React.ElementRef, @@ -108,12 +107,12 @@ const ToastDescription = React.forwardRef< className={cn("text-sm opacity-90", className)} {...props} /> -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName -type ToastProps = React.ComponentPropsWithoutRef; +type ToastProps = React.ComponentPropsWithoutRef -type ToastActionElement = React.ReactElement; +type ToastActionElement = React.ReactElement export { type ToastProps, @@ -125,4 +124,5 @@ export { ToastDescription, ToastClose, ToastAction, -}; +} + diff --git a/src/components/UI/Tooltip.tsx b/src/components/UI/Tooltip.tsx index 9cdc8f83..bde4b344 100644 --- a/src/components/UI/Tooltip.tsx +++ b/src/components/UI/Tooltip.tsx @@ -1,7 +1,7 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const TooltipProvider = TooltipPrimitive.Provider; diff --git a/src/components/UI/Typography/H4.tsx b/src/components/UI/Typography/H4.tsx index e2562d6e..33eeb0af 100644 --- a/src/components/UI/Typography/H4.tsx +++ b/src/components/UI/Typography/H4.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; export interface H4Props { className?: string; diff --git a/src/components/UI/Typography/H5.tsx b/src/components/UI/Typography/H5.tsx new file mode 100644 index 00000000..5bd1dfb1 --- /dev/null +++ b/src/components/UI/Typography/H5.tsx @@ -0,0 +1,14 @@ +import { cn } from "@app/core/utils/cn.ts"; + +export interface H5Props { + className?: string; + children: React.ReactNode; +} + +export const H5 = ({ className, children }: H5Props): JSX.Element => ( +
+ {children} +
+); diff --git a/src/components/UI/Typography/Link.tsx b/src/components/UI/Typography/Link.tsx index af1a90ac..efcfbdf6 100644 --- a/src/components/UI/Typography/Link.tsx +++ b/src/components/UI/Typography/Link.tsx @@ -1,14 +1,17 @@ +import { cn } from "@app/core/utils/cn"; + export interface LinkProps { href: string; children: React.ReactNode; + className?: string; } -export const Link = ({ href, children }: LinkProps): JSX.Element => ( +export const Link = ({ href, children, className }: LinkProps): JSX.Element => ( {children} diff --git a/src/components/UI/Typography/Subtle.tsx b/src/components/UI/Typography/Subtle.tsx index 172e37ec..2772453c 100644 --- a/src/components/UI/Typography/Subtle.tsx +++ b/src/components/UI/Typography/Subtle.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; export interface SubtleProps { className?: string; diff --git a/src/components/generic/ThemeController.tsx b/src/components/generic/ThemeController.tsx index c1d89ce0..6dec03ad 100644 --- a/src/components/generic/ThemeController.tsx +++ b/src/components/generic/ThemeController.tsx @@ -1,4 +1,4 @@ -import { useAppStore } from "@core/stores/appStore.js"; +import { useAppStore } from "@core/stores/appStore.ts"; import type { ReactNode } from "react"; export interface ThemeControllerProps { diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts new file mode 100644 index 00000000..a31b7b5a --- /dev/null +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -0,0 +1,29 @@ +import { useMemo } from 'react'; + +export type BrowserFeature = 'Web Bluetooth' | 'Web Serial' | 'Secure Context'; + +interface BrowserSupport { + supported: BrowserFeature[]; + unsupported: BrowserFeature[]; +} + +export function useBrowserFeatureDetection(): BrowserSupport { + const support = useMemo(() => { + const features: [BrowserFeature, boolean][] = [ + ['Web Bluetooth', !!navigator?.bluetooth], + ['Web Serial', !!navigator?.serial], + ['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost'] + ]; + + return features.reduce( + (acc, [feature, isSupported]) => { + const list = isSupported ? acc.supported : acc.unsupported; + list.push(feature); + return acc; + }, + { supported: [], unsupported: [] } + ); + }, []); + + return support; +} \ No newline at end of file diff --git a/src/core/hooks/useCookie.ts b/src/core/hooks/useCookie.ts new file mode 100644 index 00000000..df3d9d82 --- /dev/null +++ b/src/core/hooks/useCookie.ts @@ -0,0 +1,52 @@ +import Cookies, { type CookieAttributes } from "js-cookie"; +import { useCallback, useState } from "react"; + +interface CookieHookResult { + value: T | undefined; + setCookie: (value: T, options?: CookieAttributes) => void; + removeCookie: () => void; +} + +function useCookie( + cookieName: string, + initialValue?: T, +): CookieHookResult { + const [cookieValue, setCookieValue] = useState(() => { + try { + const cookie = Cookies.get(cookieName); + return cookie ? (JSON.parse(cookie) as T) : initialValue; + } catch (error) { + console.error(`Error parsing cookie ${cookieName}:`, error); + return initialValue; + } + }); + + const setCookie = useCallback( + (value: T, options?: CookieAttributes) => { + try { + Cookies.set(cookieName, JSON.stringify(value), options); + setCookieValue(value); + } catch (error) { + console.error(`Error setting cookie ${cookieName}:`, error); + } + }, + [cookieName], + ); + + const removeCookie = useCallback(() => { + try { + Cookies.remove(cookieName); + setCookieValue(undefined); + } catch (error) { + console.error(`Error removing cookie ${cookieName}:`, error); + } + }, [cookieName]); + + return { + value: cookieValue, + setCookie, + removeCookie, + }; +} + +export default useCookie; diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx new file mode 100644 index 00000000..ee65d161 --- /dev/null +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -0,0 +1,120 @@ +import { Button } from "@app/components/UI/Button"; +import type { CookieAttributes } from "js-cookie"; +import { useCallback, useEffect, useRef } from "react"; +import useCookie from "./useCookie"; +import { useToast } from "./useToast"; + +interface UseBackupReminderOptions { + reminderInDays?: number; + message: string; + onAccept?: () => void | Promise; + enabled: boolean; + cookieOptions?: CookieAttributes; +} + +interface ReminderState { + suppressed: boolean; + lastShown: string; +} + +const TOAST_APPEAR_DELAY = 10_000 // 10 seconds; +const TOAST_DURATION = 30_000 // 30 seconds;: + +// remind user in 1 year to backup keys again, if they accept the reminder; +const ON_ACCEPT_REMINDER_DAYS = 365 + +function isReminderExpired(lastShown: string): boolean { + const lastShownDate = new Date(lastShown); + const now = new Date(); + const daysSinceLastShown = + (now.getTime() - lastShownDate.getTime()) / (1000 * 60 * 60 * 24); + return daysSinceLastShown >= 7; +} + +export function useBackupReminder({ + reminderInDays = 7, + enabled, + message, + onAccept = () => { }, + cookieOptions, +}: UseBackupReminderOptions) { + const { toast } = useToast(); + const toastShownRef = useRef(false); + const { value: reminderCookie, setCookie } = + useCookie("key_backup_reminder"); + + const suppressReminder = useCallback( + (days: number) => { + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + days); + + setCookie( + { + suppressed: true, + lastShown: new Date().toISOString(), + }, + { ...cookieOptions, expires: expiryDate }, + ); + }, + [setCookie, cookieOptions], + ); + + useEffect(() => { + if (!enabled || toastShownRef.current) return; + + const shouldShowReminder = + !reminderCookie?.suppressed || + isReminderExpired(reminderCookie.lastShown); + if (!shouldShowReminder) return; + + toastShownRef.current = true; + + const { dismiss } = toast( + { + title: "Backup Reminder", + duration: TOAST_DURATION, + delay: TOAST_APPEAR_DELAY, + description: message, + action: ( +
+ + +
+ ), + }, + ); + + return () => { + if (!toastShownRef.current) { + dismiss(); + } + }; + }, [ + enabled, + message, + onAccept, + reminderInDays, + suppressReminder, + toast, + reminderCookie, + ]); +} diff --git a/src/core/hooks/useToast.ts b/src/core/hooks/useToast.ts index 90b9a1c5..c913d223 100644 --- a/src/core/hooks/useToast.ts +++ b/src/core/hooks/useToast.ts @@ -1,6 +1,6 @@ import { type ReactNode, useSyncExternalStore } from "react"; -import type { ToastActionElement, ToastProps } from "@components/UI/Toast.js"; +import type { ToastActionElement, ToastProps } from "@components/UI/Toast.tsx"; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 1000000; @@ -10,6 +10,7 @@ type ToasterToast = ToastProps & { title?: ReactNode; description?: ReactNode; action?: ToastActionElement; + delay?: number; }; const actionTypes = { @@ -30,21 +31,21 @@ type ActionType = typeof actionTypes; type Action = | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; interface State { toasts: ToasterToast[]; @@ -80,7 +81,7 @@ export const reducer = (state: State, action: Action): State => { return { ...state, toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, + t.id === action.toast.id ? { ...t, ...action.toast } : t ), }; @@ -102,10 +103,10 @@ export const reducer = (state: State, action: Action): State => { toasts: state.toasts.map((t) => t.id === toastId || toastId === undefined ? { - ...t, - open: false, - } - : t, + ...t, + open: false, + } + : t ), }; } @@ -137,7 +138,7 @@ function dispatch(action: Action) { type Toast = Omit; -function toast({ ...props }: Toast) { +function toast({ delay = 0, ...props }: Toast) { const id = genId(); const update = (props: ToasterToast) => @@ -147,17 +148,19 @@ function toast({ ...props }: Toast) { }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); + setTimeout(() => { + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, }, - }, - }); + }); + }, delay); return { id: id, @@ -190,4 +193,4 @@ function useToast() { }; } -export { useToast, toast }; +export { toast, useToast }; \ No newline at end of file diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index a716a85b..bd407611 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -25,7 +25,8 @@ export type DialogVariant = | "shutdown" | "reboot" | "deviceName" - | "nodeRemoval"; + | "nodeRemoval" + | "pkiBackup"; export interface Device { id: number; @@ -60,6 +61,7 @@ export interface Device { reboot: boolean; deviceName: boolean; nodeRemoval: boolean; + pkiBackup: boolean; }; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -142,6 +144,7 @@ export const useDeviceStore = create((set, get) => ({ reboot: false, deviceName: false, nodeRemoval: false, + pkiBackup: false, }, pendingSettingsChanges: false, messageDraft: "", diff --git a/src/core/subscriptions.ts b/src/core/subscriptions.ts index 36bc72d2..afba12b7 100644 --- a/src/core/subscriptions.ts +++ b/src/core/subscriptions.ts @@ -1,4 +1,4 @@ -import type { Device } from "@core/stores/deviceStore.js"; +import type { Device } from "@core/stores/deviceStore.ts"; import { Protobuf, type Types } from "@meshtastic/js"; export const subscribeAll = ( diff --git a/src/core/utils/x25519.ts b/src/core/utils/x25519.ts index 2729d80a..7e69f339 100644 --- a/src/core/utils/x25519.ts +++ b/src/core/utils/x25519.ts @@ -3,6 +3,8 @@ import { x25519 } from "@noble/curves/ed25519"; export function getX25519PrivateKey(): Uint8Array { const key = x25519.utils.randomPrivateKey(); + // scalar clamping for curve25519, according to + // https://www.rfc-editor.org/rfc/rfc7748#section-5 key[0] &= 248; key[31] &= 127; key[31] |= 64; diff --git a/src/index.css b/src/index.css index d9508cd3..8a35066d 100644 --- a/src/index.css +++ b/src/index.css @@ -99,4 +99,4 @@ img { -drag: none; -webkit-user-drag: none; -} \ No newline at end of file +} diff --git a/src/index.tsx b/src/index.tsx index bbd5808d..33d79536 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import "maplibre-gl/dist/maplibre-gl.css"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { App } from "@app/App.js"; +import { App } from "@app/App.tsx"; const container = document.getElementById("root") as HTMLElement; const root = createRoot(container); diff --git a/src/pages/Channels.tsx b/src/pages/Channels.tsx index 83eefdbd..3468aa0e 100644 --- a/src/pages/Channels.tsx +++ b/src/pages/Channels.tsx @@ -3,11 +3,11 @@ import { TabsContent, TabsList, TabsTrigger, -} from "@app/components/UI/Tabs.js"; -import { Channel } from "@components/PageComponents/Channel.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@app/components/UI/Tabs.tsx"; +import { Channel } from "@components/PageComponents/Channel.tsx"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Types } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/js"; import { ImportIcon, QrCodeIcon } from "lucide-react"; diff --git a/src/pages/Config/DeviceConfig.tsx b/src/pages/Config/DeviceConfig.tsx index 45973e60..e870b944 100644 --- a/src/pages/Config/DeviceConfig.tsx +++ b/src/pages/Config/DeviceConfig.tsx @@ -1,18 +1,18 @@ -import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.js"; -import { Device } from "@components/PageComponents/Config/Device.js"; -import { Display } from "@components/PageComponents/Config/Display.js"; -import { LoRa } from "@components/PageComponents/Config/LoRa.js"; -import { Network } from "@components/PageComponents/Config/Network.js"; -import { Position } from "@components/PageComponents/Config/Position.js"; -import { Power } from "@components/PageComponents/Config/Power.js"; -import { Security } from "@components/PageComponents/Config/Security.js"; +import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.tsx"; +import { Device } from "@components/PageComponents/Config/Device.tsx"; +import { Display } from "@components/PageComponents/Config/Display.tsx"; +import { LoRa } from "@components/PageComponents/Config/LoRa.tsx"; +import { Network } from "@components/PageComponents/Config/Network.tsx"; +import { Position } from "@components/PageComponents/Config/Position.tsx"; +import { Power } from "@components/PageComponents/Config/Power.tsx"; +import { Security } from "@components/PageComponents/Config/Security.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Tabs.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; export const DeviceConfig = (): JSX.Element => { const { metadata } = useDevice(); diff --git a/src/pages/Config/ModuleConfig.tsx b/src/pages/Config/ModuleConfig.tsx index 276553da..95bd3dd1 100644 --- a/src/pages/Config/ModuleConfig.tsx +++ b/src/pages/Config/ModuleConfig.tsx @@ -1,21 +1,21 @@ -import { AmbientLighting } from "@app/components/PageComponents/ModuleConfig/AmbientLighting.js"; -import { DetectionSensor } from "@app/components/PageComponents/ModuleConfig/DetectionSensor.js"; -import { NeighborInfo } from "@app/components/PageComponents/ModuleConfig/NeighborInfo.js"; -import { Audio } from "@components/PageComponents/ModuleConfig/Audio.js"; -import { CannedMessage } from "@components/PageComponents/ModuleConfig/CannedMessage.js"; -import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.js"; -import { MQTT } from "@components/PageComponents/ModuleConfig/MQTT.js"; -import { Paxcounter } from "@components/PageComponents/ModuleConfig/Paxcounter.js"; -import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.js"; -import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js"; -import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.js"; -import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.js"; +import { AmbientLighting } from "@app/components/PageComponents/ModuleConfig/AmbientLighting.tsx"; +import { DetectionSensor } from "@app/components/PageComponents/ModuleConfig/DetectionSensor.tsx"; +import { NeighborInfo } from "@app/components/PageComponents/ModuleConfig/NeighborInfo.tsx"; +import { Audio } from "@components/PageComponents/ModuleConfig/Audio.tsx"; +import { CannedMessage } from "@components/PageComponents/ModuleConfig/CannedMessage.tsx"; +import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.tsx"; +import { MQTT } from "@components/PageComponents/ModuleConfig/MQTT.tsx"; +import { Paxcounter } from "@components/PageComponents/ModuleConfig/Paxcounter.tsx"; +import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.tsx"; +import { Serial } from "@components/PageComponents/ModuleConfig/Serial.tsx"; +import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.tsx"; +import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; +} from "@components/UI/Tabs.tsx"; export const ModuleConfig = (): JSX.Element => { const tabs = [ diff --git a/src/pages/Config/index.tsx b/src/pages/Config/index.tsx index c568670a..c942940c 100644 --- a/src/pages/Config/index.tsx +++ b/src/pages/Config/index.tsx @@ -1,11 +1,11 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { DeviceConfig } from "@pages/Config/DeviceConfig.js"; -import { ModuleConfig } from "@pages/Config/ModuleConfig.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { DeviceConfig } from "@pages/Config/DeviceConfig.tsx"; +import { ModuleConfig } from "@pages/Config/ModuleConfig.tsx"; import { BoxesIcon, SaveIcon, SettingsIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 3bbbbd9f..ee2c4693 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -1,9 +1,9 @@ -import { useAppStore } from "@app/core/stores/appStore.js"; -import { useDeviceStore } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; -import { Separator } from "@components/UI/Seperator.js"; -import { H3 } from "@components/UI/Typography/H3.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +import { useAppStore } from "@app/core/stores/appStore.ts"; +import { useDeviceStore } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; +import { Separator } from "@components/UI/Seperator.tsx"; +import { H3 } from "@components/UI/Typography/H3.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { BluetoothIcon, ListPlusIcon, diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx index 42cc5fcb..e326b336 100644 --- a/src/pages/Map.tsx +++ b/src/pages/Map.tsx @@ -1,11 +1,12 @@ -import { Subtle } from "@app/components/UI/Typography/Subtle.js"; -import { cn } from "@app/core/utils/cn.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; +import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail"; +import { cn } from "@app/core/utils/cn.ts"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { bbox, lineString } from "@turf/turf"; @@ -16,8 +17,9 @@ import { ZoomOutIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { Marker, useMap } from "react-map-gl"; +import { AttributionControl, Marker, Popup, useMap } from "react-map-gl"; import MapGl from "react-map-gl/maplibre"; +import { Protobuf } from "@meshtastic/js"; export const MapPage = (): JSX.Element => { const { nodes, waypoints } = useDevice(); @@ -25,6 +27,8 @@ export const MapPage = (): JSX.Element => { const { default: map } = useMap(); const [zoom, setZoom] = useState(0); + const [selectedNode, setSelectedNode] = + useState(null); const allNodes = Array.from(nodes.values()); @@ -126,6 +130,7 @@ export const MapPage = (): JSX.Element => { // }} // @ts-ignore + attributionControl={false} renderWorldCopies={false} maxPitch={0} @@ -142,6 +147,9 @@ export const MapPage = (): JSX.Element => { longitude: 0, }} > + {waypoints.map((wp) => ( { ))} */} {allNodes.map((node) => { - if (node.position?.latitudeI) { + if (node.position?.latitudeI && node.num !== selectedNode?.num) { return ( { style={{ filter: darkMode ? "invert(1)" : "" }} anchor="bottom" onClick={() => { + setSelectedNode(node); map?.easeTo({ zoom: 12, center: [ @@ -189,6 +198,17 @@ export const MapPage = (): JSX.Element => { ); } })} + {selectedNode?.position && ( + setSelectedNode(null)} + > + + + )} diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 4e7fbae9..28a35d2c 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -1,14 +1,14 @@ -import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.tsx"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { Protobuf, Types } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; -import { getChannelName } from "@pages/Channels.js"; +import { getChannelName } from "@pages/Channels.tsx"; import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx index 57b1a546..a2d1ec6a 100644 --- a/src/pages/Nodes.tsx +++ b/src/pages/Nodes.tsx @@ -2,12 +2,12 @@ import { NodeOptionsDialog } from "@app/components/Dialog/NodeOptionsDialog"; import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteResponseDialog"; import Footer from "@app/components/UI/Footer"; import { useAppStore } from "@app/core/stores/appStore"; -import { Sidebar } from "@components/Sidebar.js"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { Table } from "@components/generic/Table/index.js"; -import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { Table } from "@components/generic/Table/index.tsx"; +import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { Protobuf, type Types } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; diff --git a/src/validation/config/position.ts b/src/validation/config/position.ts index b02f095d..7eb42c56 100644 --- a/src/validation/config/position.ts +++ b/src/validation/config/position.ts @@ -43,7 +43,4 @@ export class PositionValidation @IsEnum(Protobuf.Config.Config_PositionConfig_GpsMode) gpsMode: Protobuf.Config.Config_PositionConfig_GpsMode; - - @IsArray() - channelPrecision: number[]; } diff --git a/src/validation/rasterSource.ts b/src/validation/rasterSource.ts index 0afeec77..965206d4 100644 --- a/src/validation/rasterSource.ts +++ b/src/validation/rasterSource.ts @@ -1,6 +1,6 @@ import { IsArray, IsBoolean, IsNumber, IsString, IsUrl } from "class-validator"; -import type { RasterSource } from "@core/stores/appStore.js"; +import type { RasterSource } from "@core/stores/appStore.ts"; export class MapValidation { @IsArray() diff --git a/tsconfig.json b/tsconfig.json index af036a6e..4cf9d6f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ "@types/w3c-web-serial" ], "strictPropertyInitialization": false, - "experimentalDecorators": true + "experimentalDecorators": true, + "allowImportingTsExtensions": true, } }