From 0cea0b21657f9835d80a1dec526c94277085a3f1 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sun, 21 Nov 2021 00:28:24 +1100 Subject: [PATCH] Internal rework --- package.json | 13 +- pnpm-lock.yaml | 290 +++++++++--------- src/App.tsx | 151 +-------- src/components/Connection.tsx | 269 ++++++++++++++++ src/components/LoraConfig.tsx | 9 +- src/components/chat/MessageBar.tsx | 37 ++- src/components/generic/Blur.tsx | 4 +- src/components/generic/Button.tsx | 6 +- src/components/generic/Card.tsx | 2 +- src/components/generic/Modal.tsx | 37 +++ src/components/generic/StatCard.tsx | 2 +- ...iceStatusDropdown.tsx => DeviceStatus.tsx} | 38 ++- src/components/pwa/ReloadPrompt.tsx | 4 +- src/core/connection.ts | 103 +++++++ src/core/slices/appSlice.ts | 10 + src/core/slices/meshtasticSlice.ts | 151 +++++---- src/index.tsx | 5 +- src/pages/Messages.tsx | 34 +- src/pages/Nodes/Index.tsx | 17 +- src/pages/Nodes/Node.tsx | 40 +-- src/pages/Plugins/ExternalNotification.tsx | 21 +- src/pages/Plugins/Files.tsx | 22 +- src/pages/Plugins/RangeTest.tsx | 18 +- src/pages/Plugins/Serial.tsx | 21 +- src/pages/settings/Channels.tsx | 14 +- src/pages/settings/Connection.tsx | 271 ---------------- src/pages/settings/Index.tsx | 8 - src/pages/settings/Interface.tsx | 2 +- src/pages/settings/Position.tsx | 12 +- src/pages/settings/Power.tsx | 14 +- src/pages/settings/Radio.tsx | 12 +- src/pages/settings/User.tsx | 50 +-- src/pages/settings/WiFi.tsx | 14 +- todo.txt | 5 +- 34 files changed, 903 insertions(+), 803 deletions(-) create mode 100644 src/components/Connection.tsx create mode 100644 src/components/generic/Modal.tsx rename src/components/menu/buttons/{DeviceStatusDropdown.tsx => DeviceStatus.tsx} (56%) delete mode 100644 src/pages/settings/Connection.tsx diff --git a/package.json b/package.json index 8501a68c..679864e3 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,15 @@ }, "dependencies": { "@headlessui/react": "^1.4.2", - "@meshtastic/meshtasticjs": "^0.6.28", + "@meshtastic/meshtasticjs": "^0.6.29", "@reduxjs/toolkit": "^1.6.2", "boring-avatars": "^1.5.8", - "i18next": "^21.5.1", + "i18next": "^21.5.2", "i18next-browser-languagedetector": "^6.1.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-file-icon": "^1.1.0", - "react-hook-form": "^7.19.5", + "react-hook-form": "^7.20.2", "react-i18next": "^11.14.2", "react-icons": "^4.3.1", "react-json-pretty": "^2.2.0", @@ -46,19 +46,20 @@ "babel-plugin-module-resolver": "^4.1.0", "eslint": "8.2.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-react": "^7.27.0", + "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "gzipper": "^6.0.0", "postcss": "^8.3.11", "prettier": "^2.4.1", "tailwindcss": "^3.0.0-alpha.2", "tar": "^6.1.11", - "typescript": "^4.4.4", + "typescript": "^4.5.2", "vite": "^2.6.14", - "vite-plugin-pwa": "^0.11.5", + "vite-plugin-pwa": "^0.11.6", "workbox-window": "^6.4.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95aff183..3554e977 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,7 +2,7 @@ lockfileVersion: 5.3 specifiers: '@headlessui/react': ^1.4.2 - '@meshtastic/meshtasticjs': ^0.6.28 + '@meshtastic/meshtasticjs': ^0.6.29 '@reduxjs/toolkit': ^1.6.2 '@types/react': ^17.0.35 '@types/react-dom': ^17.0.11 @@ -18,20 +18,21 @@ specifiers: boring-avatars: ^1.5.8 eslint: 8.2.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-react: ^7.27.0 + eslint-plugin-react: ^7.27.1 eslint-plugin-react-hooks: ^4.3.0 gzipper: ^6.0.0 - i18next: ^21.5.1 + i18next: ^21.5.2 i18next-browser-languagedetector: ^6.1.2 postcss: ^8.3.11 prettier: ^2.4.1 react: ^17.0.2 react-dom: ^17.0.2 react-file-icon: ^1.1.0 - react-hook-form: ^7.19.5 + react-hook-form: ^7.20.2 react-i18next: ^11.14.2 react-icons: ^4.3.1 react-json-pretty: ^2.2.0 @@ -42,24 +43,24 @@ specifiers: tar: ^6.1.11 timeago-react: ^3.0.4 type-route: ^0.6.0 - typescript: ^4.4.4 + typescript: ^4.5.2 use-breakpoint: ^2.0.2 vite: ^2.6.14 - vite-plugin-pwa: ^0.11.5 + vite-plugin-pwa: ^0.11.6 workbox-window: ^6.4.1 dependencies: '@headlessui/react': 1.4.2_react-dom@17.0.2+react@17.0.2 - '@meshtastic/meshtasticjs': 0.6.28 + '@meshtastic/meshtasticjs': 0.6.29 '@reduxjs/toolkit': 1.6.2_react-redux@7.2.6+react@17.0.2 boring-avatars: 1.5.8 - i18next: 21.5.1 + i18next: 21.5.2 i18next-browser-languagedetector: 6.1.2 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 react-file-icon: 1.1.0_react-dom@17.0.2+react@17.0.2 - react-hook-form: 7.19.5_react@17.0.2 - react-i18next: 11.14.2_i18next@21.5.1+react@17.0.2 + react-hook-form: 7.20.2_react@17.0.2 + react-i18next: 11.14.2_i18next@21.5.2+react@17.0.2 react-icons: 4.3.1_react@17.0.2 react-json-pretty: 2.2.0_react-dom@17.0.2+react@17.0.2 react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2 @@ -75,38 +76,39 @@ devDependencies: '@types/react-file-icon': 1.0.1 '@types/w3c-web-serial': 1.0.2 '@types/web-bluetooth': 0.0.11 - '@typescript-eslint/eslint-plugin': 5.4.0_b983626bd16070d34b18187cb6bde052 - '@typescript-eslint/parser': 5.4.0_eslint@8.2.0+typescript@4.4.4 - '@verypossible/eslint-config': 1.6.1_typescript@4.4.4 + '@typescript-eslint/eslint-plugin': 5.4.0_d6f2571581882eb2d6c9d9867e002185 + '@typescript-eslint/parser': 5.4.0_eslint@8.2.0+typescript@4.5.2 + '@verypossible/eslint-config': 1.6.1_typescript@4.5.2 '@vitejs/plugin-react': 1.0.9 autoprefixer: 10.4.0_postcss@8.3.11 babel-plugin-module-resolver: 4.1.0 eslint: 8.2.0 eslint-config-prettier: 8.3.0_eslint@8.2.0 + eslint-import-resolver-alias: 1.1.2_eslint-plugin-import@2.25.3 eslint-import-resolver-babel-module: 5.3.1_e51044130ac762fd207a8cd2109b5344 eslint-import-resolver-typescript: 2.5.0_6e04a54c7bcd7530b1a4c2da0aa486b1 eslint-plugin-import: 2.25.3_eslint@8.2.0 - eslint-plugin-react: 7.27.0_eslint@8.2.0 + eslint-plugin-react: 7.27.1_eslint@8.2.0 eslint-plugin-react-hooks: 4.3.0_eslint@8.2.0 gzipper: 6.0.0 postcss: 8.3.11 prettier: 2.4.1 tailwindcss: 3.0.0-alpha.2_0c54bdadaf9d9c9c6c134cb2c6c061a3 tar: 6.1.11 - typescript: 4.4.4 + typescript: 4.5.2 vite: 2.6.14 - vite-plugin-pwa: 0.11.5_vite@2.6.14 + vite-plugin-pwa: 0.11.6_vite@2.6.14 workbox-window: 6.4.1 packages: - /@apideck/better-ajv-errors/0.2.7_ajv@8.8.0: + /@apideck/better-ajv-errors/0.2.7_ajv@8.8.1: resolution: {integrity: sha512-J2dW+EHYudbwI7MGovcHWLBrxasl21uuroc2zT8bH2RxYuv2g5GqsO5jcKUZz4LaMST45xhKjVuyRYkhcWyMhA==} engines: {node: '>=10'} peerDependencies: ajv: '>=8' dependencies: - ajv: 8.8.0 + ajv: 8.8.1 json-schema: 0.3.0 jsonpointer: 5.0.0 leven: 3.1.0 @@ -125,8 +127,8 @@ packages: '@babel/highlight': 7.16.0 dev: true - /@babel/compat-data/7.16.0: - resolution: {integrity: sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==} + /@babel/compat-data/7.16.4: + resolution: {integrity: sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==} engines: {node: '>=6.9.0'} dev: true @@ -139,7 +141,7 @@ packages: '@babel/helper-compilation-targets': 7.16.3_@babel+core@7.16.0 '@babel/helper-module-transforms': 7.16.0 '@babel/helpers': 7.16.3 - '@babel/parser': 7.16.3 + '@babel/parser': 7.16.4 '@babel/template': 7.16.0 '@babel/traverse': 7.16.3 '@babel/types': 7.16.0 @@ -183,7 +185,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.16.0 + '@babel/compat-data': 7.16.4 '@babel/core': 7.16.0 '@babel/helper-validator-option': 7.14.5 browserslist: 4.18.1 @@ -218,8 +220,8 @@ packages: regexpu-core: 4.8.0 dev: true - /@babel/helper-define-polyfill-provider/0.2.4_@babel+core@7.16.0: - resolution: {integrity: sha512-OrpPZ97s+aPi6h2n1OXzdhVis1SGSsMU2aMHgLcOKfsp4/v1NWpx3CWT3lBj5eeBq9cDkPkh+YCfdF7O12uNDQ==} + /@babel/helper-define-polyfill-provider/0.3.0_@babel+core@7.16.0: + resolution: {integrity: sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==} peerDependencies: '@babel/core': ^7.4.0-0 dependencies: @@ -308,8 +310,8 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-remap-async-to-generator/7.16.0: - resolution: {integrity: sha512-MLM1IOMe9aQBqMWxcRw8dcb9jlM86NIw7KA0Wri91Xkfied+dE0QuBFSBjMNvqzmS0OSIDsMNC24dBEkPUi7ew==} + /@babel/helper-remap-async-to-generator/7.16.4: + resolution: {integrity: sha512-vGERmmhR+s7eH5Y/cp8PCVzj4XEjerq8jooMfxFdA5xVtAk9Sh4AQsrWgiErUEBjtGrBtOFKDUcWQFW4/dFwMA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-annotate-as-pure': 7.16.0 @@ -394,8 +396,8 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/parser/7.16.3: - resolution: {integrity: sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==} + /@babel/parser/7.16.4: + resolution: {integrity: sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==} engines: {node: '>=6.0.0'} hasBin: true dev: true @@ -422,15 +424,15 @@ packages: '@babel/plugin-proposal-optional-chaining': 7.16.0_@babel+core@7.16.0 dev: true - /@babel/plugin-proposal-async-generator-functions/7.16.0_@babel+core@7.16.0: - resolution: {integrity: sha512-nyYmIo7ZqKsY6P4lnVmBlxp9B3a96CscbLotlsNuktMHahkDwoPYEjXrZHU0Tj844Z9f1IthVxQln57mhkcExw==} + /@babel/plugin-proposal-async-generator-functions/7.16.4_@babel+core@7.16.0: + resolution: {integrity: sha512-/CUekqaAaZCQHleSK/9HajvcD/zdnJiKRiuUFq8ITE+0HsPzquf53cpFiqAwl/UfmJbR6n5uGPQSPdrmKOvHHg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 - '@babel/helper-remap-async-to-generator': 7.16.0 + '@babel/helper-remap-async-to-generator': 7.16.4 '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.16.0 transitivePeerDependencies: - supports-color @@ -535,7 +537,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.16.0 + '@babel/compat-data': 7.16.4 '@babel/core': 7.16.0 '@babel/helper-compilation-targets': 7.16.3_@babel+core@7.16.0 '@babel/helper-plugin-utils': 7.14.5 @@ -763,7 +765,7 @@ packages: '@babel/core': 7.16.0 '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 - '@babel/helper-remap-async-to-generator': 7.16.0 + '@babel/helper-remap-async-to-generator': 7.16.4 transitivePeerDependencies: - supports-color dev: true @@ -1146,20 +1148,20 @@ packages: '@babel/helper-plugin-utils': 7.14.5 dev: true - /@babel/preset-env/7.16.0_@babel+core@7.16.0: - resolution: {integrity: sha512-cdTu/W0IrviamtnZiTfixPfIncr2M1VqRrkjzZWlr1B4TVYimCFK5jkyOdP4qw2MrlKHi+b3ORj6x8GoCew8Dg==} + /@babel/preset-env/7.16.4_@babel+core@7.16.0: + resolution: {integrity: sha512-v0QtNd81v/xKj4gNKeuAerQ/azeNn/G1B1qMLeXOcV8+4TWlD2j3NV1u8q29SDFBXx/NBq5kyEAO+0mpRgacjA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.16.0 + '@babel/compat-data': 7.16.4 '@babel/core': 7.16.0 '@babel/helper-compilation-targets': 7.16.3_@babel+core@7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/helper-validator-option': 7.14.5 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.2_@babel+core@7.16.0 '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.16.0_@babel+core@7.16.0 - '@babel/plugin-proposal-async-generator-functions': 7.16.0_@babel+core@7.16.0 + '@babel/plugin-proposal-async-generator-functions': 7.16.4_@babel+core@7.16.0 '@babel/plugin-proposal-class-properties': 7.16.0_@babel+core@7.16.0 '@babel/plugin-proposal-class-static-block': 7.16.0_@babel+core@7.16.0 '@babel/plugin-proposal-dynamic-import': 7.16.0_@babel+core@7.16.0 @@ -1222,9 +1224,9 @@ packages: '@babel/plugin-transform-unicode-regex': 7.16.0_@babel+core@7.16.0 '@babel/preset-modules': 0.1.5_@babel+core@7.16.0 '@babel/types': 7.16.0 - babel-plugin-polyfill-corejs2: 0.2.3_@babel+core@7.16.0 - babel-plugin-polyfill-corejs3: 0.3.0_@babel+core@7.16.0 - babel-plugin-polyfill-regenerator: 0.2.3_@babel+core@7.16.0 + babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.16.0 + babel-plugin-polyfill-corejs3: 0.4.0_@babel+core@7.16.0 + babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.16.0 core-js-compat: 3.19.1 semver: 6.3.0 transitivePeerDependencies: @@ -1255,7 +1257,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.16.0 - '@babel/parser': 7.16.3 + '@babel/parser': 7.16.4 '@babel/types': 7.16.0 dev: true @@ -1268,7 +1270,7 @@ packages: '@babel/helper-function-name': 7.16.0 '@babel/helper-hoist-variables': 7.16.0 '@babel/helper-split-export-declaration': 7.16.0 - '@babel/parser': 7.16.3 + '@babel/parser': 7.16.4 '@babel/types': 7.16.0 debug: 4.3.2 globals: 11.12.0 @@ -1355,8 +1357,8 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@meshtastic/meshtasticjs/0.6.28: - resolution: {integrity: sha512-yoFzIM+fktvYqgRr/IVA41h4JMEv5GJue/xtxWNhWweEX6fiPoy+MoGeDkwU2dRyNzzZ03RLa6xNXqYxiHRlLA==} + /@meshtastic/meshtasticjs/0.6.29: + resolution: {integrity: sha512-XcfHTGlWBLgdfXCK81U/ilDpepyId/OEvXRNiEerg40m9pD7FaNlqPnAKRhvoeOl56VQdKtCpsiWJdeknSI4tw==} dependencies: '@protobuf-ts/runtime': 2.0.7 sub-events: 1.8.9 @@ -1403,7 +1405,7 @@ packages: react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2 redux: 4.1.2 redux-thunk: 2.4.0_redux@4.1.2 - reselect: 4.1.3 + reselect: 4.1.4 dev: false /@rollup/plugin-babel/5.3.0_@babel+core@7.16.0+rollup@2.60.0: @@ -1496,8 +1498,8 @@ packages: resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=} dev: true - /@types/node/16.11.7: - resolution: {integrity: sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==} + /@types/node/16.11.9: + resolution: {integrity: sha512-MKmdASMf3LtPzwLyRrFjtFFZ48cMf8jmX5VRYrDQiJa8Ybu5VAmkqBWqKU8fdCwD8ysw4mQ9nrEHvzg6gunR7A==} dev: true /@types/parse-json/4.0.0: @@ -1538,7 +1540,7 @@ packages: /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 16.11.7 + '@types/node': 16.11.9 dev: true /@types/scheduler/0.16.2: @@ -1556,7 +1558,7 @@ packages: resolution: {integrity: sha512-2CF3Kk2Rcvg/c2QzO7mXUhY7eL9CC3aKzrF+dNWNmp7Q8bmlvjmUM1nFPMSngawdJ+CcIdu8eJlQRytBgAZR9w==} dev: true - /@typescript-eslint/eslint-plugin/4.33.0_cc617358c89d3f38c52462f6d809db4c: + /@typescript-eslint/eslint-plugin/4.33.0_d00b196ac5df1286ea7e45797bebddbc: resolution: {integrity: sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -1567,8 +1569,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/experimental-utils': 4.33.0_eslint@7.32.0+typescript@4.4.4 - '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.4.4 + '@typescript-eslint/experimental-utils': 4.33.0_eslint@7.32.0+typescript@4.5.2 + '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.5.2 '@typescript-eslint/scope-manager': 4.33.0 debug: 4.3.2 eslint: 7.32.0 @@ -1576,13 +1578,13 @@ packages: ignore: 5.1.9 regexpp: 3.2.0 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.4.4 - typescript: 4.4.4 + tsutils: 3.21.0_typescript@4.5.2 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.4.0_b983626bd16070d34b18187cb6bde052: + /@typescript-eslint/eslint-plugin/5.4.0_d6f2571581882eb2d6c9d9867e002185: resolution: {integrity: sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1593,8 +1595,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/experimental-utils': 5.4.0_eslint@8.2.0+typescript@4.4.4 - '@typescript-eslint/parser': 5.4.0_eslint@8.2.0+typescript@4.4.4 + '@typescript-eslint/experimental-utils': 5.4.0_eslint@8.2.0+typescript@4.5.2 + '@typescript-eslint/parser': 5.4.0_eslint@8.2.0+typescript@4.5.2 '@typescript-eslint/scope-manager': 5.4.0 debug: 4.3.2 eslint: 8.2.0 @@ -1602,13 +1604,13 @@ packages: ignore: 5.1.9 regexpp: 3.2.0 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.4.4 - typescript: 4.4.4 + tsutils: 3.21.0_typescript@4.5.2 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/experimental-utils/4.33.0_eslint@7.32.0+typescript@4.4.4: + /@typescript-eslint/experimental-utils/4.33.0_eslint@7.32.0+typescript@4.5.2: resolution: {integrity: sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -1617,7 +1619,7 @@ packages: '@types/json-schema': 7.0.9 '@typescript-eslint/scope-manager': 4.33.0 '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.4.4 + '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.5.2 eslint: 7.32.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@7.32.0 @@ -1626,7 +1628,7 @@ packages: - typescript dev: true - /@typescript-eslint/experimental-utils/5.4.0_eslint@8.2.0+typescript@4.4.4: + /@typescript-eslint/experimental-utils/5.4.0_eslint@8.2.0+typescript@4.5.2: resolution: {integrity: sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1635,7 +1637,7 @@ packages: '@types/json-schema': 7.0.9 '@typescript-eslint/scope-manager': 5.4.0 '@typescript-eslint/types': 5.4.0 - '@typescript-eslint/typescript-estree': 5.4.0_typescript@4.4.4 + '@typescript-eslint/typescript-estree': 5.4.0_typescript@4.5.2 eslint: 8.2.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.2.0 @@ -1644,7 +1646,7 @@ packages: - typescript dev: true - /@typescript-eslint/parser/4.33.0_eslint@7.32.0+typescript@4.4.4: + /@typescript-eslint/parser/4.33.0_eslint@7.32.0+typescript@4.5.2: resolution: {integrity: sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -1656,15 +1658,15 @@ packages: dependencies: '@typescript-eslint/scope-manager': 4.33.0 '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.4.4 + '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.5.2 debug: 4.3.2 eslint: 7.32.0 - typescript: 4.4.4 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser/5.4.0_eslint@8.2.0+typescript@4.4.4: + /@typescript-eslint/parser/5.4.0_eslint@8.2.0+typescript@4.5.2: resolution: {integrity: sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1676,10 +1678,10 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.4.0 '@typescript-eslint/types': 5.4.0 - '@typescript-eslint/typescript-estree': 5.4.0_typescript@4.4.4 + '@typescript-eslint/typescript-estree': 5.4.0_typescript@4.5.2 debug: 4.3.2 eslint: 8.2.0 - typescript: 4.4.4 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true @@ -1710,7 +1712,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/4.33.0_typescript@4.4.4: + /@typescript-eslint/typescript-estree/4.33.0_typescript@4.5.2: resolution: {integrity: sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -1725,13 +1727,13 @@ packages: globby: 11.0.4 is-glob: 4.0.3 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.4.4 - typescript: 4.4.4 + tsutils: 3.21.0_typescript@4.5.2 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.4.0_typescript@4.4.4: + /@typescript-eslint/typescript-estree/5.4.0_typescript@4.5.2: resolution: {integrity: sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1746,8 +1748,8 @@ packages: globby: 11.0.4 is-glob: 4.0.3 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.4.4 - typescript: 4.4.4 + tsutils: 3.21.0_typescript@4.5.2 + typescript: 4.5.2 transitivePeerDependencies: - supports-color dev: true @@ -1768,18 +1770,18 @@ packages: eslint-visitor-keys: 3.1.0 dev: true - /@verypossible/eslint-config/1.6.1_typescript@4.4.4: + /@verypossible/eslint-config/1.6.1_typescript@4.5.2: resolution: {integrity: sha512-3qf2FSag49zqI6rZlwKcF8RryLX0RJ3W+koJuhDhdQNyelSEeTxiijQ+Y/Xss4ILFzyqpBnzqiphmABGcOgj1Q==} dependencies: - '@typescript-eslint/eslint-plugin': 4.33.0_cc617358c89d3f38c52462f6d809db4c - '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.4.4 + '@typescript-eslint/eslint-plugin': 4.33.0_d00b196ac5df1286ea7e45797bebddbc + '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.5.2 babel-plugin-module-resolver: 4.1.0 eslint: 7.32.0 eslint-config-prettier: 8.3.0_eslint@7.32.0 eslint-import-resolver-babel-module: 5.3.1_e51044130ac762fd207a8cd2109b5344 eslint-import-resolver-typescript: 2.5.0_a820dc868cc8cd66d8297be6779b9035 eslint-plugin-import: 2.25.3_eslint@7.32.0 - eslint-plugin-react: 7.27.0_eslint@7.32.0 + eslint-plugin-react: 7.27.1_eslint@7.32.0 eslint-plugin-react-hooks: 4.3.0_eslint@7.32.0 prettier: 2.4.1 transitivePeerDependencies: @@ -1812,12 +1814,12 @@ packages: acorn: 7.4.1 dev: true - /acorn-jsx/5.3.2_acorn@8.5.0: + /acorn-jsx/5.3.2_acorn@8.6.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.5.0 + acorn: 8.6.0 dev: true /acorn-node/1.8.2: @@ -1839,8 +1841,8 @@ packages: hasBin: true dev: true - /acorn/8.5.0: - resolution: {integrity: sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==} + /acorn/8.6.0: + resolution: {integrity: sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -1854,8 +1856,8 @@ packages: uri-js: 4.4.1 dev: true - /ajv/8.8.0: - resolution: {integrity: sha512-L+cJ/+pkdICMueKR6wIx3VP2fjIx3yAhuvadUv/osv9yFD7OVZy442xFF+Oeu3ZvmhBGQzoF6mTSt+LUWBmGQg==} + /ajv/8.8.1: + resolution: {integrity: sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg==} dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 @@ -1965,7 +1967,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.18.1 - caniuse-lite: 1.0.30001280 + caniuse-lite: 1.0.30001282 fraction.js: 4.1.2 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -1991,42 +1993,42 @@ packages: find-babel-config: 1.2.0 glob: 7.2.0 pkg-up: 3.1.0 - reselect: 4.1.3 + reselect: 4.1.4 resolve: 1.20.0 dev: true - /babel-plugin-polyfill-corejs2/0.2.3_@babel+core@7.16.0: - resolution: {integrity: sha512-NDZ0auNRzmAfE1oDDPW2JhzIMXUk+FFe2ICejmt5T4ocKgiQx3e0VCRx9NCAidcMtL2RUZaWtXnmjTCkx0tcbA==} + /babel-plugin-polyfill-corejs2/0.3.0_@babel+core@7.16.0: + resolution: {integrity: sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.16.0 + '@babel/compat-data': 7.16.4 '@babel/core': 7.16.0 - '@babel/helper-define-polyfill-provider': 0.2.4_@babel+core@7.16.0 + '@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.16.0 semver: 6.3.0 transitivePeerDependencies: - supports-color dev: true - /babel-plugin-polyfill-corejs3/0.3.0_@babel+core@7.16.0: - resolution: {integrity: sha512-JLwi9vloVdXLjzACL80j24bG6/T1gYxwowG44dg6HN/7aTPdyPbJJidf6ajoA3RPHHtW0j9KMrSOLpIZpAnPpg==} + /babel-plugin-polyfill-corejs3/0.4.0_@babel+core@7.16.0: + resolution: {integrity: sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-define-polyfill-provider': 0.2.4_@babel+core@7.16.0 + '@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.16.0 core-js-compat: 3.19.1 transitivePeerDependencies: - supports-color dev: true - /babel-plugin-polyfill-regenerator/0.2.3_@babel+core@7.16.0: - resolution: {integrity: sha512-JVE78oRZPKFIeUqFGrSORNzQnrDwZR16oiWeGM8ZyjBn2XAT5OjP+wXx5ESuo33nUsFUEJYjtklnsKbxW5L+7g==} + /babel-plugin-polyfill-regenerator/0.3.0_@babel+core@7.16.0: + resolution: {integrity: sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-define-polyfill-provider': 0.2.4_@babel+core@7.16.0 + '@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.16.0 transitivePeerDependencies: - supports-color dev: true @@ -2063,8 +2065,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001280 - electron-to-chromium: 1.3.899 + caniuse-lite: 1.0.30001282 + electron-to-chromium: 1.3.904 escalade: 3.1.1 node-releases: 2.0.1 picocolors: 1.0.0 @@ -2096,8 +2098,8 @@ packages: engines: {node: '>= 6'} dev: true - /caniuse-lite/1.0.30001280: - resolution: {integrity: sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==} + /caniuse-lite/1.0.30001282: + resolution: {integrity: sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==} dev: true /chalk/2.4.2: @@ -2167,8 +2169,8 @@ packages: engines: {node: '>= 10'} dev: true - /common-tags/1.8.1: - resolution: {integrity: sha512-uOZd85rJqrdEIE/JjhW5YAeatX8iqjjvVzIyfx7JL7G5r9Tep6YpYT9gEJWhWpVyDQEyzukWd6p2qULpJ8tmBw==} + /common-tags/1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} dev: true @@ -2339,8 +2341,8 @@ packages: jake: 10.8.2 dev: true - /electron-to-chromium/1.3.899: - resolution: {integrity: sha512-w16Dtd2zl7VZ4N4Db+FIa7n36sgPGCKjrKvUUmp5ialsikvcQLjcJR9RWnlYNxIyEHLdHaoIZEqKsPxU9MdyBg==} + /electron-to-chromium/1.3.904: + resolution: {integrity: sha512-x5uZWXcVNYkTh4JubD7KSC1VMKz0vZwJUqVwY3ihsW0bst1BXDe494Uqbg3Y0fDGVjJqA8vEeGuvO5foyH2+qw==} dev: true /emoji-regex/8.0.0: @@ -2601,6 +2603,15 @@ packages: eslint: 8.2.0 dev: true + /eslint-import-resolver-alias/1.1.2_eslint-plugin-import@2.25.3: + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + dependencies: + eslint-plugin-import: 2.25.3_eslint@8.2.0 + dev: true + /eslint-import-resolver-babel-module/5.3.1_e51044130ac762fd207a8cd2109b5344: resolution: {integrity: sha512-WomQAkjO7lUNOdU3FG2zgNgylkoAVUmaw04bHgSpM9QrMWuOLLWa2qcP6CrsBd4VWuLRbUPyzrgBc9ZQIx9agw==} engines: {node: '>=10.0.0'} @@ -2633,7 +2644,7 @@ packages: glob: 7.2.0 is-glob: 4.0.3 resolve: 1.20.0 - tsconfig-paths: 3.11.0 + tsconfig-paths: 3.12.0 transitivePeerDependencies: - supports-color dev: true @@ -2651,7 +2662,7 @@ packages: glob: 7.2.0 is-glob: 4.0.3 resolve: 1.20.0 - tsconfig-paths: 3.11.0 + tsconfig-paths: 3.12.0 transitivePeerDependencies: - supports-color dev: true @@ -2684,7 +2695,7 @@ packages: minimatch: 3.0.4 object.values: 1.1.5 resolve: 1.20.0 - tsconfig-paths: 3.11.0 + tsconfig-paths: 3.12.0 dev: true /eslint-plugin-import/2.25.3_eslint@8.2.0: @@ -2706,7 +2717,7 @@ packages: minimatch: 3.0.4 object.values: 1.1.5 resolve: 1.20.0 - tsconfig-paths: 3.11.0 + tsconfig-paths: 3.12.0 dev: true /eslint-plugin-react-hooks/4.3.0_eslint@7.32.0: @@ -2727,8 +2738,8 @@ packages: eslint: 8.2.0 dev: true - /eslint-plugin-react/7.27.0_eslint@7.32.0: - resolution: {integrity: sha512-0Ut+CkzpppgFtoIhdzi2LpdpxxBvgFf99eFqWxJnUrO7mMe0eOiNpou6rvNYeVVV6lWZvTah0BFne7k5xHjARg==} + /eslint-plugin-react/7.27.1_eslint@7.32.0: + resolution: {integrity: sha512-meyunDjMMYeWr/4EBLTV1op3iSG3mjT/pz5gti38UzfM4OPpNc2m0t2xvKCOMU5D6FSdd34BIMFOvQbW+i8GAA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -2750,8 +2761,8 @@ packages: string.prototype.matchall: 4.0.6 dev: true - /eslint-plugin-react/7.27.0_eslint@8.2.0: - resolution: {integrity: sha512-0Ut+CkzpppgFtoIhdzi2LpdpxxBvgFf99eFqWxJnUrO7mMe0eOiNpou6rvNYeVVV6lWZvTah0BFne7k5xHjARg==} + /eslint-plugin-react/7.27.1_eslint@8.2.0: + resolution: {integrity: sha512-meyunDjMMYeWr/4EBLTV1op3iSG3mjT/pz5gti38UzfM4OPpNc2m0t2xvKCOMU5D6FSdd34BIMFOvQbW+i8GAA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -2940,8 +2951,8 @@ packages: resolution: {integrity: sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.5.0 - acorn-jsx: 5.3.2_acorn@8.5.0 + acorn: 8.6.0 + acorn-jsx: 5.3.2_acorn@8.6.0 eslint-visitor-keys: 3.1.0 dev: true @@ -3260,8 +3271,8 @@ packages: '@babel/runtime': 7.16.3 dev: false - /i18next/21.5.1: - resolution: {integrity: sha512-fmpns1dbYYgyOkiATp1rg5gyXzvBdvM0YQFDCM38BoqybG2Rs3looAv+e24ghFeeozD1fteUtDTZ36SQ0a+ycg==} + /i18next/21.5.2: + resolution: {integrity: sha512-Iuztr2+7CPCh5SYQV0utw2HXMx1za18xfznrw/PmgX+98oIpm84bhIM7VUPODjLycwIZ299oP7sEVQ9oCgmzfg==} dependencies: '@babel/runtime': 7.16.3 dev: false @@ -3516,7 +3527,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 16.11.7 + '@types/node': 16.11.9 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -4091,15 +4102,16 @@ packages: tinycolor2: 1.4.2 dev: false - /react-hook-form/7.19.5_react@17.0.2: - resolution: {integrity: sha512-+M9gd0nWWASuEitf5PQGOGNElnHknzY3rGISrPDXwsOmZb7c/jyvkRUqk4OJXaZit1ZwesSv+EysttdAeFEfmw==} + /react-hook-form/7.20.2_react@17.0.2: + resolution: {integrity: sha512-J5zkZW0Mf3KuMlk7Tl1tWYXoSjYhfKyEu//NfWTn2xzvv2vnDT5/EE/vHgtNb7kVeYpx6/mHIBd9aOfNnWcOsg==} + engines: {node: '>=12.0'} peerDependencies: react: ^16.8.0 || ^17 dependencies: react: 17.0.2 dev: false - /react-i18next/11.14.2_i18next@21.5.1+react@17.0.2: + /react-i18next/11.14.2_i18next@21.5.2+react@17.0.2: resolution: {integrity: sha512-fmDhwNA0zDmSEL3BBT5qwNMvxrKu25oXDDAZyHprfB0AHZmWXfBmRLf8MX8i1iBd2I2C2vsA2D9wxYBIwzooEQ==} peerDependencies: i18next: '>= 19.0.0' @@ -4107,7 +4119,7 @@ packages: dependencies: '@babel/runtime': 7.16.3 html-parse-stringify: 3.0.1 - i18next: 21.5.1 + i18next: 21.5.2 react: 17.0.2 dev: false @@ -4254,8 +4266,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /reselect/4.1.3: - resolution: {integrity: sha512-TVpMknnmdSRNhLPgTDSCQKw32zt1ZIJtEcSxfL/ihtDqShEMUs2X2UY/g96YAVynUXxqLWSXObLGIcqKHQObHw==} + /reselect/4.1.4: + resolution: {integrity: sha512-i1LgXw8DKSU5qz1EV0ZIKz4yIUHJ7L3bODh+Da6HmVSm9vdL/hG7IpbgzQ3k2XSirzf8/eI7OMEs81gb1VV2fQ==} /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -4403,8 +4415,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /source-map-support/0.5.20: - resolution: {integrity: sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==} + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 @@ -4543,7 +4555,7 @@ packages: resolution: {integrity: sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==} engines: {node: '>=10.0.0'} dependencies: - ajv: 8.8.0 + ajv: 8.8.1 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -4624,7 +4636,7 @@ packages: dependencies: commander: 2.20.3 source-map: 0.7.3 - source-map-support: 0.5.20 + source-map-support: 0.5.21 dev: true /text-table/0.2.0: @@ -4673,8 +4685,8 @@ packages: punycode: 2.1.1 dev: true - /tsconfig-paths/3.11.0: - resolution: {integrity: sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==} + /tsconfig-paths/3.12.0: + resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} dependencies: '@types/json5': 0.0.29 json5: 1.0.1 @@ -4686,14 +4698,14 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tsutils/3.21.0_typescript@4.4.4: + /tsutils/3.21.0_typescript@4.5.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.4.4 + typescript: 4.5.2 dev: true /type-check/0.4.0: @@ -4719,8 +4731,8 @@ packages: history: 5.1.0 dev: false - /typescript/4.4.4: - resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==} + /typescript/4.5.2: + resolution: {integrity: sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==} engines: {node: '>=4.2.0'} hasBin: true dev: true @@ -4803,8 +4815,8 @@ packages: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true - /vite-plugin-pwa/0.11.5_vite@2.6.14: - resolution: {integrity: sha512-qn79L7008ZMn9GS0ClxypOBRA3Ft8/a8saIQ03SC2R1QndbZVW+YQVHTlFno33Wp6fu5UJacoHWuZYCuKZKaOA==} + /vite-plugin-pwa/0.11.6_vite@2.6.14: + resolution: {integrity: sha512-C95xVO8csEedN29aHszKjRSb/P7Odd6+tCP3LfjqQQkNRkZPieT6y8mS5MlEbs9V+8D+z4THD6ksYB5mzLTzPg==} peerDependencies: vite: ^2.0.0 dependencies: @@ -4924,16 +4936,16 @@ packages: resolution: {integrity: sha512-cvH74tEO8SrziFrCntZ/35B0uaMZFKG+gnk3vZmKLSUTab/6MlhL+UwYXf1sMV5SD/W/v7pnFKZbdAOAg5Ne2w==} engines: {node: '>=10.0.0'} dependencies: - '@apideck/better-ajv-errors': 0.2.7_ajv@8.8.0 + '@apideck/better-ajv-errors': 0.2.7_ajv@8.8.1 '@babel/core': 7.16.0 - '@babel/preset-env': 7.16.0_@babel+core@7.16.0 + '@babel/preset-env': 7.16.4_@babel+core@7.16.0 '@babel/runtime': 7.16.3 '@rollup/plugin-babel': 5.3.0_@babel+core@7.16.0+rollup@2.60.0 '@rollup/plugin-node-resolve': 11.2.1_rollup@2.60.0 '@rollup/plugin-replace': 2.4.2_rollup@2.60.0 '@surma/rollup-plugin-off-main-thread': 2.2.3 - ajv: 8.8.0 - common-tags: 1.8.1 + ajv: 8.8.1 + common-tags: 1.8.2 fast-json-stable-stringify: 2.1.0 fs-extra: 9.1.0 glob: 7.2.0 diff --git a/src/App.tsx b/src/App.tsx index 2284ac14..c8651625 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,14 @@ import React from 'react'; -import { useAppDispatch, useAppSelector } from '@app/hooks/redux'; -import { DeviceStatusDropdown } from '@components/menu/buttons/DeviceStatusDropdown'; +import { DeviceStatus } from '@app/components/menu/buttons/DeviceStatus'; +import { useAppSelector } from '@app/hooks/redux'; +import { Connection } from '@components/Connection'; import { MobileNavToggle } from '@components/menu/buttons/MobileNavToggle'; import { ThemeToggle } from '@components/menu/buttons/ThemeToggle'; import { Logo } from '@components/menu/Logo'; import { MobileNav } from '@components/menu/MobileNav'; import { Navigation } from '@components/menu/Navigation'; -import { connection, setConnection } from '@core/connection'; import { useRoute } from '@core/router'; -import { - addChannel, - addMessage, - addNode, - addPosition, - addUser, - setDeviceStatus, - setLastMeshInterraction, - setMyNodeInfo, - setPreferences, - setReady, -} from '@core/slices/meshtasticSlice'; -import { - IBLEConnection, - IHTTPConnection, - ISerialConnection, - Protobuf, - SettingsManager, - Types, -} from '@meshtastic/meshtasticjs'; import { Messages } from '@pages/Messages'; import { Nodes } from '@pages/Nodes/Index'; import { Settings } from '@pages/settings/Index'; @@ -36,128 +16,13 @@ import { Settings } from '@pages/settings/Index'; import { NotFound } from './pages/NotFound'; import { Plugins } from './pages/Plugins/Index'; -const App = (): JSX.Element => { - const dispatch = useAppDispatch(); +export const App = (): JSX.Element => { const route = useRoute(); - - const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo); const darkMode = useAppSelector((state) => state.app.darkMode); - const hostOverrideEnabled = useAppSelector( - (state) => state.meshtastic.hostOverrideEnabled, - ); - - const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride); - - const connectionURL = hostOverrideEnabled - ? hostOverride - : import.meta.env.PROD - ? window.location.hostname - : (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ?? - 'http://meshtastic.local'; - - React.useEffect(() => { - const connectionMethod = localStorage.getItem('connectionMethod'); - - switch (connectionMethod) { - case 'serial': - setConnection(new ISerialConnection()); - //show connection dialogue - break; - case 'bluetooth': - setConnection(new IBLEConnection()); - //show connection dialogue - break; - default: - setConnection(new IHTTPConnection()); - void connection.connect({ - address: connectionURL, - tls: false, - receiveBatchRequests: false, - fetchInterval: 2000, - }); - break; - } - SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE; - }, [hostOverrideEnabled, hostOverride, connectionURL]); - - React.useEffect(() => { - connection.onDeviceStatus.subscribe((status) => { - dispatch(setDeviceStatus(status)); - - if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) { - dispatch(setReady(true)); - } - if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) { - dispatch(setReady(false)); - } - }); - - connection.onMyNodeInfo.subscribe((nodeInfo) => { - dispatch(setMyNodeInfo(nodeInfo)); - }); - - connection.onUserPacket.subscribe((user) => { - dispatch(addUser(user)); - }); - - connection.onPositionPacket.subscribe((position) => { - dispatch(addPosition(position)); - }); - - connection.onNodeInfoPacket.subscribe( - (nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => { - dispatch(addNode(nodeInfoPacket.data)); - }, - ); - - connection.onAdminPacket.subscribe((adminPacket) => { - switch (adminPacket.data.variant.oneofKind) { - case 'getChannelResponse': - dispatch(addChannel(adminPacket.data.variant.getChannelResponse)); - break; - case 'getRadioResponse': - if (adminPacket.data.variant.getRadioResponse.preferences) { - dispatch( - setPreferences( - adminPacket.data.variant.getRadioResponse.preferences, - ), - ); - } - break; - } - }); - - connection.onMeshHeartbeat.subscribe( - (date): void | { payload: number; type: string } => - dispatch(setLastMeshInterraction(date.getTime())), - ); - - connection.onTextPacket.subscribe((message) => { - dispatch( - addMessage({ - message: message, - ack: message.packet.from !== myNodeInfo.myNodeNum, - isSender: message.packet.from === myNodeInfo.myNodeNum, - received: new Date(message.packet.rxTime), - }), - ); - }); - - return (): void => { - connection.onDeviceStatus.cancelAll(); - connection.onMyNodeInfo.cancelAll(); - connection.onNodeInfoPacket.cancelAll(); - connection.onAdminPacket.cancelAll(); - connection.onMeshHeartbeat.cancelAll(); - connection.onTextPacket.cancelAll(); - connection.onRoutingPacket.cancelAll(); - }; - }, [dispatch, myNodeInfo.myNodeNum]); return ( -
+
+
@@ -168,7 +33,7 @@ const App = (): JSX.Element => {
- +
@@ -189,5 +54,3 @@ const App = (): JSX.Element => {
); }; - -export default App; diff --git a/src/components/Connection.tsx b/src/components/Connection.tsx new file mode 100644 index 00000000..0747d6eb --- /dev/null +++ b/src/components/Connection.tsx @@ -0,0 +1,269 @@ +import React from 'react'; + +import { FiCheck } from 'react-icons/fi'; +import JSONPretty from 'react-json-pretty'; + +import { useAppDispatch, useAppSelector } from '@app/hooks/redux'; +import { Button } from '@components/generic/Button'; +import { Card } from '@components/generic/Card'; +import { Checkbox } from '@components/generic/form/Checkbox'; +import { Input } from '@components/generic/form/Input'; +import { Select } from '@components/generic/form/Select'; +import { IconButton } from '@components/generic/IconButton'; +import { Modal } from '@components/generic/Modal'; +import { + ble, + connection, + connectionUrl, + serial, + setConnection, +} from '@core/connection'; +import { closeConnectionModal } from '@core/slices/appSlice'; +import { + IBLEConnection, + IHTTPConnection, + ISerialConnection, + Protobuf, + SettingsManager, +} from '@meshtastic/meshtasticjs'; +import type { + BLEConnectionParameters, + HTTPConnectionParameters, + SerialConnectionParameters, +} from '@meshtastic/meshtasticjs/dist/types'; + +import { DeviceStatus } from './menu/buttons/DeviceStatus'; + +enum connType { + HTTP, + BLE, + SERIAL, +} + +export const Connection = (): JSX.Element => { + const dispatch = useAppDispatch(); + const [selectedConnType, setSelectedConnType] = React.useState(connType.HTTP); + const [bleDevices, setBleDevices] = React.useState([]); + const [serialDevices, setSerialDevices] = React.useState([]); + const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>( + 'local', + ); + const hostOverrideEnabled = useAppSelector( + (state) => state.meshtastic.hostOverrideEnabled, + ); + const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride); + const connectionModalOpen = useAppSelector( + (state) => state.app.connectionModalOpen, + ); + const ready = useAppSelector((state) => state.meshtastic.ready); + const connect = async ( + connectionType: connType, + params: + | HTTPConnectionParameters + | SerialConnectionParameters + | BLEConnectionParameters, + ): Promise => { + connection.complete(); + await connection.disconnect(); + + if (connectionType === connType.BLE) { + setConnection(new IBLEConnection()); + } else if (connectionType === connType.HTTP) { + setConnection(new IHTTPConnection()); + } else { + setConnection(new ISerialConnection()); + } + + // @ts-ignore tmp + await connection.connect(params); + }; + + const updateBleDeviceList = React.useCallback(async (): Promise => { + const devices = await ble.getDevices(); + setBleDevices(devices); + }, []); + + const updateSerialDeviceList = React.useCallback(async (): Promise => { + const devices = await serial.getPorts(); + setSerialDevices(devices); + }, []); + + React.useEffect(() => { + if (ready) { + dispatch(closeConnectionModal()); + } + }, [ready, dispatch]); + + React.useEffect(() => { + if (selectedConnType === connType.BLE) { + void updateBleDeviceList(); + } + if (selectedConnType === connType.SERIAL) { + void updateSerialDeviceList(); + } + }, [selectedConnType, updateBleDeviceList, updateSerialDeviceList]); + + React.useEffect(() => { + const connectionMethod = localStorage.getItem('connectionMethod'); + + switch (connectionMethod) { + case 'serial': + setConnection(new ISerialConnection()); + //show connection dialogue + break; + case 'bluetooth': + setConnection(new IBLEConnection()); + //show connection dialogue + break; + default: + setConnection(new IHTTPConnection()); + void connection.connect({ + address: connectionUrl, + tls: false, + receiveBatchRequests: false, + fetchInterval: 2000, + }); + break; + } + SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE; + }, [hostOverrideEnabled, hostOverride]); + + return ( + { + dispatch(closeConnectionModal()); + }} + > + +
+ {ready ? ( +
+ { + setHttpIpSource(e.target.value as 'local' | 'remote'); + }} + /> + {httpIpSource === 'local' ? ( + + ) : ( + + )} + + + )} + {selectedConnType === connType.BLE && ( +
+
+ + +
+
+
Previously connected devices
+ {bleDevices.map((device, index) => ( +
=> { + await connect(connType.BLE, { + device: device, + }); + }} + className="flex justify-between p-2 bg-gray-700 rounded-md" + key={index} + > +
{device.name}
+ => { + await connect(connType.BLE, { + device: device, + }); + }} + icon={} + /> +
+ ))} +
+
+ )} + {selectedConnType === connType.SERIAL && ( +
+
+ + +
+
+
Previously connected devices
+ {serialDevices.map((device, index) => ( +
+
+ {device.getInfo().usbProductId} + {device.getInfo().usbVendorId} +
+ => { + await connect(connType.SERIAL, { + // @ts-ignore tmp + device: device, + }); + }} + icon={} + /> + +
+ ))} +
+
+ )} + + ) : ( +
+ +
+ )} +
+
+
+ ); +}; diff --git a/src/components/LoraConfig.tsx b/src/components/LoraConfig.tsx index 4c474464..c57ffc37 100644 --- a/src/components/LoraConfig.tsx +++ b/src/components/LoraConfig.tsx @@ -2,14 +2,13 @@ import React from 'react'; import { useForm } from 'react-hook-form'; +import { Card } from '@components/generic/Card'; +import { Checkbox } from '@components/generic/form/Checkbox'; +import { Input } from '@components/generic/form/Input'; import { Loading } from '@components/generic/Loading'; +import { connection } from '@core/connection'; import { Protobuf } from '@meshtastic/meshtasticjs'; -import { connection } from '../core/connection'; -import { Card } from './generic/Card'; -import { Checkbox } from './generic/form/Checkbox'; -import { Input } from './generic/form/Input'; - export interface LoraConfigProps { channel: Protobuf.Channel; } diff --git a/src/components/chat/MessageBar.tsx b/src/components/chat/MessageBar.tsx index 0c3c1b82..8406b00d 100644 --- a/src/components/chat/MessageBar.tsx +++ b/src/components/chat/MessageBar.tsx @@ -3,30 +3,42 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { FiSend } from 'react-icons/fi'; -import { ackMessage } from '@app/core/slices/meshtasticSlice'; import { useAppDispatch, useAppSelector } from '@app/hooks/redux'; import { Input } from '@components/generic/form/Input'; import { connection } from '@core/connection'; +import { ackMessage } from '@core/slices/meshtasticSlice'; import { Select } from '../generic/form/Select'; import { IconButton } from '../generic/IconButton'; -export const MessageBar = (): JSX.Element => { +export interface MessageBarProps { + channelIndex: number; +} + +export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => { const dispatch = useAppDispatch(); const ready = useAppSelector((state) => state.meshtastic.ready); const nodes = useAppSelector((state) => state.meshtastic.nodes); - const users = useAppSelector((state) => state.meshtastic.users); - const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo); + const myNodeNum = useAppSelector( + (state) => state.meshtastic.radio.hardware, + ).myNodeNum; + const [currentMessage, setCurrentMessage] = React.useState(''); const [destinationNode, setDestinationNode] = React.useState(0xffffffff); const sendMessage = (): void => { if (ready) { - void connection.sendText(currentMessage, destinationNode, true, (id) => { - dispatch(ackMessage(id)); + void connection.sendText( + currentMessage, + destinationNode, + true, + channelIndex--, + (id) => { + dispatch(ackMessage({ channel: 0, messageId: id })); - return Promise.resolve(); - }); + return Promise.resolve(); + }, + ); setCurrentMessage(''); } }; @@ -51,14 +63,11 @@ export const MessageBar = (): JSX.Element => { value: 0xffffffff, }, ...nodes - .filter((node) => node.num !== myNodeInfo.myNodeNum) + .filter((node) => node.number !== myNodeNum) .map((node) => { - const user = users.filter( - (user) => user.packet.from === node.num, - )[0]?.data; return { - name: user ? user.shortName : node.num, - value: node.num, + name: node.user ? node.user.shortName : node.number, + value: node.number, }; }), ]} diff --git a/src/components/generic/Blur.tsx b/src/components/generic/Blur.tsx index 14e21709..90446ce1 100644 --- a/src/components/generic/Blur.tsx +++ b/src/components/generic/Blur.tsx @@ -15,14 +15,14 @@ export const Blur = ({ return (
diff --git a/src/components/generic/Button.tsx b/src/components/generic/Button.tsx index 3bd6a66b..51dad900 100644 --- a/src/components/generic/Button.tsx +++ b/src/components/generic/Button.tsx @@ -9,6 +9,7 @@ interface ButtonProps extends DefaultButtonProps { circle?: boolean; active?: boolean; border?: boolean; + padding?: number; confirmAction?: () => void; } @@ -21,6 +22,7 @@ export const Button = ({ confirmAction, disabled, children, + padding = 3, ...props }: ButtonProps): JSX.Element => { const [hasConfirmed, setHasConfirmed] = React.useState(false); @@ -43,7 +45,9 @@ export const Button = ({ className={`items-center select-none flex dark:text-white active:scale-95 transition duration-200 ease-in-out ${ active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : '' } ${ - circle ? 'rounded-full h-10 w-10' : 'rounded-md p-3 space-x-3 text-sm' + circle + ? 'rounded-full h-10 w-10' + : `rounded-md p-${padding} space-x-3 text-sm` } ${ disabled ? 'cursor-not-allowed dark:bg-primaryDark bg-white' diff --git a/src/components/generic/Card.tsx b/src/components/generic/Card.tsx index 2fd8d264..ed86e2fd 100644 --- a/src/components/generic/Card.tsx +++ b/src/components/generic/Card.tsx @@ -24,7 +24,7 @@ export const Card = ({ }: CardProps): JSX.Element => { return (
{loading && } diff --git a/src/components/generic/Modal.tsx b/src/components/generic/Modal.tsx new file mode 100644 index 00000000..1ee81f48 --- /dev/null +++ b/src/components/generic/Modal.tsx @@ -0,0 +1,37 @@ +import type React from 'react'; + +import { useAppSelector } from '@app/hooks/redux'; +import { Dialog } from '@headlessui/react'; + +export interface ModalProps { + children: React.ReactNode; + open: boolean; + onClose: () => void; +} + +export const Modal = ({ children, open, onClose }: ModalProps): JSX.Element => { + const darkMode = useAppSelector((state) => state.app.darkMode); + return ( + <> + + +
+ +
+ {children} +
+
+
+ + ); +}; diff --git a/src/components/generic/StatCard.tsx b/src/components/generic/StatCard.tsx index 625f4f5d..c4554e48 100644 --- a/src/components/generic/StatCard.tsx +++ b/src/components/generic/StatCard.tsx @@ -7,7 +7,7 @@ export interface StatCardProps { export const StatCard = ({ title, value }: StatCardProps): JSX.Element => { return ( -
+
{title}
{value}
diff --git a/src/components/menu/buttons/DeviceStatusDropdown.tsx b/src/components/menu/buttons/DeviceStatus.tsx similarity index 56% rename from src/components/menu/buttons/DeviceStatusDropdown.tsx rename to src/components/menu/buttons/DeviceStatus.tsx index 764b91d3..0d9ea68c 100644 --- a/src/components/menu/buttons/DeviceStatusDropdown.tsx +++ b/src/components/menu/buttons/DeviceStatus.tsx @@ -2,20 +2,28 @@ import type React from 'react'; import { FiWifi, FiWifiOff } from 'react-icons/fi'; -import { useAppSelector } from '@app/hooks/redux'; -import { IconButton } from '@components/generic/IconButton'; +import { useAppDispatch, useAppSelector } from '@app/hooks/redux'; +import { Button } from '@components/generic/Button'; +import { openConnectionModal } from '@core/slices/appSlice'; import { Types } from '@meshtastic/meshtasticjs'; -export const DeviceStatusDropdown = (): JSX.Element => { +export const DeviceStatus = (): JSX.Element => { + const dispatch = useAppDispatch(); const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus); const ready = useAppSelector((state) => state.meshtastic.ready); return ( -
-
+
+ ); }; diff --git a/src/components/pwa/ReloadPrompt.tsx b/src/components/pwa/ReloadPrompt.tsx index 977e5033..c1c0da45 100644 --- a/src/components/pwa/ReloadPrompt.tsx +++ b/src/components/pwa/ReloadPrompt.tsx @@ -6,7 +6,7 @@ import type React from 'react'; // eslint-disable-next-line import/no-unresolved import { useRegisterSW } from 'virtual:pwa-register/react'; -const ReloadPrompt = (): JSX.Element => { +export const ReloadPrompt = (): JSX.Element => { const { offlineReady: [offlineReady, setOfflineReady], needRefresh: [needRefresh, setNeedRefresh], @@ -58,5 +58,3 @@ const ReloadPrompt = (): JSX.Element => {
); }; - -export default ReloadPrompt; diff --git a/src/core/connection.ts b/src/core/connection.ts index 8ecd2242..f1166f6f 100644 --- a/src/core/connection.ts +++ b/src/core/connection.ts @@ -1,16 +1,119 @@ +import { + addChannel, + addMessage, + addNode, + addPosition, + addUser, + setDeviceStatus, + setLastMeshInterraction, + setMyNodeInfo, + setPreferences, + setReady, +} from '@core/slices/meshtasticSlice'; +import { store } from '@core/store'; import { IBLEConnection, IHTTPConnection, ISerialConnection, + Protobuf, + Types, } from '@meshtastic/meshtasticjs'; type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection; export let connection: connectionType = new IHTTPConnection(); +const state = store.getState().meshtastic; +export const connectionUrl = state.hostOverrideEnabled + ? state.hostOverride + : import.meta.env.PROD + ? window.location.hostname + : (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ?? + 'http://meshtastic.local'; + export const ble = new IBLEConnection(); export const serial = new ISerialConnection(); export const setConnection = (conn: connectionType): void => { + cleanupListeners(); connection = conn; + + registerListeners(); +}; + +const cleanupListeners = (): void => { + connection.onDeviceStatus.cancelAll(); + connection.onMyNodeInfo.cancelAll(); + connection.onUserPacket.cancelAll(); + connection.onPositionPacket.cancelAll(); + connection.onNodeInfoPacket.cancelAll(); + connection.onAdminPacket.cancelAll(); + connection.onMeshHeartbeat.cancelAll(); + connection.onTextPacket.cancelAll(); +}; + +const registerListeners = (): void => { + connection.onDeviceStatus.subscribe((status) => { + store.dispatch(setDeviceStatus(status)); + + if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) { + store.dispatch(setReady(true)); + } + if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) { + store.dispatch(setReady(false)); + } + }); + + connection.onMyNodeInfo.subscribe((nodeInfo) => { + store.dispatch(setMyNodeInfo(nodeInfo)); + }); + + connection.onUserPacket.subscribe((user) => { + store.dispatch(addUser(user)); + }); + + connection.onPositionPacket.subscribe((position) => { + store.dispatch(addPosition(position)); + }); + + connection.onNodeInfoPacket.subscribe( + (nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => { + store.dispatch(addNode(nodeInfoPacket.data)); + }, + ); + + connection.onAdminPacket.subscribe((adminPacket) => { + switch (adminPacket.data.variant.oneofKind) { + case 'getChannelResponse': + store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse)); + break; + case 'getRadioResponse': + if (adminPacket.data.variant.getRadioResponse.preferences) { + store.dispatch( + setPreferences( + adminPacket.data.variant.getRadioResponse.preferences, + ), + ); + } + break; + } + }); + + connection.onMeshHeartbeat.subscribe( + (date): void | { payload: number; type: string } => + store.dispatch(setLastMeshInterraction(date.getTime())), + ); + + connection.onTextPacket.subscribe((message) => { + const myNodeNum = store.getState().meshtastic.radio.hardware.myNodeNum; + + store.dispatch( + addMessage({ + message: message, + ack: message.packet.from !== myNodeNum, + isSender: message.packet.from === myNodeNum, + received: new Date(message.packet.rxTime), + }), + ); + }); }; diff --git a/src/core/slices/appSlice.ts b/src/core/slices/appSlice.ts index 6016aa5f..c2a5550b 100644 --- a/src/core/slices/appSlice.ts +++ b/src/core/slices/appSlice.ts @@ -5,12 +5,14 @@ export type currentPageName = 'messages' | 'settings'; interface AppState { mobileNavOpen: boolean; + connectionModalOpen: boolean; darkMode: boolean; currentPage: currentPageName; } const initialState: AppState = { mobileNavOpen: false, + connectionModalOpen: true, darkMode: localStorage.getItem('darkMode') === 'true' ?? false, currentPage: 'messages', }; @@ -25,6 +27,12 @@ export const appSlice = createSlice({ closeMobileNav(state) { state.mobileNavOpen = false; }, + openConnectionModal(state) { + state.connectionModalOpen = true; + }, + closeConnectionModal(state) { + state.connectionModalOpen = false; + }, setDarkModeEnabled(state, action: PayloadAction) { localStorage.setItem('darkMode', String(action.payload)); state.darkMode = action.payload; @@ -38,6 +46,8 @@ export const appSlice = createSlice({ export const { openMobileNav, closeMobileNav, + openConnectionModal, + closeConnectionModal, setDarkModeEnabled, setCurrentPage, } = appSlice.actions; diff --git a/src/core/slices/meshtasticSlice.ts b/src/core/slices/meshtasticSlice.ts index 6ddf887b..9132c459 100644 --- a/src/core/slices/meshtasticSlice.ts +++ b/src/core/slices/meshtasticSlice.ts @@ -2,7 +2,7 @@ import { Protobuf, Types } from '@meshtastic/meshtasticjs'; import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { connection } from '../connection'; +// import { connection } from '../connection'; export interface MessageWithAck { message: Types.TextPacket; @@ -16,17 +16,31 @@ enum connType { SERIAL, } +export interface ChannelData { + channel: Protobuf.Channel; + messages: MessageWithAck[]; +} + +export interface Node { + number: number; + lastHeard: Date; + snr: number[]; + positions: Protobuf.Position[]; + user?: Protobuf.User; +} + +export interface Radio { + channels: ChannelData[]; + preferences: Protobuf.RadioConfig_UserPreferences; + hardware: Protobuf.MyNodeInfo; +} + interface MeshtasticState { deviceStatus: Types.DeviceStatusEnum; lastMeshInterraction: number; ready: boolean; - myNodeInfo: Protobuf.MyNodeInfo; - users: Types.UserPacket[]; - positionPackets: Types.PositionPacket[]; - nodes: Protobuf.NodeInfo[]; - channels: Protobuf.Channel[]; - preferences: Protobuf.RadioConfig_UserPreferences; - messages: MessageWithAck[]; + nodes: Node[]; + radio: Radio; hostOverrideEnabled: boolean; hostOverride: string; connectionType: connType; @@ -36,13 +50,12 @@ const initialState: MeshtasticState = { deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED, lastMeshInterraction: 0, ready: false, - myNodeInfo: Protobuf.MyNodeInfo.create(), - users: [], - positionPackets: [], nodes: [], - channels: [], - preferences: Protobuf.RadioConfig_UserPreferences.create(), - messages: [], + radio: { + channels: [], + preferences: Protobuf.RadioConfig_UserPreferences.create(), + hardware: Protobuf.MyNodeInfo.create(), + }, //todo implement // connectionMethod: localStorage.getItem('connectionMethod'), hostOverrideEnabled: @@ -65,76 +78,94 @@ export const meshtasticSlice = createSlice({ state.ready = action.payload; }, setMyNodeInfo: (state, action: PayloadAction) => { - state.myNodeInfo = action.payload; + state.radio.hardware = action.payload; }, addUser: (state, action: PayloadAction) => { - if ( - state.users.findIndex( - (user) => user.data.id === action.payload.data.id, - ) !== -1 - ) { - state.users = state.users.map((user) => { - return user.data.id === action.payload.data.id - ? action.payload - : user; - }); - } else { - state.users.push(action.payload); + const node = state.nodes.find( + (node) => node.number === action.payload.packet.from, + ); + if (node) { + node.user = action.payload.data; + // todo: use rx time + node.lastHeard = new Date(); } }, addPosition: (state, action: PayloadAction) => { - if ( - state.positionPackets.findIndex( - (position) => position.packet.from === action.payload.packet.from, - ) !== -1 - ) { - state.positionPackets = state.positionPackets.map((position) => { - return position.packet.from === action.payload.packet.from - ? action.payload - : position; - }); - } else { - state.positionPackets.push(action.payload); + const node = state.nodes.find( + (node) => node.number === action.payload.packet.from, + ); + + node?.positions.push(action.payload.data); + if (node) { + // todo: use rx time + node.lastHeard = new Date(); } }, addNode: (state, action: PayloadAction) => { - if ( - state.nodes.findIndex((node) => node.num === action.payload.num) !== -1 - ) { - state.nodes = state.nodes.map((node) => { - return node.num === action.payload.num ? action.payload : node; - }); + const node = state.nodes.find( + (node) => node.number === action.payload.num, + ); + + if (node) { + console.log('node exists'); + + node.lastHeard = new Date(action.payload.lastHeard * 1000); + node.snr.push(action.payload.snr); } else { - state.nodes.push(action.payload); + console.log('node does not exist'); + + state.nodes.push({ + number: action.payload.num, + lastHeard: new Date(action.payload.lastHeard * 1000), + snr: [action.payload.snr], + positions: [], + }); } }, addChannel: (state, action: PayloadAction) => { if ( - state.channels.findIndex( - (channel) => channel.index === action.payload.index, + state.radio.channels.findIndex( + (channel) => channel.channel.index === action.payload.index, ) !== -1 ) { - state.channels = state.channels.map((channel) => { - return channel.index === action.payload.index - ? action.payload + state.radio.channels = state.radio.channels.map((channel) => { + return channel.channel.index === action.payload.index + ? { + channel: action.payload, + messages: channel.messages, + } : channel; }); } else { - state.channels.push(action.payload); + state.radio.channels.push({ + channel: action.payload, + messages: [], + }); } }, setPreferences: ( state, action: PayloadAction, ) => { - state.preferences = action.payload; + state.radio.preferences = action.payload; }, addMessage: (state, action: PayloadAction) => { - state.messages.push(action.payload); + const channelIndex = state.radio.channels.findIndex( + (channel) => + channel.channel.index === action.payload.message.packet.channel, + ); + state.radio.channels[channelIndex].messages.push(action.payload); }, - ackMessage: (state, messageId: PayloadAction) => { - state.messages.map((message) => { - if (message.message.packet.id === messageId.payload) { + ackMessage: ( + state, + action: PayloadAction<{ channel: number; messageId: number }>, + ) => { + const channelIndex = state.radio.channels.findIndex( + (channel) => channel.channel.index === action.payload.channel, + ); + // todo: update last mesh/user interraction here + state.radio.channels[channelIndex].messages.map((message) => { + if (message.message.packet.id === action.payload.messageId) { message.ack = true; } }); @@ -143,21 +174,21 @@ export const meshtasticSlice = createSlice({ state.hostOverrideEnabled = action.payload; localStorage.setItem('hostOverrideEnabled', String(action.payload)); if (state.hostOverrideEnabled !== action.payload) { - connection.disconnect(); + // connection.disconnect(); } }, setHostOverride: (state, action: PayloadAction) => { state.hostOverride = action.payload; localStorage.setItem('hostOverride', action.payload); if (state.hostOverride !== action.payload) { - connection.disconnect(); + // connection.disconnect(); } }, setConnectionType: (state, action: PayloadAction) => { state.connectionType = action.payload; localStorage.setItem('connectionType', String(action.payload)); if (state.connectionType !== action.payload) { - connection.disconnect(); + // connection.disconnect(); } }, }, diff --git a/src/index.tsx b/src/index.tsx index 78d440ac..c99c7552 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,12 +6,11 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; +import { App } from '@app/App'; +import { ReloadPrompt } from '@components/pwa/ReloadPrompt'; import { RouteProvider } from '@core/router'; import { store } from '@core/store'; -import App from './App'; -import ReloadPrompt from './components/pwa/ReloadPrompt'; - ReactDOM.render( diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index b775207b..e0396996 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -1,4 +1,4 @@ -import type React from 'react'; +import React from 'react'; import { FiHash } from 'react-icons/fi'; @@ -10,9 +10,9 @@ import { Protobuf } from '@meshtastic/meshtasticjs'; import { useAppSelector } from '../hooks/redux'; export const Messages = (): JSX.Element => { - const messages = useAppSelector((state) => state.meshtastic.messages); - const users = useAppSelector((state) => state.meshtastic.users); - const channels = useAppSelector((state) => state.meshtastic.channels); + const nodes = useAppSelector((state) => state.meshtastic.nodes); + const channels = useAppSelector((state) => state.meshtastic.radio.channels); + const [channelIndex, setChannelIndex] = React.useState(0); return (
@@ -23,25 +23,28 @@ export const Messages = (): JSX.Element => { options={channels .filter( (channel) => - channel.role !== Protobuf.Channel_Role.DISABLED && - channel.settings?.name !== 'admin', + channel.channel.role !== Protobuf.Channel_Role.DISABLED && + channel.channel.settings?.name !== 'admin', ) .map((channel) => { return { - name: channel.settings?.name.length - ? channel.settings.name - : channel.role === Protobuf.Channel_Role.PRIMARY + name: channel.channel.settings?.name.length + ? channel.channel.settings.name + : channel.channel.role === Protobuf.Channel_Role.PRIMARY ? 'Primary' - : `CH: ${channel.index}`, - value: channel.index, + : `CH: ${channel.channel.index}`, + value: channel.channel.index, }; })} + onChange={(e): void => { + setChannelIndex(parseInt(e.target.value)); + }} small />
- {messages.map((message, index) => ( + {channels[channelIndex]?.messages.map((message, index) => ( { ack={message.ack} rxTime={new Date()} senderName={ - users.find( - (user) => user.packet.from === message.message.packet.from, - )?.data.longName ?? 'UNK' + nodes.find((node) => node.number === message.message.packet.from) + ?.user?.longName ?? 'UNK' } /> ))}
- +
); }; diff --git a/src/pages/Nodes/Index.tsx b/src/pages/Nodes/Index.tsx index ffb8ee89..46c8c110 100644 --- a/src/pages/Nodes/Index.tsx +++ b/src/pages/Nodes/Index.tsx @@ -9,23 +9,26 @@ import { Protobuf } from '@meshtastic/meshtasticjs'; import { Node } from './Node'; export const Nodes = (): JSX.Element => { - const nodes = useAppSelector((state) => state.meshtastic.nodes); - const users = useAppSelector((state) => state.meshtastic.users); + const myNodeNum = useAppSelector( + (state) => state.meshtastic.radio.hardware, + ).myNodeNum; + const nodes = useAppSelector((state) => state.meshtastic.nodes).filter( + (node) => node.number !== myNodeNum, + ); return ( { - const user = users.find((user) => user.packet.from === node.num)?.data; return { - title: user ? user.longName : node.num.toString(), - description: user - ? Protobuf.HardwareModel[user.hwModel] + title: node.user?.longName ?? node.number.toString(), + description: node.user + ? Protobuf.HardwareModel[node.user.hwModel] : 'Unknown Hardware', icon: ( diff --git a/src/pages/Nodes/Node.tsx b/src/pages/Nodes/Node.tsx index f399d53f..936fecc7 100644 --- a/src/pages/Nodes/Node.tsx +++ b/src/pages/Nodes/Node.tsx @@ -6,33 +6,24 @@ import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; import TimeAgo from 'timeago-react'; -import { Cover } from '@app/components/generic/Cover'; -import { useAppSelector } from '@app/hooks/redux'; import { Card } from '@components/generic/Card'; -import { Checkbox } from '@components/generic/form/Checkbox'; -import { Input } from '@components/generic/form/Input'; +import { Cover } from '@components/generic/Cover'; import { IconButton } from '@components/generic/IconButton'; import { StatCard } from '@components/generic/StatCard'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; -import type { Protobuf } from '@meshtastic/meshtasticjs'; +import type { Node as NodeType } from '@core/slices/meshtasticSlice'; export interface NodeProps { navOpen?: boolean; setNavOpen?: React.Dispatch>; - node: Protobuf.NodeInfo; + node: NodeType; } export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => { - const user = useAppSelector((state) => state.meshtastic.users).find( - (user) => user.packet.from === node.num, - )?.data; - const position = useAppSelector( - (state) => state.meshtastic.positionPackets, - ).find((position) => position.packet.from === node.num)?.data; const [debug, setDebug] = React.useState(false); return ( { }} /> } - footer={<>} >
- ) : ( - 'Never' - ) + node.lastHeard ? : 'Never' } />
- + } />
- -
-
- -
-
- - - - +
diff --git a/src/pages/Plugins/ExternalNotification.tsx b/src/pages/Plugins/ExternalNotification.tsx index f42bf053..8422ca28 100644 --- a/src/pages/Plugins/ExternalNotification.tsx +++ b/src/pages/Plugins/ExternalNotification.tsx @@ -1,16 +1,16 @@ -import type React from 'react'; +import React from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { FiMenu } from 'react-icons/fi'; import { FormFooter } from '@app/components/FormFooter'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; import { Card } from '@components/generic/Card'; import { Checkbox } from '@components/generic/form/Checkbox'; import { Input } from '@components/generic/form/Input'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated'; export interface ExternalNotificationProps { @@ -22,7 +22,9 @@ export const ExternalNotification = ({ navOpen, setNavOpen, }: ExternalNotificationProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const { register, handleSubmit, formState, reset, control } = useForm({ @@ -39,6 +41,19 @@ export const ExternalNotification = ({ }, }); + React.useEffect(() => { + reset({ + extNotificationPluginActive: preferences.extNotificationPluginActive, + extNotificationPluginAlertBell: + preferences.extNotificationPluginAlertBell, + extNotificationPluginAlertMessage: + preferences.extNotificationPluginAlertMessage, + extNotificationPluginEnabled: preferences.extNotificationPluginEnabled, + extNotificationPluginOutput: preferences.extNotificationPluginOutput, + extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs, + }); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { void connection.setPreferences(data); }); diff --git a/src/pages/Plugins/Files.tsx b/src/pages/Plugins/Files.tsx index 01504592..6ceb0da8 100644 --- a/src/pages/Plugins/Files.tsx +++ b/src/pages/Plugins/Files.tsx @@ -4,11 +4,11 @@ import type React from 'react'; import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi'; import useSWR from 'swr'; -import fetcher from '@app/core/utils/fetcher'; -import { useAppSelector } from '@app/hooks/redux'; import { Card } from '@components/generic/Card'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connectionUrl } from '@core/connection'; +import fetcher from '@core/utils/fetcher'; export interface RangeTestProps { navOpen?: boolean; @@ -33,20 +33,8 @@ interface IFiles { } export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => { - const hostOverrideEnabled = useAppSelector( - (state) => state.meshtastic.hostOverrideEnabled, - ); - const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride); - - const connectionURL = hostOverrideEnabled - ? hostOverride - : import.meta.env.PROD - ? window.location.hostname - : (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ?? - 'http://meshtastic.local'; - const { data } = useSWR( - `http://${connectionURL}/json/spiffs/browse/static`, + `http://${connectionUrl}/json/spiffs/browse/static`, fetcher, ); @@ -107,7 +95,7 @@ export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => { /> */}
{ className="mx-2 my-auto" // confirmAction={async (): Promise => { // await fetch( - // `http://${connectionURL}/json/spiffs/delete/static?remove=${file.name}`, + // `http://${connectionUrl}/json/spiffs/delete/static?remove=${file.name}`, // { // method: 'DELETE', // }, diff --git a/src/pages/Plugins/RangeTest.tsx b/src/pages/Plugins/RangeTest.tsx index 2ceb88b8..0824442a 100644 --- a/src/pages/Plugins/RangeTest.tsx +++ b/src/pages/Plugins/RangeTest.tsx @@ -1,16 +1,16 @@ -import type React from 'react'; +import React from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { FiMenu } from 'react-icons/fi'; -import { FormFooter } from '@app/components/FormFooter'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Checkbox } from '@components/generic/form/Checkbox'; import { Input } from '@components/generic/form/Input'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated'; export interface RangeTestProps { @@ -22,7 +22,9 @@ export const RangeTest = ({ navOpen, setNavOpen, }: RangeTestProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const { register, handleSubmit, formState, reset, control } = useForm({ @@ -33,6 +35,14 @@ export const RangeTest = ({ }, }); + React.useEffect(() => { + reset({ + rangeTestPluginEnabled: preferences.rangeTestPluginEnabled, + rangeTestPluginSave: preferences.rangeTestPluginSave, + rangeTestPluginSender: preferences.rangeTestPluginSender, + }); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { void connection.setPreferences(data); }); diff --git a/src/pages/Plugins/Serial.tsx b/src/pages/Plugins/Serial.tsx index ca528b14..84f44f5e 100644 --- a/src/pages/Plugins/Serial.tsx +++ b/src/pages/Plugins/Serial.tsx @@ -1,16 +1,16 @@ -import type React from 'react'; +import React from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { FiMenu } from 'react-icons/fi'; -import { FormFooter } from '@app/components/FormFooter'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Checkbox } from '@components/generic/form/Checkbox'; import { Input } from '@components/generic/form/Input'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated'; export interface SerialProps { @@ -19,7 +19,9 @@ export interface SerialProps { } export const Serial = ({ navOpen, setNavOpen }: SerialProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const { register, handleSubmit, formState, reset, control } = useForm({ @@ -33,6 +35,17 @@ export const Serial = ({ navOpen, setNavOpen }: SerialProps): JSX.Element => { }, }); + React.useEffect(() => { + reset({ + serialpluginEnabled: preferences.serialpluginEnabled, + serialpluginEcho: preferences.serialpluginEcho, + serialpluginMode: preferences.serialpluginMode, + serialpluginRxd: preferences.serialpluginRxd, + serialpluginTimeout: preferences.serialpluginTimeout, + serialpluginTxd: preferences.serialpluginTxd, + }); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { void connection.setPreferences(data); }); diff --git a/src/pages/settings/Channels.tsx b/src/pages/settings/Channels.tsx index fe96ea47..f12760c8 100644 --- a/src/pages/settings/Channels.tsx +++ b/src/pages/settings/Channels.tsx @@ -3,15 +3,15 @@ import React from 'react'; import { FiCode, FiMenu, FiSave } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; -import { LoraConfig } from '@app/components/LoraConfig'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; import { Channel } from '@components/Channel'; import { Button } from '@components/generic/Button'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; import { IconButton } from '@components/generic/IconButton'; +import { LoraConfig } from '@components/LoraConfig'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; export interface ChannelsProps { navOpen?: boolean; @@ -22,7 +22,7 @@ export const Channels = ({ navOpen, setNavOpen, }: ChannelsProps): JSX.Element => { - const channels = useAppSelector((state) => state.meshtastic.channels); + const channels = useAppSelector((state) => state.meshtastic.radio.channels); const [debug, setDebug] = React.useState(false); return ( @@ -58,15 +58,15 @@ export const Channels = ({ } >
- {channels[0] && } + {channels[0] && } } />
{channels.map((channel) => ( ))} diff --git a/src/pages/settings/Connection.tsx b/src/pages/settings/Connection.tsx deleted file mode 100644 index b40e6a78..00000000 --- a/src/pages/settings/Connection.tsx +++ /dev/null @@ -1,271 +0,0 @@ -import React from 'react'; - -import { useForm } from 'react-hook-form'; -import { FiCheck, FiMenu } from 'react-icons/fi'; -import JSONPretty from 'react-json-pretty'; - -import { FormFooter } from '@app/components/FormFooter'; -import { ble, connection, serial, setConnection } from '@app/core/connection'; -import { useAppSelector } from '@app/hooks/redux'; -import { Button } from '@components/generic/Button'; -import { Card } from '@components/generic/Card'; -import { Checkbox } from '@components/generic/form/Checkbox'; -import { Input } from '@components/generic/form/Input'; -import { Select } from '@components/generic/form/Select'; -import { IconButton } from '@components/generic/IconButton'; -import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; -import { - IBLEConnection, - IHTTPConnection, - ISerialConnection, -} from '@meshtastic/meshtasticjs'; -import type { - BLEConnectionParameters, - HTTPConnectionParameters, - SerialConnectionParameters, -} from '@meshtastic/meshtasticjs/dist/types'; - -export interface ConnectionProps { - navOpen?: boolean; - setNavOpen?: React.Dispatch>; -} - -enum connType { - HTTP, - BLE, - SERIAL, -} - -export const Connection = ({ - navOpen, - setNavOpen, -}: ConnectionProps): JSX.Element => { - const [selectedConnType, setSelectedConnType] = React.useState(connType.HTTP); - const [bleDevices, setBleDevices] = React.useState([]); - const [serialDevices, setSerialDevices] = React.useState([]); - const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>( - 'local', - ); - const hostOverrideEnabled = useAppSelector( - (state) => state.meshtastic.hostOverrideEnabled, - ); - const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride); - - const { formState, reset } = useForm<{ - method: connType; - }>({ - defaultValues: { - method: connType.HTTP, - }, - }); - - const connect = async ( - connectionType: connType, - params: - | HTTPConnectionParameters - | SerialConnectionParameters - | BLEConnectionParameters, - ): Promise => { - connection.complete(); - connection.disconnect(); - - if (connectionType === connType.BLE) { - setConnection(new IBLEConnection()); - } else if (connectionType === connType.HTTP) { - setConnection(new IHTTPConnection()); - } else { - setConnection(new ISerialConnection()); - } - - // @ts-ignore tmp - await connection.connect(params); - }; - - const updateBleDeviceList = React.useCallback(async (): Promise => { - const devices = await ble.getDevices(); - setBleDevices(devices); - }, []); - - const updateSerialDeviceList = React.useCallback(async (): Promise => { - const devices = await serial.getPorts(); - console.log(devices); - - setSerialDevices(devices); - }, []); - - React.useEffect(() => { - if (selectedConnType === connType.BLE) { - void updateBleDeviceList(); - } - if (selectedConnType === connType.SERIAL) { - void updateSerialDeviceList(); - } - }, [selectedConnType, updateBleDeviceList, updateSerialDeviceList]); - - const connectionURL: string = hostOverrideEnabled - ? hostOverride - : import.meta.env.PROD - ? window.location.hostname - : (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ?? - 'http://meshtastic.local'; - - return ( - } - onClick={(): void => { - setNavOpen && setNavOpen(!navOpen); - }} - /> - } - footer={ - { - return; - }} - clearAction={reset} - /> - } - > - -
-
- { - setHttpIpSource(e.target.value as 'local' | 'remote'); - }} - /> - {httpIpSource === 'local' ? ( - - ) : ( - - )} - - - )} - {selectedConnType === connType.BLE && ( -
-
- - -
-
-
Previously connected devices
- {bleDevices.map((device, index) => ( -
=> { - console.log('clicked'); - - await connect(connType.BLE, { - device: device, - }); - }} - className="flex justify-between p-2 bg-gray-700 rounded-md" - key={index} - > -
{device.name}
- => { - console.log('clicked'); - - await connect(connType.BLE, { - device: device, - }); - }} - icon={} - /> -
- ))} -
-
- )} - {selectedConnType === connType.SERIAL && ( -
-
- - -
-
-
Previously connected devices
- {serialDevices.map((device, index) => ( -
-
- {device.getInfo().usbProductId} - {device.getInfo().usbVendorId} -
- => { - await connect(connType.SERIAL, { - // @ts-ignore tmp - device: device, - }); - }} - icon={} - /> - -
- ))} -
-
- )} - - -
-
-
- ); -}; diff --git a/src/pages/settings/Index.tsx b/src/pages/settings/Index.tsx index 2f6c07a8..c4658561 100644 --- a/src/pages/settings/Index.tsx +++ b/src/pages/settings/Index.tsx @@ -3,7 +3,6 @@ import type React from 'react'; import { FiLayers, FiLayout, - FiLink2, FiMapPin, FiRadio, FiUser, @@ -14,7 +13,6 @@ import { import { PageLayout } from '@components/templates/PageLayout'; import { Channels } from './Channels'; -import { Connection } from './Connection'; import { Interface } from './Interface'; import { Position } from './Position'; import { Power } from './Power'; @@ -24,11 +22,6 @@ import { WiFi } from './WiFi'; export const Settings = (): JSX.Element => { const sidebarItems = [ - { - title: 'Connection', - description: 'Connection method and parameters', - icon: , - }, { title: 'WiFi', description: 'WiFi credentials and mode', @@ -70,7 +63,6 @@ export const Settings = (): JSX.Element => { title="Settings" sidebarItems={sidebarItems} panels={[ - , , , , diff --git a/src/pages/settings/Interface.tsx b/src/pages/settings/Interface.tsx index d89921e1..0c774baa 100644 --- a/src/pages/settings/Interface.tsx +++ b/src/pages/settings/Interface.tsx @@ -3,11 +3,11 @@ import type React from 'react'; import { useTranslation } from 'react-i18next'; import { FiMenu, FiSave } from 'react-icons/fi'; -import i18n from '@app/core/translation'; import { Button } from '@components/generic/Button'; import { Card } from '@components/generic/Card'; import { Select } from '@components/generic/form/Select'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import i18n from '@core/translation'; export interface InterfaceProps { navOpen?: boolean; diff --git a/src/pages/settings/Position.tsx b/src/pages/settings/Position.tsx index 69466bad..657257d4 100644 --- a/src/pages/settings/Position.tsx +++ b/src/pages/settings/Position.tsx @@ -4,9 +4,8 @@ import { useForm } from 'react-hook-form'; import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; -import { FormFooter } from '@app/components/FormFooter'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; import { Checkbox } from '@components/generic/form/Checkbox'; @@ -14,6 +13,7 @@ import { Input } from '@components/generic/form/Input'; import { Select } from '@components/generic/form/Select'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import { Protobuf } from '@meshtastic/meshtasticjs'; export interface PositionProps { @@ -25,7 +25,9 @@ export const Position = ({ navOpen, setNavOpen, }: PositionProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const [debug, setDebug] = React.useState(false); const [loading, setLoading] = React.useState(false); const { register, handleSubmit, formState, reset } = @@ -41,6 +43,10 @@ export const Position = ({ }, }); + React.useEffect(() => { + reset(preferences); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { setLoading(true); void connection.setPreferences(data, async () => { diff --git a/src/pages/settings/Power.tsx b/src/pages/settings/Power.tsx index 65ee9986..f18d5587 100644 --- a/src/pages/settings/Power.tsx +++ b/src/pages/settings/Power.tsx @@ -4,15 +4,15 @@ import { useForm } from 'react-hook-form'; import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; -import { FormFooter } from '@app/components/FormFooter'; -import { Select } from '@app/components/generic/form/Select'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; import { Checkbox } from '@components/generic/form/Checkbox'; +import { Select } from '@components/generic/form/Select'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import { Protobuf } from '@meshtastic/meshtasticjs'; export interface PowerProps { @@ -21,7 +21,9 @@ export interface PowerProps { } export const Power = ({ navOpen, setNavOpen }: PowerProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const [debug, setDebug] = React.useState(false); const [loading, setLoading] = React.useState(false); const { register, handleSubmit, formState, reset } = @@ -32,6 +34,10 @@ export const Power = ({ navOpen, setNavOpen }: PowerProps): JSX.Element => { }, }); + React.useEffect(() => { + reset(preferences); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { setLoading(true); void connection.setPreferences(data, async () => { diff --git a/src/pages/settings/Radio.tsx b/src/pages/settings/Radio.tsx index 9f213b72..c3f64078 100644 --- a/src/pages/settings/Radio.tsx +++ b/src/pages/settings/Radio.tsx @@ -4,15 +4,15 @@ import { useForm } from 'react-hook-form'; import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; -import { FormFooter } from '@app/components/FormFooter'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; import { Checkbox } from '@components/generic/form/Checkbox'; import { Select } from '@components/generic/form/Select'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import { Protobuf } from '@meshtastic/meshtasticjs'; export interface RadioProps { @@ -21,7 +21,9 @@ export interface RadioProps { } export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => { - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const [debug, setDebug] = React.useState(false); const [loading, setLoading] = React.useState(false); const { register, handleSubmit, formState, reset } = @@ -29,6 +31,10 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => { defaultValues: preferences, }); + React.useEffect(() => { + reset(preferences); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { setLoading(true); void connection.setPreferences(data, async () => { diff --git a/src/pages/settings/User.tsx b/src/pages/settings/User.tsx index 9a7ec3d2..e5a60eb3 100644 --- a/src/pages/settings/User.tsx +++ b/src/pages/settings/User.tsx @@ -5,8 +5,8 @@ import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; import { base16 } from 'rfc4648'; -import { FormFooter } from '@app/components/FormFooter'; -import { useAppDispatch, useAppSelector } from '@app/hooks/redux'; +import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; import { Checkbox } from '@components/generic/form/Checkbox'; @@ -15,7 +15,6 @@ import { Select } from '@components/generic/form/Select'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; import { connection } from '@core/connection'; -import { addUser } from '@core/slices/meshtasticSlice'; import { Protobuf } from '@meshtastic/meshtasticjs'; export interface UserProps { @@ -26,10 +25,11 @@ export interface UserProps { export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => { const [debug, setDebug] = React.useState(false); const [loading, setLoading] = React.useState(false); - const dispatch = useAppDispatch(); - const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo); - const user = useAppSelector((state) => state.meshtastic.users).find( - (user) => user.packet.from === myNodeInfo.myNodeNum, + const myNodeNum = useAppSelector( + (state) => state.meshtastic.radio.hardware, + ).myNodeNum; + const node = useAppSelector((state) => state.meshtastic.nodes).find( + (node) => node.number === myNodeNum, ); const { register, handleSubmit, formState, reset } = useForm<{ longName: string; @@ -38,22 +38,34 @@ export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => { team: Protobuf.Team; }>({ defaultValues: { - longName: user?.data.longName, - shortName: user?.data.shortName, - isLicensed: user?.data.isLicensed, - team: user?.data.team, + longName: node?.user?.longName, + shortName: node?.user?.shortName, + isLicensed: node?.user?.isLicensed, + team: node?.user?.team, }, }); + React.useEffect(() => { + reset({ + longName: node?.user?.longName, + shortName: node?.user?.shortName, + isLicensed: node?.user?.isLicensed, + team: node?.user?.team, + }); + }, [reset, node]); + const onSubmit = handleSubmit((data) => { setLoading(true); - // TODO: can be removed once getUser is implemented - if (user) { - void connection.setOwner({ ...user.data, ...data }, async () => { + + if (node?.user) { + void connection.setOwner({ ...node.user, ...data }, async () => { await Promise.resolve(); setLoading(false); }); - dispatch(addUser({ ...user, ...{ data: { ...user.data, ...data } } })); + // TODO: can be removed once getUser is implemented + // dispatch( + // addUser({ ...node.user, ...{ data: { ...node.user.data, ...data } } }), + // ); } }); @@ -87,15 +99,15 @@ export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => { } > - } /> + } />
- + { label="Mac Address" defaultValue={ base16 - .stringify(user?.data.macaddr ?? []) + .stringify(node?.user?.macaddr ?? []) .match(/.{1,2}/g) ?.join(':') ?? '' } diff --git a/src/pages/settings/WiFi.tsx b/src/pages/settings/WiFi.tsx index 58c85279..23c8ec0b 100644 --- a/src/pages/settings/WiFi.tsx +++ b/src/pages/settings/WiFi.tsx @@ -5,15 +5,15 @@ import { useTranslation } from 'react-i18next'; import { FiCode, FiMenu } from 'react-icons/fi'; import JSONPretty from 'react-json-pretty'; -import { FormFooter } from '@app/components/FormFooter'; -import { Checkbox } from '@app/components/generic/form/Checkbox'; -import { connection } from '@app/core/connection'; import { useAppSelector } from '@app/hooks/redux'; +import { FormFooter } from '@components/FormFooter'; import { Card } from '@components/generic/Card'; import { Cover } from '@components/generic/Cover'; +import { Checkbox } from '@components/generic/form/Checkbox'; import { Input } from '@components/generic/form/Input'; import { IconButton } from '@components/generic/IconButton'; import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; +import { connection } from '@core/connection'; import type { Protobuf } from '@meshtastic/meshtasticjs'; export interface WiFiProps { @@ -23,7 +23,9 @@ export interface WiFiProps { export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => { const { t } = useTranslation(); - const preferences = useAppSelector((state) => state.meshtastic.preferences); + const preferences = useAppSelector( + (state) => state.meshtastic.radio.preferences, + ); const [debug, setDebug] = React.useState(false); const [loading, setLoading] = React.useState(false); const { register, handleSubmit, formState, reset, control } = @@ -37,6 +39,10 @@ export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => { defaultValue: false, }); + React.useEffect(() => { + reset(preferences); + }, [reset, preferences]); + const onSubmit = handleSubmit((data) => { setLoading(true); void connection.setPreferences(data, async () => { diff --git a/todo.txt b/todo.txt index b35f24c2..377f28f3 100644 --- a/todo.txt +++ b/todo.txt @@ -1,17 +1,14 @@ Add desctiptions to form elements (below on mobile, to the right on desktop) -full width form elements on channel manager, don't use deprecated `modemConfig` add default value to undefined protobufs, (omit if default to keep them small (only for ota packets)) add input validation min,max etc -change ch type select to disable, make primary and delete buttons maybe make channel editor acordion? add url routing for settings tabs -form not updated if rendered before store populated, populated on remount, also disabled fields will remain disabled even if there disable props provided by redux are true, required toggling add loading blur to card (prop) form still considered dirty after save form prefix should be located in the input (absolute?) form suffix should focus input reset store on new connection +redux actions seem to be dispatched twice meshtastic.js -- either extrapolate user and position out of nodeInfo, or fire off events for all position and user packets even when contained in a nodeInfo packet, decide how to fire events for nodeInfo, wether we merge it or do something else - fix entering device-reconnecting state and not re-connecting despite packets being received \ No newline at end of file