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 (
);
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 (
+
+ );
+};
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,
}
}