Browse Source

Map logic rework

pull/21/head
Sacha Weatherstone 4 years ago
parent
commit
d5e1ae4941
  1. 9
      package.json
  2. 226
      pnpm-lock.yaml
  3. 10
      src/App.tsx
  4. 3
      src/components/Connection.tsx
  5. 161
      src/components/Map/index.tsx
  6. 224
      src/components/MapBox/MapboxProvider.tsx
  7. 12
      src/components/MapBox/mapboxContext.ts
  8. 3
      src/components/chat/MessageBar.tsx
  9. 4
      src/components/connection/BLE.tsx
  10. 2
      src/components/connection/HTTP.tsx
  11. 6
      src/components/connection/Serial.tsx
  12. 2
      src/components/generic/Modal.tsx
  13. 2
      src/components/generic/form/BitwiseSelect.tsx
  14. 3
      src/components/menu/MobileNav.tsx
  15. 3
      src/components/menu/buttons/DeviceStatus.tsx
  16. 3
      src/components/menu/buttons/MobileNavToggle.tsx
  17. 2
      src/components/menu/buttons/Notifications.tsx
  18. 3
      src/components/menu/buttons/ThemeToggle.tsx
  19. 2
      src/components/templates/PageLayout.tsx
  20. 2
      src/core/connection.ts
  21. 28
      src/hooks/mapbox.ts
  22. 9
      src/hooks/redux.ts
  23. 7
      src/hooks/useAppDispatch.ts
  24. 6
      src/hooks/useAppSelector.ts
  25. 0
      src/hooks/useBreakpoint.ts
  26. 36
      src/hooks/useCreateMapbox.ts
  27. 8
      src/hooks/useMapbox.ts
  28. 12
      src/index.css
  29. 3
      src/pages/Messages.tsx
  30. 5
      src/pages/Nodes/Index.tsx
  31. 31
      src/pages/Nodes/NodeCard.tsx
  32. 2
      src/pages/Plugins/ExternalNotification.tsx
  33. 2
      src/pages/Plugins/RangeTest.tsx
  34. 2
      src/pages/Plugins/Serial.tsx
  35. 2
      src/pages/Plugins/StoreAndForward.tsx
  36. 2
      src/pages/settings/Channels.tsx
  37. 2
      src/pages/settings/Index.tsx
  38. 2
      src/pages/settings/Position.tsx
  39. 2
      src/pages/settings/Power.tsx
  40. 2
      src/pages/settings/Radio.tsx
  41. 2
      src/pages/settings/User.tsx
  42. 2
      src/pages/settings/WiFi.tsx
  43. 1
      tsconfig.json
  44. 1
      vite.config.ts

9
package.json

@ -14,7 +14,7 @@
"dependencies": {
"@floating-ui/react-dom": "^0.4.1",
"@headlessui/react": "^1.4.2",
"@meshtastic/components": "^1.0.12",
"@meshtastic/components": "^1.0.13",
"@meshtastic/meshtasticjs": "^0.6.36",
"@reduxjs/toolkit": "^1.7.1",
"base64-js": "^1.5.1",
@ -27,7 +27,7 @@
"react-error-boundary": "^3.1.4",
"react-file-icon": "^1.1.0",
"react-hook-form": "^7.22.5",
"react-i18next": "^11.15.2",
"react-i18next": "^11.15.3",
"react-icons": "^4.3.1",
"react-json-pretty": "^2.2.0",
"react-qr-code": "^2.0.3",
@ -40,7 +40,6 @@
"use-breakpoint": "^3.0.0"
},
"devDependencies": {
"@rollup/plugin-typescript": "^8.3.0",
"@types/mapbox-gl": "^2.6.0",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
@ -53,12 +52,12 @@
"@vitejs/plugin-react": "^1.1.3",
"autoprefixer": "^10.4.1",
"babel-plugin-module-resolver": "^4.1.0",
"eslint": "8.5.0",
"eslint": "8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-babel-module": "^5.3.1",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"gzipper": "^6.2.1",

226
pnpm-lock.yaml

@ -3,10 +3,9 @@ lockfileVersion: 5.3
specifiers:
'@floating-ui/react-dom': ^0.4.1
'@headlessui/react': ^1.4.2
'@meshtastic/components': ^1.0.12
'@meshtastic/components': ^1.0.13
'@meshtastic/meshtasticjs': ^0.6.36
'@reduxjs/toolkit': ^1.7.1
'@rollup/plugin-typescript': ^8.3.0
'@types/mapbox-gl': ^2.6.0
'@types/react': ^17.0.38
'@types/react-dom': ^17.0.11
@ -21,12 +20,12 @@ specifiers:
babel-plugin-module-resolver: ^4.1.0
base64-js: ^1.5.1
boring-avatars: ^1.6.1
eslint: 8.5.0
eslint: 8.6.0
eslint-config-prettier: ^8.3.0
eslint-import-resolver-alias: ^1.1.2
eslint-import-resolver-babel-module: ^5.3.1
eslint-import-resolver-typescript: ^2.5.0
eslint-plugin-import: ^2.25.3
eslint-plugin-import: ^2.25.4
eslint-plugin-react: ^7.28.0
eslint-plugin-react-hooks: ^4.3.0
gzipper: ^6.2.1
@ -40,7 +39,7 @@ specifiers:
react-error-boundary: ^3.1.4
react-file-icon: ^1.1.0
react-hook-form: ^7.22.5
react-i18next: ^11.15.2
react-i18next: ^11.15.3
react-icons: ^4.3.1
react-json-pretty: ^2.2.0
react-qr-code: ^2.0.3
@ -62,7 +61,7 @@ specifiers:
dependencies:
'@floating-ui/react-dom': 0.4.1_b3482aaf5744fc7c2aeb7941b0e0a78f
'@headlessui/react': 1.4[email protected][email protected]
'@meshtastic/components': 1.0.12
'@meshtastic/components': 1.0.13
'@meshtastic/meshtasticjs': 0.6.36
'@reduxjs/toolkit': 1.7[email protected][email protected]
base64-js: 1.5.1
@ -75,7 +74,7 @@ dependencies:
react-error-boundary: 3.1[email protected]
react-file-icon: 1.1[email protected][email protected]
react-hook-form: 7.22[email protected]
react-i18next: 11.15.2_80acb397921aff391f276c2217c76c9e
react-i18next: 11.15.3_80acb397921aff391f276c2217c76c9e
react-icons: 4.3[email protected]
react-json-pretty: 2.2[email protected][email protected]
react-qr-code: 2.0[email protected]
@ -88,27 +87,26 @@ dependencies:
use-breakpoint: 3.0[email protected][email protected]
devDependencies:
'@rollup/plugin-typescript': 8.3[email protected]
'@types/mapbox-gl': 2.6.0
'@types/react': 17.0.38
'@types/react-dom': 17.0.11
'@types/react-file-icon': 1.0.1
'@types/w3c-web-serial': 1.0.2
'@types/web-bluetooth': 0.0.12
'@typescript-eslint/eslint-plugin': 5.8.1_3a47348159e115370aa4cba56aba33b6
'@typescript-eslint/parser': 5.8.1_eslint@8.5[email protected]
'@typescript-eslint/eslint-plugin': 5.8.1_13039593e64cd539d0b4c5c2da390958
'@typescript-eslint/parser': 5.8.1_eslint@8.6[email protected]
'@verypossible/eslint-config': 1.6[email protected]
'@vitejs/plugin-react': 1.1.3
autoprefixer: 10.4[email protected]
babel-plugin-module-resolver: 4.1.0
eslint: 8.5.0
eslint-config-prettier: 8.3.0_eslint@8.5.0
eslint-import-resolver-alias: 1.1[email protected].3
eslint: 8.6.0
eslint-config-prettier: 8.3.0_eslint@8.6.0
eslint-import-resolver-alias: 1.1[email protected].4
eslint-import-resolver-babel-module: 5.3.1_e51044130ac762fd207a8cd2109b5344
eslint-import-resolver-typescript: 2.5.0_f385d671d5f1c72a868db745a891bc1f
eslint-plugin-import: 2.25.[email protected].0
eslint-plugin-react: 7.28.0_eslint@8.5.0
eslint-plugin-react-hooks: 4.3.0_eslint@8.5.0
eslint-import-resolver-typescript: 2.5.0_b5a36b8c1535387c8dd00eff7ec6b551
eslint-plugin-import: 2.25.[email protected].0
eslint-plugin-react: 7.28.0_eslint@8.6.0
eslint-plugin-react-hooks: 4.3.0_eslint@8.6.0
gzipper: 6.2.1
postcss: 8.4.5
prettier: 2.5.1
@ -1258,7 +1256,7 @@ packages:
babel-plugin-polyfill-corejs2: 0.3.0_@[email protected]
babel-plugin-polyfill-corejs3: 0.4.0_@[email protected]
babel-plugin-polyfill-regenerator: 0.3.0_@[email protected]
core-js-compat: 3.20.1
core-js-compat: 3.20.2
semver: 6.3.0
transitivePeerDependencies:
- supports-color
@ -1408,7 +1406,7 @@ packages:
dependencies:
ajv: 6.12.6
debug: 4.3.3
espree: 9.2.0
espree: 9.3.0
globals: 13.12.0
ignore: 4.0.6
import-fresh: 3.3.0
@ -1524,8 +1522,8 @@ packages:
engines: {node: '>=6.0.0'}
dev: false
/@meshtastic/components/1.0.12:
resolution: {integrity: sha512-7phiGyKSz2pRDYqscFDhL5lXdLlwY8LQU+hfgd13tEUe4Ch335rPYdyI7Fw+EZAVQTC456RCgMN7MyVfzsRunA==}
/@meshtastic/components/1.0.13:
resolution: {integrity: sha512-UdRwPVywIMaOYl0httBK51WagSuPhNPAuPG6DbvEq/TBBuJiGrTS/gYWsMb1UJhKzwb9XPKFMqAujowyBGN9cw==}
dependencies:
inter-ui: 3.19.3
react: 17.0.2
@ -1626,30 +1624,6 @@ packages:
rollup: 2.62.0
dev: true
/@rollup/plugin-typescript/[email protected]:
resolution: {integrity: sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==}
engines: {node: '>=8.0.0'}
peerDependencies:
rollup: ^2.14.0
tslib: '*'
typescript: '>=3.7.0'
dependencies:
'@rollup/pluginutils': 3.1.0
resolve: 1.20.0
typescript: 4.5.4
dev: true
/@rollup/pluginutils/3.1.0:
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
engines: {node: '>= 8.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0
dependencies:
'@types/estree': 0.0.39
estree-walker: 1.0.1
picomatch: 2.3.0
dev: true
/@rollup/pluginutils/[email protected]:
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
engines: {node: '>= 8.0.0'}
@ -1658,7 +1632,7 @@ packages:
dependencies:
'@types/estree': 0.0.39
estree-walker: 1.0.1
picomatch: 2.3.0
picomatch: 2.3.1
rollup: 2.62.0
dev: true
@ -1667,7 +1641,7 @@ packages:
engines: {node: '>= 8.0.0'}
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
picomatch: 2.3.1
dev: true
/@surma/rollup-plugin-off-main-thread/2.2.3:
@ -1712,8 +1686,8 @@ packages:
'@types/geojson': 7946.0.8
dev: true
/@types/node/17.0.5:
resolution: {integrity: sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==}
/@types/node/17.0.7:
resolution: {integrity: sha512-1QUk+WAUD4t8iR+Oj+UgI8oJa6yyxaB8a8pHaC8uqM6RrS1qbL7bf3Pwl5rHv0psm2CuDErgho6v5N+G+5fwtQ==}
dev: true
/@types/parse-json/4.0.0:
@ -1760,7 +1734,7 @@ packages:
/@types/resolve/1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 17.0.5
'@types/node': 17.0.7
dev: true
/@types/scheduler/0.16.2:
@ -1804,7 +1778,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin/5.8.1_3a47348159e115370aa4cba56aba33b6:
/@typescript-eslint/eslint-plugin/5.8.1_13039593e64cd539d0b4c5c2da390958:
resolution: {integrity: sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -1815,11 +1789,11 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/experimental-utils': 5.8.1_eslint@8.5[email protected]
'@typescript-eslint/parser': 5.8.1_eslint@8.5[email protected]
'@typescript-eslint/experimental-utils': 5.8.1_eslint@8.6[email protected]
'@typescript-eslint/parser': 5.8.1_eslint@8.6[email protected]
'@typescript-eslint/scope-manager': 5.8.1
debug: 4.3.3
eslint: 8.5.0
eslint: 8.6.0
functional-red-black-tree: 1.0.1
ignore: 5.2.0
regexpp: 3.2.0
@ -1848,7 +1822,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/experimental-utils/5.8.1_eslint@8.5[email protected]:
/@typescript-eslint/experimental-utils/5.8.1_eslint@8.6[email protected]:
resolution: {integrity: sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -1858,9 +1832,9 @@ packages:
'@typescript-eslint/scope-manager': 5.8.1
'@typescript-eslint/types': 5.8.1
'@typescript-eslint/typescript-estree': 5.8[email protected]
eslint: 8.5.0
eslint: 8.6.0
eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.5.0
eslint-utils: 3.0.0_eslint@8.6.0
transitivePeerDependencies:
- supports-color
- typescript
@ -1886,7 +1860,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser/5.8.1_eslint@8.5[email protected]:
/@typescript-eslint/parser/5.8.1_eslint@8.6[email protected]:
resolution: {integrity: sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -1900,7 +1874,7 @@ packages:
'@typescript-eslint/types': 5.8.1
'@typescript-eslint/typescript-estree': 5.8[email protected]
debug: 4.3.3
eslint: 8.5.0
eslint: 8.6.0
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
@ -1999,8 +1973,8 @@ packages:
eslint: 7.32.0
eslint-config-prettier: 8.3[email protected]
eslint-import-resolver-babel-module: 5.3.1_e51044130ac762fd207a8cd2109b5344
eslint-import-resolver-typescript: 2.5.0_a820dc868cc8cd66d8297be6779b9035
eslint-plugin-import: 2.25.3[email protected]
eslint-import-resolver-typescript: 2.5.0_157002f9dff1b62f2b20650d7e8bf1eb
eslint-plugin-import: 2.25.4[email protected]
eslint-plugin-react: 7.28[email protected]
eslint-plugin-react-hooks: 4.3[email protected]
prettier: 2.5.1
@ -2119,7 +2093,7 @@ packages:
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.0
picomatch: 2.3.1
dev: true
/aproba/1.2.0:
@ -2203,7 +2177,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.19.1
caniuse-lite: 1.0.30001294
caniuse-lite: 1.0.30001295
fraction.js: 4.1.2
normalize-range: 0.1.2
picocolors: 1.0.0
@ -2253,7 +2227,7 @@ packages:
dependencies:
'@babel/core': 7.16.7
'@babel/helper-define-polyfill-provider': 0.3.0_@[email protected]
core-js-compat: 3.20.1
core-js-compat: 3.20.2
transitivePeerDependencies:
- supports-color
dev: true
@ -2312,8 +2286,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001294
electron-to-chromium: 1.4.30
caniuse-lite: 1.0.30001295
electron-to-chromium: 1.4.31
escalade: 3.1.1
node-releases: 2.0.1
picocolors: 1.0.0
@ -2352,8 +2326,8 @@ packages:
engines: {node: '>= 6'}
dev: true
/caniuse-lite/1.0.30001294:
resolution: {integrity: sha512-LiMlrs1nSKZ8qkNhpUf5KD0Al1KCBE3zaT7OLOwEkagXMEDij98SiOovn9wxVGQpklk9vVC/pUSqgYmkmKOS8g==}
/caniuse-lite/1.0.30001295:
resolution: {integrity: sha512-lSP16vcyC0FEy0R4ECc9duSPoKoZy+YkpGkue9G4D81OfPnliopaZrU10+qtPdT8PbGXad/PNx43TIQrOmJZSQ==}
dev: true
/chalk/2.4.2:
@ -2460,8 +2434,8 @@ packages:
safe-buffer: 5.1.2
dev: true
/core-js-compat/3.20.1:
resolution: {integrity: sha512-AVhKZNpqMV3Jz8hU0YEXXE06qoxtQGsAqU0u1neUngz5IusDJRX/ZJ6t3i7mS7QxNyEONbCo14GprkBrxPlTZA==}
/core-js-compat/3.20.2:
resolution: {integrity: sha512-qZEzVQ+5Qh6cROaTPFLNS4lkvQ6mBzE3R6A6EEpssj7Zr2egMHgsy4XapdifqJDGC9CBiNv7s+ejI96rLNQFdg==}
dependencies:
browserslist: 4.19.1
semver: 7.0.0
@ -2672,8 +2646,8 @@ packages:
jake: 10.8.2
dev: true
/electron-to-chromium/1.4.30:
resolution: {integrity: sha512-609z9sIMxDHg+TcR/VB3MXwH+uwtrYyeAwWc/orhnr90ixs6WVGSrt85CDLGUdNnLqCA7liv426V20EecjvflQ==}
/electron-to-chromium/1.4.31:
resolution: {integrity: sha512-t3XVQtk+Frkv6aTD4RRk0OqosU+VLe1dQFW83MDer78ZD6a52frgXuYOIsLYTQiH2Lm+JB2OKYcn7zrX+YGAiQ==}
dev: true
/emoji-regex/8.0.0:
@ -2931,22 +2905,22 @@ packages:
eslint: 7.32.0
dev: true
/eslint-config-prettier/8.3.0_eslint@8.5.0:
/eslint-config-prettier/8.3.0_eslint@8.6.0:
resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.5.0
eslint: 8.6.0
dev: true
/eslint-import-resolver-alias/[email protected].3:
/eslint-import-resolver-alias/[email protected].4:
resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==}
engines: {node: '>= 4'}
peerDependencies:
eslint-plugin-import: '>=1.4.0'
dependencies:
eslint-plugin-import: 2.25.[email protected].0
eslint-plugin-import: 2.25.[email protected].0
dev: true
/eslint-import-resolver-babel-module/5.3.1_e51044130ac762fd207a8cd2109b5344:
@ -2968,7 +2942,7 @@ packages:
resolve: 1.20.0
dev: true
/eslint-import-resolver-typescript/2.5.0_a820dc868cc8cd66d8297be6779b9035:
/eslint-import-resolver-typescript/2.5.0_157002f9dff1b62f2b20650d7e8bf1eb:
resolution: {integrity: sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==}
engines: {node: '>=4'}
peerDependencies:
@ -2977,7 +2951,7 @@ packages:
dependencies:
debug: 4.3.3
eslint: 7.32.0
eslint-plugin-import: 2.25.3[email protected]
eslint-plugin-import: 2.25.4[email protected]
glob: 7.2.0
is-glob: 4.0.3
resolve: 1.20.0
@ -2986,7 +2960,7 @@ packages:
- supports-color
dev: true
/eslint-import-resolver-typescript/2.5.0_f385d671d5f1c72a868db745a891bc1f:
/eslint-import-resolver-typescript/2.5.0_b5a36b8c1535387c8dd00eff7ec6b551:
resolution: {integrity: sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==}
engines: {node: '>=4'}
peerDependencies:
@ -2994,8 +2968,8 @@ packages:
eslint-plugin-import: '*'
dependencies:
debug: 4.3.3
eslint: 8.5.0
eslint-plugin-import: 2.25.[email protected].0
eslint: 8.6.0
eslint-plugin-import: 2.25.[email protected].0
glob: 7.2.0
is-glob: 4.0.3
resolve: 1.20.0
@ -3004,17 +2978,16 @@ packages:
- supports-color
dev: true
/eslint-module-utils/2.7.1:
resolution: {integrity: sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==}
/eslint-module-utils/2.7.2:
resolution: {integrity: sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==}
engines: {node: '>=4'}
dependencies:
debug: 3.2.7
find-up: 2.1.0
pkg-dir: 2.0.0
dev: true
/eslint-plugin-import/2.25.3[email protected]:
resolution: {integrity: sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==}
/eslint-plugin-import/2.25.4[email protected]:
resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==}
engines: {node: '>=4'}
peerDependencies:
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
@ -3025,7 +2998,7 @@ packages:
doctrine: 2.1.0
eslint: 7.32.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.1
eslint-module-utils: 2.7.2
has: 1.0.3
is-core-module: 2.8.0
is-glob: 4.0.3
@ -3035,8 +3008,8 @@ packages:
tsconfig-paths: 3.12.0
dev: true
/eslint-plugin-import/2.25.[email protected].0:
resolution: {integrity: sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==}
/eslint-plugin-import/2.25.[email protected].0:
resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==}
engines: {node: '>=4'}
peerDependencies:
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
@ -3045,9 +3018,9 @@ packages:
array.prototype.flat: 1.2.5
debug: 2.6.9
doctrine: 2.1.0
eslint: 8.5.0
eslint: 8.6.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.1
eslint-module-utils: 2.7.2
has: 1.0.3
is-core-module: 2.8.0
is-glob: 4.0.3
@ -3066,13 +3039,13 @@ packages:
eslint: 7.32.0
dev: true
/eslint-plugin-react-hooks/4.3.0_eslint@8.5.0:
/eslint-plugin-react-hooks/4.3.0_eslint@8.6.0:
resolution: {integrity: sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==}
engines: {node: '>=10'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
dependencies:
eslint: 8.5.0
eslint: 8.6.0
dev: true
/eslint-plugin-react/[email protected]:
@ -3098,7 +3071,7 @@ packages:
string.prototype.matchall: 4.0.6
dev: true
/eslint-plugin-react/7.28.0_eslint@8.5.0:
/eslint-plugin-react/7.28.0_eslint@8.6.0:
resolution: {integrity: sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==}
engines: {node: '>=4'}
peerDependencies:
@ -3107,7 +3080,7 @@ packages:
array-includes: 3.1.4
array.prototype.flatmap: 1.2.5
doctrine: 2.1.0
eslint: 8.5.0
eslint: 8.6.0
estraverse: 5.3.0
jsx-ast-utils: 3.2.1
minimatch: 3.0.4
@ -3154,13 +3127,13 @@ packages:
eslint-visitor-keys: 2.1.0
dev: true
/eslint-utils/3.0.0_eslint@8.5.0:
/eslint-utils/3.0.0_eslint@8.6.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
dependencies:
eslint: 8.5.0
eslint: 8.6.0
eslint-visitor-keys: 2.1.0
dev: true
@ -3228,8 +3201,8 @@ packages:
- supports-color
dev: true
/eslint/8.5.0:
resolution: {integrity: sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg==}
/eslint/8.6.0:
resolution: {integrity: sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
@ -3243,9 +3216,9 @@ packages:
enquirer: 2.3.6
escape-string-regexp: 4.0.0
eslint-scope: 7.1.0
eslint-utils: 3.0.0_eslint@8.5.0
eslint-utils: 3.0.0_eslint@8.6.0
eslint-visitor-keys: 3.1.0
espree: 9.2.0
espree: 9.3.0
esquery: 1.4.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
@ -3284,8 +3257,8 @@ packages:
eslint-visitor-keys: 1.3.0
dev: true
/espree/9.2.0:
resolution: {integrity: sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==}
/espree/9.3.0:
resolution: {integrity: sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
acorn: 8.7.0
@ -3689,13 +3662,6 @@ packages:
resolution: {integrity: sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==}
dev: false
/import-cwd/3.0.0:
resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==}
engines: {node: '>=8'}
dependencies:
import-from: 3.0.0
dev: true
/import-fresh/3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@ -3704,13 +3670,6 @@ packages:
resolve-from: 4.0.0
dev: true
/import-from/3.0.0:
resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==}
engines: {node: '>=8'}
dependencies:
resolve-from: 5.0.0
dev: true
/imurmurhash/0.1.4:
resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=}
engines: {node: '>=0.8.19'}
@ -3953,7 +3912,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 17.0.5
'@types/node': 17.0.7
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@ -4179,7 +4138,7 @@ packages:
engines: {node: '>=8.6'}
dependencies:
braces: 3.0.2
picomatch: 2.3.0
picomatch: 2.3.1
dev: true
/mimic-response/2.1.0:
@ -4497,18 +4456,11 @@ packages:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
/picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/pkg-dir/2.0.0:
resolution: {integrity: sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=}
engines: {node: '>=4'}
dependencies:
find-up: 2.1.0
dev: true
/pkg-up/3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
@ -4524,8 +4476,8 @@ packages:
postcss: 8.4.5
dev: true
/postcss-load-config/3.1.0:
resolution: {integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==}
/postcss-load-config/3.1.1:
resolution: {integrity: sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==}
engines: {node: '>= 10'}
peerDependencies:
ts-node: '>=9.0.0'
@ -4533,7 +4485,6 @@ packages:
ts-node:
optional: true
dependencies:
import-cwd: 3.0.0
lilconfig: 2.0.4
yaml: 1.10.2
dev: true
@ -4731,8 +4682,8 @@ packages:
react: 17.0.2
dev: false
/react-i18next/11.15.2_80acb397921aff391f276c2217c76c9e:
resolution: {integrity: sha512-28WlZ6tr57RQoIM1x4/gTzvzh3Vvr+YeZwJHTOqV/sLXMFbR+xzjgSMdLnOCDxmlQ0irGN2uM+HT+mYK+3bg6g==}
/react-i18next/11.15.3_80acb397921aff391f276c2217c76c9e:
resolution: {integrity: sha512-RSUEM4So3Tu2JHV0JsZ5Yje+4nz66YViMfPZoywxOy0xyn3L7tE2CHvJ7Y9LUsrTU7vGmZ5bwb8PpjnkatdIxg==}
peerDependencies:
i18next: '>= 19.0.0'
react: '>= 16.8.0'
@ -4883,7 +4834,7 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
picomatch: 2.3.1
dev: true
/redux-thunk/[email protected]:
@ -4969,11 +4920,6 @@ packages:
engines: {node: '>=4'}
dev: true
/resolve-from/5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
dev: true
/resolve-protobuf-schema/2.1.0:
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
dependencies:
@ -5382,7 +5328,7 @@ packages:
object-hash: 2.2.0
postcss: 8.4.5
postcss-js: 3.0.3
postcss-load-config: 3.1.0
postcss-load-config: 3.1.1
postcss-nested: 5.0[email protected]
postcss-selector-parser: 6.0.8
postcss-value-parser: 4.2.0

10
src/App.tsx

@ -4,7 +4,6 @@ import { ErrorBoundary } from 'react-error-boundary';
import { FiBell } from 'react-icons/fi';
import { DeviceStatus } from '@app/components/menu/buttons/DeviceStatus';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Connection } from '@components/Connection';
import { MobileNavToggle } from '@components/menu/buttons/MobileNavToggle';
import { Notifications } from '@components/menu/buttons/Notifications';
@ -14,6 +13,8 @@ import { MobileNav } from '@components/menu/MobileNav';
import { Navigation } from '@components/menu/Navigation';
import { useRoute } from '@core/router';
import { requestNotificationPermission } from '@core/utils/notifications';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Messages } from '@pages/Messages';
import { Nodes } from '@pages/Nodes/Index';
import { NotFound } from '@pages/NotFound';
@ -21,6 +22,7 @@ import { Plugins } from '@pages/Plugins/Index';
import { Settings } from '@pages/settings/Index';
import { ErrorFallback } from './components/ErrorFallback';
import { MapboxProvider } from './components/MapBox/MapboxProvider';
import { addNotification, removeNotification } from './core/slices/appSlice';
export const App = (): JSX.Element => {
@ -86,7 +88,11 @@ export const App = (): JSX.Element => {
<div className="flex w-full bg-gray-100 md:shadow-xl md:overflow-hidden dark:bg-secondaryDark md:rounded-b-3xl">
<ErrorBoundary FallbackComponent={ErrorFallback}>
{route.name === 'messages' && <Messages />}
{route.name === 'nodes' && <Nodes />}
{route.name === 'nodes' && (
<MapboxProvider>
<Nodes />
</MapboxProvider>
)}
{route.name === 'plugins' && <Plugins />}
{route.name === 'settings' && <Settings />}
{route.name === false && <NotFound />}

3
src/components/Connection.tsx

@ -1,6 +1,5 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { BLE } from '@components/connection/BLE';
import { HTTP } from '@components/connection/HTTP';
import { Serial } from '@components/connection/Serial';
@ -12,6 +11,8 @@ import {
setConnectionParams,
setConnType,
} from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button, Card, Select } from '@meshtastic/components';
import { Types } from '@meshtastic/meshtasticjs';

161
src/components/Map/index.tsx

@ -1,22 +1,17 @@
import React from 'react';
import mapboxgl from 'mapbox-gl';
import { renderToString } from 'react-dom/server';
import { FaDirections, FaGlobeAfrica, FaMountain } from 'react-icons/fa';
import { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
import { useMapbox } from '@app/hooks/mapbox';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import {
setBearing,
setExaggeration,
setHillShade,
setLatLng,
setMapStyle,
setPitch,
setZoom,
} from '@core/slices/mapSlice';
import { Card, IconButton } from '@meshtastic/components';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { useMapbox } from '@hooks/useMapbox';
import { IconButton } from '@meshtastic/components';
import type { MapStyle } from './styles';
import { MapStyles } from './styles';
@ -24,63 +19,10 @@ import { MapStyles } from './styles';
export const Map = (): JSX.Element => {
const dispatch = useAppDispatch();
const darkMode = useAppSelector((state) => state.app.darkMode);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const mapState = useAppSelector((state) => state.map);
const [markers, setMarkers] = React.useState<
{ id: number; marker: mapboxgl.Marker }[]
>([]);
const mapRef = React.useRef<HTMLDivElement>(null);
const map = useMapbox(mapRef, mapState.accessToken, {
center: mapState.latLng,
zoom: mapState.zoom,
bearing: mapState.bearing,
pitch: mapState.pitch,
style: mapState.style.url,
});
const updateNodes = React.useCallback(() => {
nodes.map((node) => {
if (map?.loaded() && node.currentPosition) {
const existingMarker = markers.find(
(marker) => marker.id === node.number,
)?.marker;
const marker =
existingMarker ??
new mapboxgl.Marker({}).setLngLat([0, 0]).addTo(map);
marker
.setLngLat([
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
])
.setPopup(
new mapboxgl.Popup().setHTML(
renderToString(
<Card>
<div className="text-xl font-medium">
{node.user?.longName}
</div>
<ul>
<li>ID: {node.number}</li>
</ul>
</Card>,
),
),
);
const mapState = useAppSelector((state) => state.map);
if (!existingMarker) {
setMarkers((markers) => [
...markers,
{
id: node.number,
marker,
},
]);
}
}
});
}, [markers, map, nodes]);
const { ref } = useMapbox();
const ChangeMapStyle = React.useCallback(
(styleName: string, style: MapStyle) => {
@ -97,95 +39,6 @@ export const Map = (): JSX.Element => {
[dispatch, darkMode, mapState.style.title],
);
React.useEffect(() => {
map?.on('load', () => {
updateNodes();
});
map?.on('styledata', () => {
if (!map.getSource('mapbox-dem')) {
map.addSource('mapbox-dem', {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
});
}
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
});
map?.on('dragend', (e) => {
dispatch(setLatLng(e.target.getCenter()));
});
map?.on('zoomend', (e) => {
dispatch(setZoom(e.target.getZoom()));
});
map?.on('rotate', (e) => {
dispatch(setBearing(e.target.getBearing()));
});
map?.on('pitch', (e) => {
dispatch(setPitch(e.target.getPitch()));
});
}, [dispatch, map, updateNodes, mapState.exaggeration]);
React.useEffect(() => {
const center = map?.getCenter();
if (center !== mapState.latLng) {
map?.setCenter(mapState.latLng);
}
}, [map, mapState.latLng]);
/**
* Hill Shading
*/
React.useEffect(() => {
if (map?.loaded()) {
if (mapState.hillShade) {
map.addLayer(
{
id: 'hillshading',
source: 'mapbox-dem',
type: 'hillshade',
// insert below waterway-river-canal-shadow;
// where hillshading sits in the Mapbox Outdoors style
},
'waterway-river-canal-shadow',
);
} else {
map.removeLayer('hillshading');
}
}
}, [map, mapState.hillShade]);
/**
* Exaggeration
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
}
}, [map, mapState.exaggeration]);
/**
* Map Style
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setStyle(mapState.style.url);
}
}, [map, mapState.style]);
/**
* Markers
*/
React.useEffect(() => {
updateNodes();
}, [nodes, updateNodes]);
return (
<div className="relative flex w-full h-full">
<div className="fixed right-0 z-20 p-2 m-4 space-y-2 bg-white rounded-md shadow-md md:mx-10 dark:bg-primaryDark">
@ -232,7 +85,7 @@ export const Map = (): JSX.Element => {
<IconButton icon={<MdRadar />} />
</div>
<div className="flex w-full h-full">
<div className="flex-grow w-full h-full" ref={mapRef} />
<div className="flex-grow w-full h-full" ref={ref} />
</div>
</div>
);

224
src/components/MapBox/MapboxProvider.tsx

@ -0,0 +1,224 @@
import React from 'react';
import mapbox from 'mapbox-gl';
import { renderToString } from 'react-dom/server';
import { FiAirplay } from 'react-icons/fi';
import {
setBearing,
setLatLng,
setMapStyle,
setPitch,
setZoom,
} from '@core/slices/mapSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { useCreateMapbox } from '@hooks/useCreateMapbox';
import { Card } from '@meshtastic/components';
import { MapStyles } from '../Map/styles';
import { MapboxContext } from './mapboxContext';
export type MapboxProviderProps = {
children: React.ReactNode;
};
export const MapboxProvider = ({
children,
}: MapboxProviderProps): JSX.Element => {
const darkMode = useAppSelector((state) => state.app.darkMode);
const mapState = useAppSelector((state) => state.map);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const dispatch = useAppDispatch();
const ref = React.useRef<HTMLDivElement>(null);
const [markers, setMarkers] = React.useState<
{ id: number; marker: mapbox.Marker }[]
>([]);
const [markerElements, setMarkerElements] = React.useState<
{ id: number; element: JSX.Element; ref: React.RefObject<HTMLDivElement> }[]
>([]);
const map = useCreateMapbox({
ref,
accessToken:
'pk.eyJ1Ijoic2FjaGF3IiwiYSI6ImNrNW9meXozZjBsdW0zbHBjM2FnNnV6cmsifQ.3E4n8eFGD9ZOFo-XDVeZnQ',
options: {
center: mapState.latLng,
zoom: mapState.zoom,
bearing: mapState.bearing,
pitch: mapState.pitch,
style: mapState.style.url,
},
});
const updateNodes = React.useCallback(() => {
nodes.map((node) => {
if (map?.loaded() && node.currentPosition) {
const existingMarker = markers.find(
(marker) => marker.id === node.number,
)?.marker;
const tmpRef = React.createRef<HTMLDivElement>();
const markerElement = markerElements.find(
(element) => element.id === node.number,
) ?? {
element: (
<div ref={tmpRef}>
Test
<FiAirplay />
</div>
),
id: node.number,
ref: tmpRef,
};
console.log(markerElement);
const marker =
existingMarker ??
new mapbox.Marker(markerElement.ref.current ?? undefined, {})
.setLngLat([0, 0])
.addTo(map);
marker
.setLngLat([
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
])
.setPopup(
new mapbox.Popup().setHTML(
renderToString(
<Card>
<div className="p-2">
<div className="text-xl font-medium">
{node.user?.longName}
</div>
<ul>
<li>ID: {node.number}</li>
</ul>
</div>
</Card>,
),
),
);
if (!existingMarker) {
setMarkers((markers) => [
...markers,
{
id: node.number,
marker,
},
]);
setMarkerElements((markerElements) => [
...markerElements,
markerElement,
]);
}
}
});
}, [markers, markerElements, map, nodes]);
React.useEffect(() => {
map?.on('load', () => {
updateNodes();
});
map?.on('styledata', () => {
if (!map.getSource('mapbox-dem')) {
map.addSource('mapbox-dem', {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
});
}
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
});
map?.on('dragend', (e) => {
dispatch(setLatLng(e.target.getCenter()));
});
map?.on('zoomend', (e) => {
dispatch(setZoom(e.target.getZoom()));
});
map?.on('rotate', (e) => {
dispatch(setBearing(e.target.getBearing()));
});
map?.on('pitch', (e) => {
dispatch(setPitch(e.target.getPitch()));
});
}, [dispatch, map, updateNodes, mapState.exaggeration]);
React.useEffect(() => {
const center = map?.getCenter();
if (center !== mapState.latLng) {
map?.setCenter(mapState.latLng);
}
}, [map, mapState.latLng]);
React.useEffect(() => {
if (['Light', 'Dark'].includes(mapState.style.title)) {
dispatch(setMapStyle(darkMode ? MapStyles.Dark : MapStyles.Light));
}
}, [dispatch, darkMode, mapState.style.title]);
/**
* Hill Shading
*/
React.useEffect(() => {
if (map?.loaded()) {
if (mapState.hillShade) {
map.addLayer(
{
id: 'hillshading',
source: 'mapbox-dem',
type: 'hillshade',
// insert below waterway-river-canal-shadow;
// where hillshading sits in the Mapbox Outdoors style
},
'waterway-river-canal-shadow',
);
} else {
map.removeLayer('hillshading');
}
}
}, [map, mapState.hillShade]);
/**
* Exaggeration
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
}
}, [map, mapState.exaggeration]);
/**
* Map Style
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setStyle(mapState.style.url);
}
}, [map, mapState.style]);
/**
* Markers
*/
React.useEffect(() => {
updateNodes();
}, [nodes, updateNodes]);
return (
<MapboxContext.Provider value={{ map, ref }}>
{children}
</MapboxContext.Provider>
);
};

12
src/components/MapBox/mapboxContext.ts

@ -0,0 +1,12 @@
import React from 'react';
import type mapbox from 'mapbox-gl';
export interface MapboxContextValue {
ref: React.Ref<HTMLDivElement>;
map?: mapbox.Map;
}
export const MapboxContext = React.createContext<MapboxContextValue>(
{} as MapboxContextValue
);

3
src/components/chat/MessageBar.tsx

@ -3,9 +3,10 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { FiSend } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { connection } from '@core/connection';
import { ackMessage } from '@core/slices/meshtasticSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { IconButton, Input, Select } from '@meshtastic/components';
export interface MessageBarProps {

4
src/components/connection/BLE.tsx

@ -10,7 +10,7 @@ import { Button, IconButton } from '@meshtastic/components';
export const BLE = (): JSX.Element => {
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
const { register, handleSubmit, control } = useForm<{
const { handleSubmit } = useForm<{
device?: BluetoothDevice;
}>();
@ -23,7 +23,7 @@ export const BLE = (): JSX.Element => {
void updateBleDeviceList();
}, [updateBleDeviceList]);
const onSubmit = handleSubmit(async (data) => {
const onSubmit = handleSubmit(async () => {
await setConnection(connType.BLE);
});

2
src/components/connection/HTTP.tsx

@ -2,9 +2,9 @@ import type React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useAppDispatch } from '@app/hooks/redux.js';
import { connectionUrl, setConnection } from '@core/connection';
import { connType, setConnectionParams } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { Button, Checkbox, Input, Select } from '@meshtastic/components';
export const HTTP = (): JSX.Element => {

6
src/components/connection/Serial.tsx

@ -3,16 +3,16 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import { FiCheck } from 'react-icons/fi';
import { useAppDispatch } from '@app/hooks/redux';
import { serial, setConnection } from '@core/connection';
import { connType, setConnectionParams } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { Button, IconButton } from '@meshtastic/components';
export const Serial = (): JSX.Element => {
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
const dispatch = useAppDispatch();
const { register, handleSubmit, control } = useForm<{
const { handleSubmit } = useForm<{
device?: SerialPort;
}>();
@ -25,7 +25,7 @@ export const Serial = (): JSX.Element => {
void updateSerialDeviceList();
}, [updateSerialDeviceList]);
const onSubmit = handleSubmit(async (data) => {
const onSubmit = handleSubmit(async () => {
await setConnection(connType.SERIAL);
});

2
src/components/generic/Modal.tsx

@ -1,7 +1,7 @@
import type React from 'react';
import { useAppSelector } from '@app/hooks/redux';
import { Dialog } from '@headlessui/react';
import { useAppSelector } from '@hooks/useAppSelector';
type DefaultDivProps = JSX.IntrinsicElements['div'];

2
src/components/generic/form/BitwiseSelect.tsx

@ -5,7 +5,7 @@ import type { Theme } from 'react-select';
import ReactSelect from 'react-select';
import { bitwiseDecode, bitwiseEncode } from '@app/core/utils/bitwise';
import { useAppSelector } from '@app/hooks/redux.js';
import { useAppSelector } from '@hooks/useAppSelector';
import { Label } from './Label';

3
src/components/menu/MobileNav.tsx

@ -1,8 +1,9 @@
import type React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Drawer } from '@components/generic/Drawer';
import { closeMobileNav } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Logo } from './Logo';
import { Navigation } from './Navigation';

3
src/components/menu/buttons/DeviceStatus.tsx

@ -2,8 +2,9 @@ import type React from 'react';
import { FiBluetooth, FiCpu, FiWifi } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { connType, openConnectionModal } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button } from '@meshtastic/components';
import { Types } from '@meshtastic/meshtasticjs';

3
src/components/menu/buttons/MobileNavToggle.tsx

@ -3,10 +3,9 @@ import type React from 'react';
import { FiMenu } from 'react-icons/fi';
import { openMobileNav } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { IconButton } from '@meshtastic/components';
import { useAppDispatch } from '../../../hooks/redux';
export const MobileNavToggle = (): JSX.Element => {
const dispatch = useAppDispatch();

2
src/components/menu/buttons/Notifications.tsx

@ -2,9 +2,9 @@ import React from 'react';
import { FiBell, FiX } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { shift, useFloating } from '@floating-ui/react-dom';
import { Popover } from '@headlessui/react';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button, IconButton } from '@meshtastic/components';
export const Notifications = (): JSX.Element => {

3
src/components/menu/buttons/ThemeToggle.tsx

@ -2,8 +2,9 @@ import type React from 'react';
import { FiMoon, FiSun } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { setDarkModeEnabled } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { IconButton } from '@meshtastic/components';
export const ThemeToggle = (): JSX.Element => {

2
src/components/templates/PageLayout.tsx

@ -2,11 +2,11 @@ import React from 'react';
import { FiXCircle } from 'react-icons/fi';
import { useBreakpoint } from '@app/hooks/breakpoint';
import { Drawer } from '@components/generic/Drawer';
import type { SidebarItemProps } from '@components/generic/SidebarItem';
import { SidebarItem } from '@components/generic/SidebarItem';
import { Tab } from '@headlessui/react';
import { useBreakpoint } from '@hooks/useBreakpoint';
import { IconButton } from '@meshtastic/components';
export interface PageLayoutProps {

2
src/core/connection.ts

@ -23,7 +23,7 @@ import {
Types,
} from '@meshtastic/meshtasticjs';
import { showNotification } from './utils/notifications.js';
import { showNotification } from './utils/notifications';
type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection;

28
src/hooks/mapbox.ts

@ -1,28 +0,0 @@
import 'mapbox-gl/dist/mapbox-gl.css';
import React from 'react';
import mapboxgl from 'mapbox-gl';
type AccessToken = string;
export function useMapbox(
ref: React.RefObject<HTMLDivElement>,
accessToken: AccessToken,
options?: Partial<mapboxgl.MapboxOptions>,
): mapboxgl.Map | undefined {
const [mapInstance, setMapInstance] = React.useState<mapboxgl.Map>();
React.useEffect(() => {
const container = ref.current;
if (mapInstance || !container) {
return;
}
mapboxgl.accessToken = accessToken;
const mergedOptions = { container, ...options };
const map = new mapboxgl.Map(mergedOptions);
setMapInstance(map);
}, [ref, accessToken, options, mapInstance]);
return mapInstance;
}

9
src/hooks/redux.ts

@ -1,9 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import type { TypedUseSelectorHook } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import type { AppDispatch, RootState } from '@core/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

7
src/hooks/useAppDispatch.ts

@ -0,0 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useDispatch } from 'react-redux';
import type { AppDispatch } from '@core/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();

6
src/hooks/useAppSelector.ts

@ -0,0 +1,6 @@
import type { TypedUseSelectorHook } from 'react-redux';
import { useSelector } from 'react-redux';
import type { RootState } from '@core/store';
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

0
src/hooks/breakpoint.ts → src/hooks/useBreakpoint.ts

36
src/hooks/useCreateMapbox.ts

@ -0,0 +1,36 @@
import 'mapbox-gl/dist/mapbox-gl.css';
import React from 'react';
import mapboxgl from 'mapbox-gl';
type AccessToken = string;
export interface useMapboxProps {
ref: React.RefObject<HTMLDivElement>;
accessToken: AccessToken;
options?: Partial<mapboxgl.MapboxOptions>;
}
export function useCreateMapbox({
ref,
accessToken,
options,
}: useMapboxProps): mapboxgl.Map | undefined {
const [mapInstance, setMapInstance] = React.useState<mapboxgl.Map>();
React.useEffect(() => {
const container = ref.current as HTMLDivElement;
if (mapInstance || !container) {
return;
}
mapboxgl.accessToken = accessToken;
const map = new mapboxgl.Map({
container,
antialias: true,
...options,
});
setMapInstance(map);
}, []);
return mapInstance;
}

8
src/hooks/useMapbox.ts

@ -0,0 +1,8 @@
import React from 'react';
import type { MapboxContextValue } from '@components/MapBox/mapboxContext';
import { MapboxContext } from '@components/MapBox/mapboxContext';
export const useMapbox = (): MapboxContextValue => {
return React.useContext(MapboxContext);
};

12
src/index.css

@ -21,3 +21,15 @@
::-webkit-scrollbar-thumb:hover {
background-color: #a8bbbf;
}
.mapboxgl-popup-close-button,
.mapboxgl-popup-tip {
display: none;
}
.mapboxgl-popup-content {
background: transparent !important;
border-radius: 0 !important;
box-sizing: unset !important;
padding: 0 !important;
}

3
src/pages/Messages.tsx

@ -4,11 +4,10 @@ import { FiHash } from 'react-icons/fi';
import { Message } from '@components/chat/Message';
import { MessageBar } from '@components/chat/MessageBar';
import { useAppSelector } from '@hooks/useAppSelector';
import { Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
import { useAppSelector } from '../hooks/redux';
export const Messages = (): JSX.Element => {
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const channels = useAppSelector((state) => state.meshtastic.radio.channels);

5
src/pages/Nodes/Index.tsx

@ -2,10 +2,10 @@ import React from 'react';
import { FiXCircle } from 'react-icons/fi';
import { useBreakpoint } from '@app/hooks/breakpoint';
import { useAppSelector } from '@app/hooks/redux';
import { Drawer } from '@components/generic/Drawer';
import { Map } from '@components/Map';
import { useAppSelector } from '@hooks/useAppSelector';
import { useBreakpoint } from '@hooks/useBreakpoint';
import { IconButton } from '@meshtastic/components';
import { NodeCard } from './NodeCard';
@ -60,6 +60,7 @@ export const Nodes = (): JSX.Element => {
<NodeCard key={node.number} node={node} />
))}
</Drawer>
<Map />
</div>
);

31
src/pages/Nodes/NodeCard.tsx

@ -16,10 +16,9 @@ import {
} from 'react-icons/md';
import TimeAgo from 'timeago-react';
import { setLatLng } from '@app/core/slices/mapSlice.js';
import { useAppDispatch } from '@app/hooks/redux.js';
import type { Node } from '@core/slices/meshtasticSlice';
import { Disclosure } from '@headlessui/react';
import { useMapbox } from '@hooks/useMapbox';
import { IconButton } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
@ -32,9 +31,10 @@ export interface NodeCardProps {
}
export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
const dispatch = useAppDispatch();
const [snrAverage, setSnrAverage] = React.useState(0);
const [satsAverage, setSatsAverage] = React.useState(0);
const { map } = useMapbox();
React.useEffect(() => {
setSnrAverage(
node.snr
@ -119,17 +119,26 @@ export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
</div>
<IconButton
disabled={PositionConfidence === 'none'}
onClick={() => {
onClick={(): void => {
if (PositionConfidence !== 'none' && node.currentPosition) {
dispatch(
setLatLng(
new mapboxgl.LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
),
map?.flyTo({
center: new mapboxgl.LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
),
);
zoom: 16,
});
}
// if (PositionConfidence !== 'none' && node.currentPosition) {
// dispatch(
// setLatLng(
// new mapboxgl.LngLat(
// node.currentPosition.longitudeI / 1e7,
// node.currentPosition.latitudeI / 1e7,
// ),
// ),
// );
// }
}}
icon={
PositionConfidence === 'high' ? (

2
src/pages/Plugins/ExternalNotification.tsx

@ -4,9 +4,9 @@ import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { FormFooter } from '@app/components/FormFooter';
import { useAppSelector } from '@app/hooks/redux';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';

2
src/pages/Plugins/RangeTest.tsx

@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';

2
src/pages/Plugins/Serial.tsx

@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';

2
src/pages/Plugins/StoreAndForward.tsx

@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';

2
src/pages/settings/Channels.tsx

@ -4,13 +4,13 @@ import { useForm, useWatch } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { Channel } from '@components/Channel';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { Loading } from '@components/generic/Loading';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,

2
src/pages/settings/Index.tsx

@ -10,7 +10,7 @@ import {
FiZap,
} from 'react-icons/fi';
import type { SidebarItemProps } from '@app/components/generic/SidebarItem.js';
import type { SidebarItemProps } from '@app/components/generic/SidebarItem';
import { PageLayout } from '@components/templates/PageLayout';
import { Channels } from './Channels';

2
src/pages/settings/Position.tsx

@ -6,12 +6,12 @@ import JSONPretty from 'react-json-pretty';
import type { Theme } from 'react-select';
import ReactSelect from 'react-select';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { Label } from '@components/generic/form/Label';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,

2
src/pages/settings/Power.tsx

@ -4,11 +4,11 @@ import { useForm } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';

2
src/pages/settings/Radio.tsx

@ -4,11 +4,11 @@ import { useForm } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';

2
src/pages/settings/User.tsx

@ -5,11 +5,11 @@ import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { base16 } from 'rfc4648';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,

2
src/pages/settings/WiFi.tsx

@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';

1
tsconfig.json

@ -21,6 +21,7 @@
"@app/*": ["./src/*"],
"@pages/*": ["./src/pages/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@core/*": ["./src/core/*"]
},
"importHelpers": true,

1
vite.config.ts

@ -67,6 +67,7 @@ export default defineConfig({
'@app': path.resolve(__dirname, './src'),
'@pages': path.resolve(__dirname, './src/pages'),
'@components': path.resolve(__dirname, './src/components'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@core': path.resolve(__dirname, './src/core'),
},
},

Loading…
Cancel
Save