Add new node info panel
This commit is contained in:
+5
-4
@@ -26,13 +26,14 @@
|
|||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-file-icon": "^1.1.0",
|
"react-file-icon": "^1.1.0",
|
||||||
"react-hook-form": "^7.22.5",
|
"react-hook-form": "^7.23.0",
|
||||||
"react-i18next": "^11.15.3",
|
"react-i18next": "^11.15.3",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-qr-code": "^2.0.3",
|
"react-qr-code": "^2.0.3",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-select": "^5.2.1",
|
"react-select": "^5.2.2",
|
||||||
|
"react-use-clipboard": "^1.0.7",
|
||||||
"rfc4648": "^1.5.1",
|
"rfc4648": "^1.5.1",
|
||||||
"swr": "^1.1.2",
|
"swr": "^1.1.2",
|
||||||
"timeago-react": "^3.0.4",
|
"timeago-react": "^3.0.4",
|
||||||
@@ -66,9 +67,9 @@
|
|||||||
"tailwindcss": "^3.0.13",
|
"tailwindcss": "^3.0.13",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"vite": "^2.7.10",
|
"vite": "^2.7.12",
|
||||||
"vite-plugin-cdn-import": "^0.3.5",
|
"vite-plugin-cdn-import": "^0.3.5",
|
||||||
"vite-plugin-pwa": "^0.11.12",
|
"vite-plugin-pwa": "^0.11.13",
|
||||||
"workbox-window": "^6.4.2"
|
"workbox-window": "^6.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+55
-32
@@ -38,13 +38,14 @@ specifiers:
|
|||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
react-error-boundary: ^3.1.4
|
react-error-boundary: ^3.1.4
|
||||||
react-file-icon: ^1.1.0
|
react-file-icon: ^1.1.0
|
||||||
react-hook-form: ^7.22.5
|
react-hook-form: ^7.23.0
|
||||||
react-i18next: ^11.15.3
|
react-i18next: ^11.15.3
|
||||||
react-icons: ^4.3.1
|
react-icons: ^4.3.1
|
||||||
react-json-pretty: ^2.2.0
|
react-json-pretty: ^2.2.0
|
||||||
react-qr-code: ^2.0.3
|
react-qr-code: ^2.0.3
|
||||||
react-redux: ^7.2.6
|
react-redux: ^7.2.6
|
||||||
react-select: ^5.2.1
|
react-select: ^5.2.2
|
||||||
|
react-use-clipboard: ^1.0.7
|
||||||
rfc4648: ^1.5.1
|
rfc4648: ^1.5.1
|
||||||
swr: ^1.1.2
|
swr: ^1.1.2
|
||||||
tailwindcss: ^3.0.13
|
tailwindcss: ^3.0.13
|
||||||
@@ -53,9 +54,9 @@ specifiers:
|
|||||||
type-route: ^0.6.0
|
type-route: ^0.6.0
|
||||||
typescript: ^4.5.4
|
typescript: ^4.5.4
|
||||||
use-breakpoint: ^3.0.1
|
use-breakpoint: ^3.0.1
|
||||||
vite: ^2.7.10
|
vite: ^2.7.12
|
||||||
vite-plugin-cdn-import: ^0.3.5
|
vite-plugin-cdn-import: ^0.3.5
|
||||||
vite-plugin-pwa: ^0.11.12
|
vite-plugin-pwa: ^0.11.13
|
||||||
workbox-window: ^6.4.2
|
workbox-window: ^6.4.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -73,13 +74,14 @@ dependencies:
|
|||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
react-error-boundary: 3.1.4_react@17.0.2
|
react-error-boundary: 3.1.4_react@17.0.2
|
||||||
react-file-icon: 1.1.0_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.22.5_react@17.0.2
|
react-hook-form: 7.23.0_react@17.0.2
|
||||||
react-i18next: 11.15.3_bc514be083f1f06b28df24d5713fc600
|
react-i18next: 11.15.3_bc514be083f1f06b28df24d5713fc600
|
||||||
react-icons: 4.3.1_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-json-pretty: 2.2.0_react-dom@17.0.2+react@17.0.2
|
||||||
react-qr-code: 2.0.3_react@17.0.2
|
react-qr-code: 2.0.3_react@17.0.2
|
||||||
react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2
|
react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2
|
||||||
react-select: 5.2.1_b3482aaf5744fc7c2aeb7941b0e0a78f
|
react-select: 5.2.2_b3482aaf5744fc7c2aeb7941b0e0a78f
|
||||||
|
react-use-clipboard: 1.0.7_react-dom@17.0.2+react@17.0.2
|
||||||
rfc4648: 1.5.1
|
rfc4648: 1.5.1
|
||||||
swr: 1.1.2_react@17.0.2
|
swr: 1.1.2_react@17.0.2
|
||||||
timeago-react: 3.0.4_react@17.0.2
|
timeago-react: 3.0.4_react@17.0.2
|
||||||
@@ -113,9 +115,9 @@ devDependencies:
|
|||||||
tailwindcss: 3.0.13_ef48b3b8837f8a23677bffe8f9cd866d
|
tailwindcss: 3.0.13_ef48b3b8837f8a23677bffe8f9cd866d
|
||||||
tar: 6.1.11
|
tar: 6.1.11
|
||||||
typescript: 4.5.4
|
typescript: 4.5.4
|
||||||
vite: 2.7.10
|
vite: 2.7.12
|
||||||
vite-plugin-cdn-import: 0.3.5
|
vite-plugin-cdn-import: 0.3.5
|
||||||
vite-plugin-pwa: 0.11.12_vite@2.7.10
|
vite-plugin-pwa: 0.11.13_vite@2.7.12
|
||||||
workbox-window: 6.4.2
|
workbox-window: 6.4.2
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
@@ -1529,7 +1531,7 @@ packages:
|
|||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
react-icons: 4.3.1_react@17.0.2
|
react-icons: 4.3.1_react@17.0.2
|
||||||
react-select: 5.2.1_b3482aaf5744fc7c2aeb7941b0e0a78f
|
react-select: 5.2.2_b3482aaf5744fc7c2aeb7941b0e0a78f
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
@@ -2201,7 +2203,7 @@ packages:
|
|||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.19.1
|
browserslist: 4.19.1
|
||||||
caniuse-lite: 1.0.30001298
|
caniuse-lite: 1.0.30001299
|
||||||
fraction.js: 4.1.2
|
fraction.js: 4.1.2
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
@@ -2310,8 +2312,8 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001298
|
caniuse-lite: 1.0.30001299
|
||||||
electron-to-chromium: 1.4.42
|
electron-to-chromium: 1.4.45
|
||||||
escalade: 3.1.1
|
escalade: 3.1.1
|
||||||
node-releases: 2.0.1
|
node-releases: 2.0.1
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
@@ -2350,8 +2352,8 @@ packages:
|
|||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/caniuse-lite/1.0.30001298:
|
/caniuse-lite/1.0.30001299:
|
||||||
resolution: {integrity: sha512-AcKqikjMLlvghZL/vfTHorlQsLDhGRalYf1+GmWCf5SCMziSGjRYQW/JEksj14NaYHIR6KIhrFAy0HV5C25UzQ==}
|
resolution: {integrity: sha512-iujN4+x7QzqA2NCSrS5VUy+4gLmRd4xv6vbBBsmfVqTx8bLAD8097euLqQgKxSVLvxjSDcvF1T/i9ocgnUFexw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chalk/2.4.2:
|
/chalk/2.4.2:
|
||||||
@@ -2458,6 +2460,12 @@ packages:
|
|||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/copy-to-clipboard/3.3.1:
|
||||||
|
resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==}
|
||||||
|
dependencies:
|
||||||
|
toggle-selection: 1.0.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
/core-js-compat/3.20.2:
|
/core-js-compat/3.20.2:
|
||||||
resolution: {integrity: sha512-qZEzVQ+5Qh6cROaTPFLNS4lkvQ6mBzE3R6A6EEpssj7Zr2egMHgsy4XapdifqJDGC9CBiNv7s+ejI96rLNQFdg==}
|
resolution: {integrity: sha512-qZEzVQ+5Qh6cROaTPFLNS4lkvQ6mBzE3R6A6EEpssj7Zr2egMHgsy4XapdifqJDGC9CBiNv7s+ejI96rLNQFdg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2551,7 +2559,7 @@ packages:
|
|||||||
object-is: 1.1.5
|
object-is: 1.1.5
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
object.assign: 4.1.2
|
object.assign: 4.1.2
|
||||||
regexp.prototype.flags: 1.3.1
|
regexp.prototype.flags: 1.3.2
|
||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
which-boxed-primitive: 1.0.2
|
which-boxed-primitive: 1.0.2
|
||||||
which-collection: 1.0.1
|
which-collection: 1.0.1
|
||||||
@@ -2670,8 +2678,8 @@ packages:
|
|||||||
jake: 10.8.2
|
jake: 10.8.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/electron-to-chromium/1.4.42:
|
/electron-to-chromium/1.4.45:
|
||||||
resolution: {integrity: sha512-JJLT8bjdswJzk8sNRnQjee0MGtO4zTn1t7eWwYPr8gPTadQgNRR/wFRKLGD6HZVZby39yHERkvuCVKNm10r7Dg==}
|
resolution: {integrity: sha512-czF9eYVuOmlY/vxyMQz2rGlNSjZpxNQYBe1gmQv7al171qOIhgyO9k7D5AKlgeTCSPKk+LHhj5ZyIdmEub9oNg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/emoji-regex/8.0.0:
|
/emoji-regex/8.0.0:
|
||||||
@@ -4220,8 +4228,8 @@ packages:
|
|||||||
resolution: {integrity: sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=}
|
resolution: {integrity: sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/nanoid/3.1.31:
|
/nanoid/3.1.32:
|
||||||
resolution: {integrity: sha512-ZivnJm0o9bb13p2Ot5CpgC2rQdzB9Uxm/mFZweqm5eMViqOJe3PV6LU2E30SiLgheesmcPrjquqraoolONSA0A==}
|
resolution: {integrity: sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4541,7 +4549,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
|
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.1.31
|
nanoid: 3.1.32
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.1
|
source-map-js: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4699,8 +4707,8 @@ packages:
|
|||||||
tinycolor2: 1.4.2
|
tinycolor2: 1.4.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-hook-form/7.22.5_react@17.0.2:
|
/react-hook-form/7.23.0_react@17.0.2:
|
||||||
resolution: {integrity: sha512-Q2zaeQFXdVQ8l3hcywhltH+Nzj4vo50wMVujHDVN/1Xy9IOaSZJwYBXA2CYTpK6rq41fnXviw3jTLb04c7Gu9Q==}
|
resolution: {integrity: sha512-bO1JCkPAjmpuKhfUpFhsjWn2RIPgWUpep8qpMAKCoc8NM8ytBA5nDx5p99wNhZWrblYQFvU+dVy9g1oYo/JKoQ==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17
|
react: ^16.8.0 || ^17
|
||||||
@@ -4793,8 +4801,8 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-select/5.2.1_b3482aaf5744fc7c2aeb7941b0e0a78f:
|
/react-select/5.2.2_b3482aaf5744fc7c2aeb7941b0e0a78f:
|
||||||
resolution: {integrity: sha512-OOyNzfKrhOcw/BlembyGWgdlJ2ObZRaqmQppPFut1RptJO423j+Y+JIsmxkvsZ4D/3CpOmwIlCvWbbAWEdh12A==}
|
resolution: {integrity: sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0
|
react: ^16.8.0 || ^17.0.0
|
||||||
react-dom: ^16.8.0 || ^17.0.0
|
react-dom: ^16.8.0 || ^17.0.0
|
||||||
@@ -4827,6 +4835,17 @@ packages:
|
|||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-use-clipboard/1.0.7_react-dom@17.0.2+react@17.0.2:
|
||||||
|
resolution: {integrity: sha512-blIprqARyITp0uVw/2Rh87mcujqXdH6vZ5NrcuXEhI5EmjBGxcGnwt/79+vdN7rwM6OliGj481lOj6ZCcsiYEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17
|
||||||
|
react-dom: ^16.8.0 || ^17
|
||||||
|
dependencies:
|
||||||
|
copy-to-clipboard: 3.3.1
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 17.0.2_react@17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react/17.0.2:
|
/react/17.0.2:
|
||||||
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -4897,8 +4916,8 @@ packages:
|
|||||||
'@babel/runtime': 7.16.7
|
'@babel/runtime': 7.16.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/regexp.prototype.flags/1.3.1:
|
/regexp.prototype.flags/1.3.2:
|
||||||
resolution: {integrity: sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==}
|
resolution: {integrity: sha512-uaro52GSI5be7+ssxjxxnLlleDBN3VHIWQHvBhfeeSXRQkuV/0Jo/hBU+omYH6NUkM+LYpTHnRRf2W/v+x7LzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
@@ -5212,7 +5231,7 @@ packages:
|
|||||||
get-intrinsic: 1.1.1
|
get-intrinsic: 1.1.1
|
||||||
has-symbols: 1.0.2
|
has-symbols: 1.0.2
|
||||||
internal-slot: 1.0.3
|
internal-slot: 1.0.3
|
||||||
regexp.prototype.flags: 1.3.1
|
regexp.prototype.flags: 1.3.2
|
||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -5492,6 +5511,10 @@ packages:
|
|||||||
is-number: 7.0.0
|
is-number: 7.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/toggle-selection/1.0.6:
|
||||||
|
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tr46/1.0.1:
|
/tr46/1.0.1:
|
||||||
resolution: {integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=}
|
resolution: {integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5661,8 +5684,8 @@ packages:
|
|||||||
- rollup
|
- rollup
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-plugin-pwa/0.11.12_vite@2.7.10:
|
/vite-plugin-pwa/0.11.13_vite@2.7.12:
|
||||||
resolution: {integrity: sha512-XqFmA4y9C4RBb5osSsa26GVwOSwbzf2GNVcT5+06KYYdguqLpuI9FW7iV/akZqg0OUNUpH4tHfme8SnHA4PIXA==}
|
resolution: {integrity: sha512-Ssj14m3TRVLfkFEAWSMcFE2d1cSdEZyrVTzfY2lSL+umHYvcIFHVDAY143sygtBCb44OPczsAOmWwBTxwOvh7g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^2.0.0
|
vite: ^2.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5670,7 +5693,7 @@ packages:
|
|||||||
fast-glob: 3.2.10
|
fast-glob: 3.2.10
|
||||||
pretty-bytes: 5.6.0
|
pretty-bytes: 5.6.0
|
||||||
rollup: 2.63.0
|
rollup: 2.63.0
|
||||||
vite: 2.7.10
|
vite: 2.7.12
|
||||||
workbox-build: 6.4.2
|
workbox-build: 6.4.2
|
||||||
workbox-window: 6.4.2
|
workbox-window: 6.4.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5679,8 +5702,8 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite/2.7.10:
|
/vite/2.7.12:
|
||||||
resolution: {integrity: sha512-KEY96ntXUid1/xJihJbgmLZx7QSC2D4Tui0FdS0Old5OokYzFclcofhtxtjDdGOk/fFpPbHv9yw88+rB93Tb8w==}
|
resolution: {integrity: sha512-KvPYToRQWhRfBeVkyhkZ5hASuHQkqZUUdUcE3xyYtq5oYEPIJ0h9LWiWTO6v990glmSac2cEPeYeXzpX5Z6qKQ==}
|
||||||
engines: {node: '>=12.2.0'}
|
engines: {node: '>=12.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<Disclosure.Panel className="p-2 border-t">
|
<Disclosure.Panel className="p-2 border-t border-gray-300 dark:border-gray-600">
|
||||||
{loading && <Loading />}
|
{loading && <Loading />}
|
||||||
<div className="flex px-2 my-auto">
|
<div className="flex px-2 my-auto">
|
||||||
<form className="w-full gap-3">
|
<form className="w-full gap-3">
|
||||||
|
|||||||
@@ -2,20 +2,17 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import mapbox from 'mapbox-gl';
|
import mapbox from 'mapbox-gl';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
|
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
|
|
||||||
export interface MarkerProps extends Omit<mapbox.MarkerOptions, 'element'> {
|
export interface MarkerProps extends Omit<mapbox.MarkerOptions, 'element'> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
center: mapbox.LngLatLike;
|
center: mapbox.LngLatLike;
|
||||||
popup: JSX.Element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Marker = ({
|
export const Marker = ({
|
||||||
children,
|
children,
|
||||||
center,
|
center,
|
||||||
popup,
|
|
||||||
...props
|
...props
|
||||||
}: MarkerProps): JSX.Element => {
|
}: MarkerProps): JSX.Element => {
|
||||||
const { map } = useMapbox();
|
const { map } = useMapbox();
|
||||||
@@ -23,14 +20,10 @@ export const Marker = ({
|
|||||||
|
|
||||||
const addMarker = React.useCallback((): void => {
|
const addMarker = React.useCallback((): void => {
|
||||||
if (map) {
|
if (map) {
|
||||||
const marker = new mapbox.Marker(ref.current, props)
|
const marker = new mapbox.Marker(ref.current, props).setLngLat(center);
|
||||||
.setLngLat(center)
|
|
||||||
.setPopup(
|
|
||||||
new mapbox.Popup().setHTML(ReactDOMServer.renderToString(popup)),
|
|
||||||
);
|
|
||||||
marker.addTo(map);
|
marker.addTo(map);
|
||||||
}
|
}
|
||||||
}, [map, center, props, popup]);
|
}, [map, center, props]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
map?.on('load', () => {
|
map?.on('load', () => {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { Tab } from '@headlessui/react';
|
||||||
|
|
||||||
|
export interface TabButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TabButton = ({ children }: TabButtonProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Tab
|
||||||
|
className={({ selected }): string =>
|
||||||
|
`border-gray-300 hover:border-b-2 dark:border-gray-600 w-full ${
|
||||||
|
selected ? 'border-b-2' : 'border-b-0'
|
||||||
|
} `
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="my-auto text-gray-500 group dark:text-gray-400">
|
||||||
|
<div className="flex p-2 rounded-t-md hover:bg-gray-200 dark:hover:bg-gray-600">
|
||||||
|
<div className="m-auto transition duration-200 ease-in-out group-active:scale-90">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -33,3 +33,26 @@
|
|||||||
box-sizing: unset !important;
|
box-sizing: unset !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.__json-pretty__ {
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #748096;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.__json-pretty__ .__json-key__ {
|
||||||
|
color: #b553bf;
|
||||||
|
}
|
||||||
|
.__json-pretty__ .__json-value__ {
|
||||||
|
color: #93a3bf;
|
||||||
|
}
|
||||||
|
.__json-pretty__ .__json-string__ {
|
||||||
|
color: #fba856;
|
||||||
|
}
|
||||||
|
.__json-pretty__ .__json-boolean__ {
|
||||||
|
color: #448aa9;
|
||||||
|
}
|
||||||
|
.__json-pretty-error__ {
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #748096;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import '@meshtastic/components/dist/style.css';
|
import '@meshtastic/components/dist/style.css';
|
||||||
import '@app/index.css';
|
import '@app/index.css';
|
||||||
import 'react-json-pretty/themes/monikai.css';
|
|
||||||
import '@core/translation';
|
import '@core/translation';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { FiXCircle } from 'react-icons/fi';
|
import mapbox from 'mapbox-gl';
|
||||||
|
import { FiMapPin, FiXCircle } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { Marker } from '@app/components/Map/Marker';
|
||||||
|
import type { Node } from '@app/core/slices/meshtasticSlice.js';
|
||||||
import { Drawer } from '@components/generic/Drawer';
|
import { Drawer } from '@components/generic/Drawer';
|
||||||
import { Map } from '@components/Map';
|
import { Map } from '@components/Map';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
@@ -9,6 +12,7 @@ import { useBreakpoint } from '@hooks/useBreakpoint';
|
|||||||
import { IconButton } from '@meshtastic/components';
|
import { IconButton } from '@meshtastic/components';
|
||||||
|
|
||||||
import { NodeCard } from './NodeCard';
|
import { NodeCard } from './NodeCard';
|
||||||
|
import { Sidebar } from './Sidebar';
|
||||||
|
|
||||||
export const Nodes = (): JSX.Element => {
|
export const Nodes = (): JSX.Element => {
|
||||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.radio.hardware);
|
const myNodeInfo = useAppSelector((state) => state.meshtastic.radio.hardware);
|
||||||
@@ -25,6 +29,8 @@ export const Nodes = (): JSX.Element => {
|
|||||||
const [navOpen, setNavOpen] = React.useState(false);
|
const [navOpen, setNavOpen] = React.useState(false);
|
||||||
|
|
||||||
const { breakpoint } = useBreakpoint();
|
const { breakpoint } = useBreakpoint();
|
||||||
|
const [sidebarOpen, setSidebarOpen] = React.useState(false);
|
||||||
|
const [selectedNode, setSelectedNode] = React.useState<Node | undefined>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full dark:text-white">
|
<div className="relative flex w-full dark:text-white">
|
||||||
@@ -36,7 +42,7 @@ export const Nodes = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between m-6 mr-6">
|
<div className="flex items-center justify-between m-6 mr-6">
|
||||||
<div className="text-4xl font-extrabold leading-none tracking-tight">
|
<div className="text-3xl font-extrabold leading-none tracking-tight">
|
||||||
Nodes
|
Nodes
|
||||||
</div>
|
</div>
|
||||||
<div className="md:hidden">
|
<div className="md:hidden">
|
||||||
@@ -53,15 +59,67 @@ export const Nodes = (): JSX.Element => {
|
|||||||
No nodes found.
|
No nodes found.
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{myNode && <NodeCard node={myNode} myNodeInfo={myNodeInfo} />}
|
{myNode && (
|
||||||
|
<NodeCard
|
||||||
|
node={myNode}
|
||||||
|
isMyNode={true}
|
||||||
|
setSelected={(): void => {
|
||||||
|
setSelectedNode(myNode);
|
||||||
|
setSidebarOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{nodes
|
{nodes
|
||||||
.filter((node) => node.number !== myNodeInfo.myNodeNum)
|
.filter((node) => node.number !== myNodeInfo.myNodeNum)
|
||||||
.map((node) => (
|
.map((node) => (
|
||||||
<NodeCard key={node.number} node={node} />
|
<NodeCard
|
||||||
|
key={node.number}
|
||||||
|
node={node}
|
||||||
|
setSelected={(): void => {
|
||||||
|
setSelectedNode(node);
|
||||||
|
setSidebarOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
{nodes.map((node) => {
|
||||||
|
return (
|
||||||
|
node.currentPosition && (
|
||||||
|
<Marker
|
||||||
|
center={
|
||||||
|
new mapbox.LngLat(
|
||||||
|
node.currentPosition.longitudeI / 1e7,
|
||||||
|
node.currentPosition.latitudeI / 1e7,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={(): void => {
|
||||||
|
setSelectedNode(node);
|
||||||
|
setSidebarOpen(true);
|
||||||
|
}}
|
||||||
|
className="z-50 bg-blue-500 border-2 border-blue-500 rounded-full bg-opacity-30"
|
||||||
|
>
|
||||||
|
<div className="m-4 ">
|
||||||
|
<FiMapPin className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Marker>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
<Map />
|
<Map />
|
||||||
|
|
||||||
|
{sidebarOpen && selectedNode && (
|
||||||
|
<Sidebar
|
||||||
|
closeSidebar={(): void => {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}}
|
||||||
|
node={selectedNode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+77
-182
@@ -1,51 +1,42 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import mapbox from 'mapbox-gl';
|
import mapbox from 'mapbox-gl';
|
||||||
import { FaSatellite } from 'react-icons/fa';
|
import { FiAlignLeft } from 'react-icons/fi';
|
||||||
import { FiCode, FiMapPin } from 'react-icons/fi';
|
|
||||||
import { GiLightningFrequency } from 'react-icons/gi';
|
|
||||||
import {
|
import {
|
||||||
MdAccountCircle,
|
MdAccountCircle,
|
||||||
MdArrowDropDown,
|
|
||||||
MdArrowDropUp,
|
|
||||||
MdGpsFixed,
|
MdGpsFixed,
|
||||||
MdGpsNotFixed,
|
MdGpsNotFixed,
|
||||||
MdGpsOff,
|
MdGpsOff,
|
||||||
MdSdStorage,
|
|
||||||
MdSignalCellularAlt,
|
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
import TimeAgo from 'timeago-react';
|
import TimeAgo from 'timeago-react';
|
||||||
|
|
||||||
import { Modal } from '@components/generic/Modal';
|
|
||||||
import { Marker } from '@components/Map/Marker';
|
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { Disclosure } from '@headlessui/react';
|
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
import { Card, IconButton } from '@meshtastic/components';
|
import { IconButton } from '@meshtastic/components';
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
|
||||||
|
|
||||||
type PositionConfidence = 'high' | 'low' | 'none';
|
type PositionConfidence = 'high' | 'low' | 'none';
|
||||||
type NodeAge = 'young' | 'aging' | 'old' | 'dead';
|
type NodeAge = 'young' | 'aging' | 'old' | 'dead';
|
||||||
|
|
||||||
export interface NodeCardProps {
|
export interface NodeCardProps {
|
||||||
node: Node;
|
node: Node;
|
||||||
myNodeInfo?: Protobuf.MyNodeInfo;
|
isMyNode?: boolean;
|
||||||
|
setSelected: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
|
export const NodeCard = ({
|
||||||
const [snrAverage, setSnrAverage] = React.useState(0);
|
node,
|
||||||
const [satsAverage, setSatsAverage] = React.useState(0);
|
isMyNode,
|
||||||
const [showDebug, setShowDebug] = React.useState(false);
|
setSelected,
|
||||||
|
}: NodeCardProps): JSX.Element => {
|
||||||
const { map } = useMapbox();
|
const { map } = useMapbox();
|
||||||
|
|
||||||
React.useEffect(() => {
|
// React.useEffect(() => {
|
||||||
setSnrAverage(
|
// setSnrAverage(
|
||||||
node.snr
|
// node.snr
|
||||||
.slice(node.snr.length - 3, node.snr.length)
|
// .slice(node.snr.length - 3, node.snr.length)
|
||||||
.reduce((a, b) => a + b) / (node.snr.length > 3 ? 3 : node.snr.length),
|
// .reduce((a, b) => a + b) / (node.snr.length > 3 ? 3 : node.snr.length),
|
||||||
);
|
// );
|
||||||
}, [node.snr]);
|
// }, [node.snr]);
|
||||||
const [PositionConfidence, setPositionConfidence] =
|
const [PositionConfidence, setPositionConfidence] =
|
||||||
React.useState<PositionConfidence>('none');
|
React.useState<PositionConfidence>('none');
|
||||||
const [age, setAge] = React.useState<NodeAge>('young');
|
const [age, setAge] = React.useState<NodeAge>('young');
|
||||||
@@ -72,165 +63,69 @@ export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
|
|||||||
: 'none',
|
: 'none',
|
||||||
);
|
);
|
||||||
}, [node.currentPosition]);
|
}, [node.currentPosition]);
|
||||||
|
|
||||||
// React.useEffect(() => {
|
|
||||||
// setSatsAverage(
|
|
||||||
// node.positions
|
|
||||||
// .filter((pos) => pos.satsInView !== 0)
|
|
||||||
// .slice(node.positions.length - 3, node.positions.length)
|
|
||||||
// .reduce((a, b) => {
|
|
||||||
// return a.satsInView + b.satsInView;
|
|
||||||
// }).satsInView / (node.positions.length > 3 ? 3 : node.positions.length),
|
|
||||||
// );
|
|
||||||
// }, [node.positions]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="m-2 rounded-md shadow-md bg-gray-50 dark:bg-gray-700">
|
||||||
<Modal
|
<div className="flex w-full gap-1 p-2 bg-gray-100 rounded-md shadow-md dark:bg-primaryDark">
|
||||||
open={showDebug}
|
{isMyNode ? (
|
||||||
onClose={(): void => {
|
<MdAccountCircle className="my-auto" />
|
||||||
setShowDebug(false);
|
) : (
|
||||||
}}
|
<div
|
||||||
>
|
className={`my-auto w-3 h-3 rounded-full ${
|
||||||
<Card>
|
age === 'young'
|
||||||
<div className="p-10 overflow-y-auto text-left max-h-96">
|
? 'bg-green-500'
|
||||||
<JSONPretty data={node} />
|
: age === 'aging'
|
||||||
</div>
|
? 'bg-yellow-500'
|
||||||
</Card>
|
: age === 'old'
|
||||||
</Modal>
|
? 'bg-red-500'
|
||||||
{node.currentPosition && (
|
: 'bg-gray-500'
|
||||||
<Marker
|
}`}
|
||||||
center={
|
/>
|
||||||
new mapbox.LngLat(
|
)}
|
||||||
node.currentPosition.longitudeI / 1e7,
|
<div className="my-auto">{node.user?.longName}</div>
|
||||||
node.currentPosition.latitudeI / 1e7,
|
|
||||||
|
<div className="my-auto ml-auto text-xs font-semibold">
|
||||||
|
{!isMyNode && (
|
||||||
|
<span>
|
||||||
|
{node.lastHeard.getTime() ? (
|
||||||
|
<TimeAgo datetime={node.lastHeard} />
|
||||||
|
) : (
|
||||||
|
'Never'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
disabled={PositionConfidence === 'none'}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (PositionConfidence !== 'none' && node.currentPosition) {
|
||||||
|
map?.flyTo({
|
||||||
|
center: new mapbox.LngLat(
|
||||||
|
node.currentPosition.longitudeI / 1e7,
|
||||||
|
node.currentPosition.latitudeI / 1e7,
|
||||||
|
),
|
||||||
|
zoom: 16,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
PositionConfidence === 'high' ? (
|
||||||
|
<MdGpsFixed />
|
||||||
|
) : PositionConfidence === 'low' ? (
|
||||||
|
<MdGpsNotFixed />
|
||||||
|
) : (
|
||||||
|
<MdGpsOff />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
popup={<div>Popup</div>}
|
/>
|
||||||
>
|
<IconButton
|
||||||
<div className="z-50 bg-blue-500 border-2 border-blue-500 rounded-full bg-opacity-30">
|
onClick={(): void => {
|
||||||
<div className="m-4 ">
|
setSelected();
|
||||||
<FiMapPin className="w-5 h-5" />
|
}}
|
||||||
</div>
|
icon={<FiAlignLeft />}
|
||||||
</div>
|
/>
|
||||||
</Marker>
|
{/* <FiBatteryCharging /> */}
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
<Disclosure
|
|
||||||
as="div"
|
|
||||||
className="m-2 rounded-md shadow-md bg-gray-50 dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
<Disclosure.Button
|
|
||||||
as="div"
|
|
||||||
className="flex w-full gap-2 p-2 bg-gray-100 rounded-md shadow-md dark:bg-primaryDark"
|
|
||||||
>
|
|
||||||
{myNodeInfo ? (
|
|
||||||
<MdAccountCircle className="my-auto" />
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={`my-auto w-3 h-3 rounded-full ${
|
|
||||||
age === 'young'
|
|
||||||
? 'bg-green-500'
|
|
||||||
: age === 'aging'
|
|
||||||
? 'bg-yellow-500'
|
|
||||||
: age === 'old'
|
|
||||||
? 'bg-red-500'
|
|
||||||
: 'bg-gray-500'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="my-auto">{node.user?.longName}</div>
|
|
||||||
|
|
||||||
<div className="my-auto ml-auto text-xs font-semibold">
|
|
||||||
{!myNodeInfo && (
|
|
||||||
<span>
|
|
||||||
{node.lastHeard.getTime() ? (
|
|
||||||
<TimeAgo datetime={node.lastHeard} />
|
|
||||||
) : (
|
|
||||||
'Never'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
disabled={PositionConfidence === 'none'}
|
|
||||||
onClick={(e): void => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (PositionConfidence !== 'none' && node.currentPosition) {
|
|
||||||
map?.flyTo({
|
|
||||||
center: new mapbox.LngLat(
|
|
||||||
node.currentPosition.longitudeI / 1e7,
|
|
||||||
node.currentPosition.latitudeI / 1e7,
|
|
||||||
),
|
|
||||||
zoom: 16,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
icon={
|
|
||||||
PositionConfidence === 'high' ? (
|
|
||||||
<MdGpsFixed />
|
|
||||||
) : PositionConfidence === 'low' ? (
|
|
||||||
<MdGpsNotFixed />
|
|
||||||
) : (
|
|
||||||
<MdGpsOff />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Disclosure.Button>
|
|
||||||
<Disclosure.Panel className="p-2">
|
|
||||||
{myNodeInfo && (
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="flex">
|
|
||||||
<MdSdStorage className="my-auto" />
|
|
||||||
Firmware Ver:
|
|
||||||
</span>
|
|
||||||
<span>{myNodeInfo.firmwareVersion}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="flex">
|
|
||||||
<GiLightningFrequency className="my-auto" />
|
|
||||||
Freq Bands:
|
|
||||||
</span>
|
|
||||||
<span>{myNodeInfo.numBands}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex">
|
|
||||||
<div className="my-auto">
|
|
||||||
{Protobuf.HardwareModel[node.user?.hwModel ?? 0]}
|
|
||||||
</div>
|
|
||||||
<div className="ml-auto">
|
|
||||||
<IconButton
|
|
||||||
onClick={(): void => {
|
|
||||||
setShowDebug(true);
|
|
||||||
}}
|
|
||||||
icon={<FiCode className="w-5 h-5" />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<MdSignalCellularAlt className="my-auto" />
|
|
||||||
SNR:
|
|
||||||
{node.snr[node.snr.length - 1] < snrAverage ? (
|
|
||||||
<MdArrowDropDown className="text-red-500" />
|
|
||||||
) : (
|
|
||||||
<MdArrowDropUp className="text-green-500" />
|
|
||||||
)}
|
|
||||||
{node.snr[node.snr.length - 1]}, Average: {snrAverage}
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<FaSatellite className="my-auto" />
|
|
||||||
Sats:
|
|
||||||
{(node.currentPosition?.satsInView ?? 0) < satsAverage ? (
|
|
||||||
<MdArrowDropDown className="text-red-500" />
|
|
||||||
) : (
|
|
||||||
<MdArrowDropUp className="text-green-500" />
|
|
||||||
)}
|
|
||||||
{node.currentPosition?.satsInView ?? 0}, Average: {satsAverage}
|
|
||||||
</div>
|
|
||||||
</Disclosure.Panel>
|
|
||||||
</Disclosure>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FiCheck,
|
||||||
|
FiClipboard,
|
||||||
|
FiCode,
|
||||||
|
FiMapPin,
|
||||||
|
FiSliders,
|
||||||
|
FiUser,
|
||||||
|
FiX,
|
||||||
|
} from 'react-icons/fi';
|
||||||
|
import { IoTelescope } from 'react-icons/io5';
|
||||||
|
import JSONPretty from 'react-json-pretty';
|
||||||
|
import useCopyClipboard from 'react-use-clipboard';
|
||||||
|
|
||||||
|
import { TabButton } from '@app/components/TabButton';
|
||||||
|
import type { Node } from '@app/core/slices/meshtasticSlice';
|
||||||
|
import { Tab } from '@headlessui/react';
|
||||||
|
import { IconButton } from '@meshtastic/components';
|
||||||
|
|
||||||
|
export interface SidebarProps {
|
||||||
|
node: Node;
|
||||||
|
closeSidebar: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Sidebar = ({ node, closeSidebar }: SidebarProps): JSX.Element => {
|
||||||
|
const [toCopy, setToCopy] = React.useState<string>('');
|
||||||
|
const [isCopied, setCopied] = useCopyClipboard(toCopy, {
|
||||||
|
successDuration: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full bg-white border-l border-gray-300 dark:border-gray-600 w-96 dark:bg-secondaryDark">
|
||||||
|
<Tab.Group>
|
||||||
|
<div className="shadow-md">
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-medium text-gray-400">
|
||||||
|
{node.number}
|
||||||
|
</h3>
|
||||||
|
<h1 className="text-lg font-medium truncate">
|
||||||
|
{node.user?.longName}({node.user?.shortName})
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="mb-auto">
|
||||||
|
<IconButton
|
||||||
|
onClick={(): void => {
|
||||||
|
closeSidebar();
|
||||||
|
}}
|
||||||
|
icon={<FiX />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tab.List className="flex justify-between border-b border-gray-300 dark:border-gray-600">
|
||||||
|
<TabButton>
|
||||||
|
<FiUser />
|
||||||
|
</TabButton>
|
||||||
|
<TabButton>
|
||||||
|
<FiMapPin />
|
||||||
|
</TabButton>
|
||||||
|
<TabButton>
|
||||||
|
<IoTelescope />
|
||||||
|
</TabButton>
|
||||||
|
<TabButton>
|
||||||
|
<FiSliders />
|
||||||
|
</TabButton>
|
||||||
|
<TabButton>
|
||||||
|
<FiCode />
|
||||||
|
</TabButton>
|
||||||
|
</Tab.List>
|
||||||
|
</div>
|
||||||
|
<Tab.Panels className="h-full bg-gray-100 dark:bg-primaryDark">
|
||||||
|
<Tab.Panel>Content 1</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<div></div>
|
||||||
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>Content 3</Tab.Panel>
|
||||||
|
<Tab.Panel>Remote Administration</Tab.Panel>
|
||||||
|
<Tab.Panel className="relative">
|
||||||
|
<div className="absolute right-0 m-2">
|
||||||
|
<IconButton
|
||||||
|
onClick={(): void => {
|
||||||
|
setToCopy(JSON.stringify(node));
|
||||||
|
setCopied();
|
||||||
|
}}
|
||||||
|
icon={isCopied ? <FiCheck /> : <FiClipboard />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<JSONPretty data={node} />
|
||||||
|
</Tab.Panel>
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user