Internal rework

This commit is contained in:
Sacha Weatherstone
2021-11-21 00:28:24 +11:00
parent 1f8170130d
commit 0cea0b2165
34 changed files with 903 additions and 803 deletions
+7 -6
View File
@@ -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"
}
}
+151 -139
View File
@@ -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
+7 -144
View File
@@ -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 (
<div
className={`h-screen w-screen ${darkMode ? 'dark rs-theme-dark' : ''}`}
>
<div className={`h-screen w-screen ${darkMode ? 'dark' : ''}`}>
<Connection />
<div className="flex flex-col h-full bg-gray-200 dark:bg-primaryDark">
<div className="flex flex-shrink-0 overflow-hidden bg-primary dark:bg-primary">
<div className="w-full overflow-hidden bg-white border-b border-gray-300 md:mt-6 md:mx-6 md:pt-4 md:pb-3 md:rounded-t-3xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
@@ -168,7 +33,7 @@ const App = (): JSX.Element => {
<Navigation className="hidden md:flex" />
<MobileNavToggle />
<div className="flex items-center space-x-2">
<DeviceStatusDropdown />
<DeviceStatus />
<ThemeToggle />
</div>
</div>
@@ -189,5 +54,3 @@ const App = (): JSX.Element => {
</div>
);
};
export default App;
+269
View File
@@ -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<BluetoothDevice[]>([]);
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
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<void> => {
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<void> => {
const devices = await ble.getDevices();
setBleDevices(devices);
}, []);
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
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 (
<Modal
open={connectionModalOpen}
onClose={(): void => {
dispatch(closeConnectionModal());
}}
>
<Card>
<div className="w-full max-w-3xl p-10 md:max-w-xl">
{ready ? (
<form className="space-y-2">
<Select
label="Method"
optionsEnum={connType}
value={selectedConnType}
onChange={(e): void => {
setSelectedConnType(parseInt(e.target.value));
}}
/>
{selectedConnType === connType.HTTP && (
<>
<Select
label="Host Source"
options={[
{
name: 'Local',
value: 'local',
},
{
name: 'Remote',
value: 'remote',
},
]}
value={httpIpSource}
onChange={(e): void => {
setHttpIpSource(e.target.value as 'local' | 'remote');
}}
/>
{httpIpSource === 'local' ? (
<Input label="Host" value={connectionUrl} disabled />
) : (
<Input label="Host" />
)}
<Checkbox label="Use TLS?" />
</>
)}
{selectedConnType === connType.BLE && (
<div>
<div className="flex space-x-2">
<Button border onClick={updateBleDeviceList}>
Refresh List
</Button>
<Button
border
onClick={async (): Promise<void> => {
await ble.getDevice();
}}
>
New Device
</Button>
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{bleDevices.map((device, index) => (
<div
onClick={async (): Promise<void> => {
await connect(connType.BLE, {
device: device,
});
}}
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={index}
>
<div className="my-auto">{device.name}</div>
<IconButton
onClick={async (): Promise<void> => {
await connect(connType.BLE, {
device: device,
});
}}
icon={<FiCheck />}
/>
</div>
))}
</div>
</div>
)}
{selectedConnType === connType.SERIAL && (
<div>
<div className="flex space-x-2">
<Button border onClick={updateSerialDeviceList}>
Refresh List
</Button>
<Button
border
onClick={async (): Promise<void> => {
console.log(await serial.getPort());
}}
>
New Device
</Button>
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{serialDevices.map((device, index) => (
<div
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={index}
>
<div className="my-auto">
{device.getInfo().usbProductId}
{device.getInfo().usbVendorId}
</div>
<IconButton
onClick={async (): Promise<void> => {
await connect(connType.SERIAL, {
// @ts-ignore tmp
device: device,
});
}}
icon={<FiCheck />}
/>
<JSONPretty data={device.getInfo()} />
</div>
))}
</div>
</div>
)}
</form>
) : (
<div>
<DeviceStatus />
</div>
)}
</div>
</Card>
</Modal>
);
};
+4 -5
View File
@@ -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;
}
+23 -14
View File
@@ -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<number>(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,
};
}),
]}
+2 -2
View File
@@ -15,14 +15,14 @@ export const Blur = ({
return (
<div
className={`absolute inset-0 z-20 w-full h-full transition-opacity ${
disableOnMd ? 'md:hidden' : 'test'
disableOnMd ? 'md:hidden' : ''
} ${className}`}
{...props}
>
<div
onClick={onClick}
className={`absolute inset-0 w-full h-full backdrop-filter backdrop-blur-sm ${
disableOnMd ? 'md:hidden' : 'test'
disableOnMd ? 'md:hidden' : ''
}`}
tabIndex={0}
></div>
+5 -1
View File
@@ -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'
+1 -1
View File
@@ -24,7 +24,7 @@ export const Card = ({
}: CardProps): JSX.Element => {
return (
<div
className={`relative flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none dark:bg-primaryDark border-gray-300 dark:border-transparent md:rounded-md ${className}`}
className={`relative flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none bg-white dark:bg-primaryDark border-gray-300 dark:border-gray-600 md:rounded-md ${className}`}
{...props}
>
{loading && <Loading />}
+37
View File
@@ -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 (
<>
<Dialog
as="div"
className={`fixed inset-0 z-30 ${darkMode ? 'dark' : ''}`}
open={open}
onClose={onClose}
>
<Dialog.Overlay className="fixed w-full h-full backdrop-filter backdrop-blur-sm" />
<div className="text-center ">
<span
className="inline-block h-screen align-middle "
aria-hidden="true"
>
&#8203;
</span>
<div className="inline-block w-full max-w-3xl align-middle">
{children}
</div>
</div>
</Dialog>
</>
);
};
+1 -1
View File
@@ -7,7 +7,7 @@ export interface StatCardProps {
export const StatCard = ({ title, value }: StatCardProps): JSX.Element => {
return (
<div className="w-full border-gray-300 shadow-md border-y md:border h-28 md:rounded-3xl dark:bg-primaryDark dark:border-transparent ">
<div className="w-full bg-white border-gray-300 shadow-md select-none dark:text-white border-y md:border dark:bg-primaryDark dark:border-gray-600 md:rounded-md ">
<div className="m-4">
<div className="text-lg font-light">{title}</div>
<div className="text-3xl font-bold">{value}</div>
@@ -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 (
<div className="flex bg-gray-100 rounded-md dark:bg-gray-700">
<div className="flex pl-2 my-auto space-x-2 dark:text-white">
<Button
padding={0}
active
onClick={(): void => {
dispatch(dispatch(openConnectionModal()));
}}
>
<div className="flex gap-2 px-2">
<div
className={`
my-auto mx-2 w-2 h-2 rounded-full min-w-[2] ${
my-auto w-2 h-2 rounded-full min-w-[2] ${
[
Types.DeviceStatusEnum.DEVICE_CONNECTED,
Types.DeviceStatusEnum.DEVICE_CONFIGURED,
@@ -31,16 +39,14 @@ export const DeviceStatusDropdown = (): JSX.Element => {
}`}
></div>
<div className="my-auto">{Types.DeviceStatusEnum[deviceStatus]}</div>
<IconButton
icon={
ready ? (
<FiWifi className="w-5 h-5" />
) : (
<FiWifiOff className="w-5 h-5 animate-pulse" />
)
}
/>
<div className="py-2">
{ready ? (
<FiWifi className="w-5 h-5" />
) : (
<FiWifiOff className="w-5 h-5 animate-pulse" />
)}
</div>
</div>
</div>
</Button>
);
};
+1 -3
View File
@@ -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 => {
</div>
);
};
export default ReloadPrompt;
+103
View File
@@ -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),
}),
);
});
};
+10
View File
@@ -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<boolean>) {
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;
+91 -60
View File
@@ -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<Protobuf.MyNodeInfo>) => {
state.myNodeInfo = action.payload;
state.radio.hardware = action.payload;
},
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
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<Types.PositionPacket>) => {
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<Protobuf.NodeInfo>) => {
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<Protobuf.Channel>) => {
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<Protobuf.RadioConfig_UserPreferences>,
) => {
state.preferences = action.payload;
state.radio.preferences = action.payload;
},
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
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<number>) => {
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<string>) => {
state.hostOverride = action.payload;
localStorage.setItem('hostOverride', action.payload);
if (state.hostOverride !== action.payload) {
connection.disconnect();
// connection.disconnect();
}
},
setConnectionType: (state, action: PayloadAction<connType>) => {
state.connectionType = action.payload;
localStorage.setItem('connectionType', String(action.payload));
if (state.connectionType !== action.payload) {
connection.disconnect();
// connection.disconnect();
}
},
},
+2 -3
View File
@@ -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(
<React.StrictMode>
<RouteProvider>
+18 -16
View File
@@ -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 (
<div className="flex flex-col w-full">
@@ -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
/>
</div>
</div>
<div className="flex flex-col flex-grow p-6 space-y-2 overflow-y-auto bg-white border-b border-gray-300 md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
{messages.map((message, index) => (
{channels[channelIndex]?.messages.map((message, index) => (
<Message
key={index}
isSender={message.isSender}
@@ -49,14 +52,13 @@ export const Messages = (): JSX.Element => {
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'
}
/>
))}
</div>
<MessageBar />
<MessageBar channelIndex={channelIndex} />
</div>
);
};
+10 -7
View File
@@ -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 (
<PageLayout
title="Nodes"
emptyMessage="No nodes discovered yet..."
sidebarItems={nodes.map((node) => {
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: (
<Avatar
size={30}
name={user ? user.longName : node.num.toString()}
name={node.user?.longName ?? node.number.toString()}
variant="beam"
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
/>
+7 -33
View File
@@ -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<React.SetStateAction<boolean>>;
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 (
<PrimaryTemplate
title={user ? user.longName : node.num.toString()}
title={node.user?.longName ?? node.number.toString()}
tagline="Node"
leftButton={
<IconButton
@@ -51,38 +42,21 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
}}
/>
}
footer={<></>}
>
<div className="w-full space-y-4">
<div className="flex flex-col justify-between gap-4 md:flex-row">
<StatCard
title="Last heard"
value={
node.lastHeard ? (
<TimeAgo datetime={new Date(node.lastHeard * 1000)} />
) : (
'Never'
)
node.lastHeard ? <TimeAgo datetime={node.lastHeard} /> : 'Never'
}
/>
<StatCard title="SNR" value={node.snr.toString()} />
</div>
<Card
title="Position"
description={new Date(node.lastHeard * 1000).toLocaleString()}
>
<Card title="Position" description={node.lastHeard.toLocaleString()}>
<Cover enabled={debug} content={<JSONPretty data={node} />} />
<div className="p-10">
<JSONPretty data={position} />
</div>
</Card>
<Card title="Settings" description="Remote node settings">
<div className="p-10">
<form className="space-y-4">
<Input label="Device Name" />
<Input label="Short Name" maxLength={3} />
<Checkbox label="Licenced Operator?" />
</form>
<JSONPretty data={node.positions[node.positions.length - 1]} />
</div>
</Card>
</div>
+18 -3
View File
@@ -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<RadioConfig_UserPreferences>({
@@ -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);
});
+5 -17
View File
@@ -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<IFiles>(
`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 => {
/> */}
</div>
<a
href={`http://${connectionURL}/${file.name.replace(
href={`http://${connectionUrl}/${file.name.replace(
'static/',
'',
)}`}
@@ -120,7 +108,7 @@ export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
className="mx-2 my-auto"
// confirmAction={async (): Promise<void> => {
// await fetch(
// `http://${connectionURL}/json/spiffs/delete/static?remove=${file.name}`,
// `http://${connectionUrl}/json/spiffs/delete/static?remove=${file.name}`,
// {
// method: 'DELETE',
// },
+14 -4
View File
@@ -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<RadioConfig_UserPreferences>({
@@ -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);
});
+17 -4
View File
@@ -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<RadioConfig_UserPreferences>({
@@ -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);
});
+7 -7
View File
@@ -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 = ({
}
>
<div className="space-y-4">
{channels[0] && <LoraConfig channel={channels[0]} />}
{channels[0] && <LoraConfig channel={channels[0].channel} />}
<Card>
<Cover enabled={debug} content={<JSONPretty data={channels} />} />
<div className="w-full p-4 space-y-2 md:p-10">
{channels.map((channel) => (
<Channel
key={channel.index}
channel={channel}
hideEnabled={channel.index === 0}
key={channel.channel.index}
channel={channel.channel}
hideEnabled={channel.channel.index === 0}
/>
))}
-271
View File
@@ -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<React.SetStateAction<boolean>>;
}
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<BluetoothDevice[]>([]);
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
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<void> => {
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<void> => {
const devices = await ble.getDevices();
setBleDevices(devices);
}, []);
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
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 (
<PrimaryTemplate
title="Connection"
tagline="Settings"
leftButton={
<IconButton
icon={<FiMenu className="w-5 h-5" />}
onClick={(): void => {
setNavOpen && setNavOpen(!navOpen);
}}
/>
}
footer={
<FormFooter
dirty={formState.isDirty}
saveAction={(): void => {
return;
}}
clearAction={reset}
/>
}
>
<Card>
<div className="w-full max-w-3xl p-10 md:max-w-xl">
<form className="space-y-2">
<Select
label="Method"
optionsEnum={connType}
value={selectedConnType}
onChange={(e): void => {
setSelectedConnType(parseInt(e.target.value));
}}
/>
{selectedConnType === connType.HTTP && (
<>
<Select
label="Host Source"
options={[
{
name: 'Local',
value: 'local',
},
{
name: 'Remote',
value: 'remote',
},
]}
value={httpIpSource}
onChange={(e): void => {
setHttpIpSource(e.target.value as 'local' | 'remote');
}}
/>
{httpIpSource === 'local' ? (
<Input label="Host" value={connectionURL} disabled />
) : (
<Input label="Host" />
)}
<Checkbox label="Use TLS?" />
</>
)}
{selectedConnType === connType.BLE && (
<div>
<div className="flex space-x-2">
<Button border onClick={updateBleDeviceList}>
Refresh List
</Button>
<Button
border
onClick={async (): Promise<void> => {
await ble.getDevice();
}}
>
New Device
</Button>
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{bleDevices.map((device, index) => (
<div
onClick={async (): Promise<void> => {
console.log('clicked');
await connect(connType.BLE, {
device: device,
});
}}
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={index}
>
<div className="my-auto">{device.name}</div>
<IconButton
onClick={async (): Promise<void> => {
console.log('clicked');
await connect(connType.BLE, {
device: device,
});
}}
icon={<FiCheck />}
/>
</div>
))}
</div>
</div>
)}
{selectedConnType === connType.SERIAL && (
<div>
<div className="flex space-x-2">
<Button border onClick={updateSerialDeviceList}>
Refresh List
</Button>
<Button
border
onClick={async (): Promise<void> => {
console.log(await serial.getPort());
}}
>
New Device
</Button>
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{serialDevices.map((device, index) => (
<div
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={index}
>
<div className="my-auto">
{device.getInfo().usbProductId}
{device.getInfo().usbVendorId}
</div>
<IconButton
onClick={async (): Promise<void> => {
await connect(connType.SERIAL, {
// @ts-ignore tmp
device: device,
});
}}
icon={<FiCheck />}
/>
<JSONPretty data={device.getInfo()} />
</div>
))}
</div>
</div>
)}
</form>
<Button
border
onClick={(): void => {
connection.disconnect();
}}
>
Disconnect
</Button>
</div>
</Card>
</PrimaryTemplate>
);
};
-8
View File
@@ -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: <FiLink2 className="flex-shrink-0 w-6 h-6" />,
},
{
title: 'WiFi',
description: 'WiFi credentials and mode',
@@ -70,7 +63,6 @@ export const Settings = (): JSX.Element => {
title="Settings"
sidebarItems={sidebarItems}
panels={[
<Connection key={1} />,
<WiFi key={2} />,
<Position key={3} />,
<User key={4} />,
+1 -1
View File
@@ -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;
+9 -3
View File
@@ -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 () => {
+10 -4
View File
@@ -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 () => {
+9 -3
View File
@@ -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 () => {
+31 -19
View File
@@ -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 => {
}
>
<Card loading={loading}>
<Cover enabled={debug} content={<JSONPretty data={user?.data} />} />
<Cover enabled={debug} content={<JSONPretty data={node?.user} />} />
<div className="w-full max-w-3xl p-10 md:max-w-xl">
<form className="space-y-2" onSubmit={onSubmit}>
<Input label="Device ID" value={user?.data.id} disabled />
<Input label="Device ID" value={node?.user?.id} disabled />
<Input
label="Hardware"
value={
Protobuf.HardwareModel[
user?.data.hwModel ?? Protobuf.HardwareModel.UNSET
node?.user?.hwModel ?? Protobuf.HardwareModel.UNSET
]
}
disabled
@@ -104,7 +116,7 @@ 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(':') ?? ''
}
+10 -4
View File
@@ -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 () => {
+1 -4
View File
@@ -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