Browse Source

WIP changes

pull/31/head
Sacha Weatherstone 4 years ago
parent
commit
e9b168dd18
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 4
      package.json
  2. 58
      pnpm-lock.yaml
  3. 10
      src/components/Dialog/PeersDialog.tsx
  4. 61
      src/components/SlideSheets/PeerInfo.tsx
  5. 13
      src/components/SlideSheets/tabs/connect/BLE.tsx
  6. 5
      src/components/SlideSheets/tabs/connect/HTTP.tsx
  7. 5
      src/components/SlideSheets/tabs/connect/Serial.tsx
  8. 18
      src/components/SlideSheets/tabs/nodes/Location.tsx
  9. 17
      src/components/SlideSheets/tabs/nodes/Overview.tsx
  10. 8
      src/components/layout/AppLayout.tsx
  11. 13
      src/components/layout/Header.tsx
  12. 20
      src/components/layout/page/SlideSheetTabbedContent.tsx
  13. 14
      src/core/stores/deviceStore.ts
  14. 63
      src/pages/Messages/ChannelChat.tsx
  15. 12
      src/pages/Messages/Message.tsx
  16. 55
      src/pages/Messages/NewLocationMessage.tsx

4
package.json

@ -48,7 +48,7 @@
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.193", "@types/chrome": "^0.0.193",
"@types/geodesy": "^2.2.3", "@types/geodesy": "^2.2.3",
"@types/node": "^18.6.5", "@types/node": "^18.7.1",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/w3c-web-serial": "^1.0.2", "@types/w3c-web-serial": "^1.0.2",
@ -60,7 +60,7 @@
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"unimported": "^1.21.0", "unimported": "^1.21.0",
"vite": "^3.0.4", "vite": "^3.0.5",
"vite-plugin-cdn-import": "^0.3.5" "vite-plugin-cdn-import": "^0.3.5"
} }
} }

58
pnpm-lock.yaml

@ -8,7 +8,7 @@ specifiers:
'@meshtastic/meshtasticjs': ^0.6.88 '@meshtastic/meshtasticjs': ^0.6.88
'@types/chrome': ^0.0.193 '@types/chrome': ^0.0.193
'@types/geodesy': ^2.2.3 '@types/geodesy': ^2.2.3
'@types/node': ^18.6.5 '@types/node': ^18.7.1
'@types/react': ^18.0.17 '@types/react': ^18.0.17
'@types/react-dom': ^18.0.6 '@types/react-dom': ^18.0.6
'@types/w3c-web-serial': ^1.0.2 '@types/w3c-web-serial': ^1.0.2
@ -37,7 +37,7 @@ specifiers:
tslib: ^2.4.0 tslib: ^2.4.0
typescript: ^4.7.4 typescript: ^4.7.4
unimported: ^1.21.0 unimported: ^1.21.0
vite: ^3.0.4 vite: ^3.0.5
vite-plugin-cdn-import: ^0.3.5 vite-plugin-cdn-import: ^0.3.5
vite-plugin-environment: ^1.1.2 vite-plugin-environment: ^1.1.2
zustand: 4.0.0 zustand: 4.0.0
@ -65,25 +65,25 @@ dependencies:
rfc4648: 1.5.2 rfc4648: 1.5.2
snarkdown: 2.0.0 snarkdown: 2.0.0
swr: 1.3[email protected] swr: 1.3[email protected]
vite-plugin-environment: 1.1[email protected].4 vite-plugin-environment: 1.1[email protected].5
zustand: 4.0[email protected][email protected] zustand: 4.0[email protected][email protected]
devDependencies: devDependencies:
'@types/chrome': 0.0.193 '@types/chrome': 0.0.193
'@types/geodesy': 2.2.3 '@types/geodesy': 2.2.3
'@types/node': 18.6.5 '@types/node': 18.7.1
'@types/react': 18.0.17 '@types/react': 18.0.17
'@types/react-dom': 18.0.6 '@types/react-dom': 18.0.6
'@types/w3c-web-serial': 1.0.2 '@types/w3c-web-serial': 1.0.2
'@types/web-bluetooth': 0.0.15 '@types/web-bluetooth': 0.0.15
'@vitejs/plugin-react': 2.0[email protected].4 '@vitejs/plugin-react': 2.0[email protected].5
gzipper: 7.1.0 gzipper: 7.1.0
rollup-plugin-visualizer: 5.7.1 rollup-plugin-visualizer: 5.7.1
tar: 6.1.11 tar: 6.1.11
tslib: 2.4.0 tslib: 2.4.0
typescript: 4.7.4 typescript: 4.7.4
unimported: 1.21.0 unimported: 1.21.0
vite: 3.0.4 vite: 3.0.5
vite-plugin-cdn-import: 0.3.5 vite-plugin-cdn-import: 0.3.5
packages: packages:
@ -106,7 +106,7 @@ packages:
'@esri/arcgis-html-sanitizer': 2.10.0 '@esri/arcgis-html-sanitizer': 2.10.0
'@esri/calcite-colors': 6.0.1 '@esri/calcite-colors': 6.0.1
'@esri/calcite-components': 1.0.0-beta.82 '@esri/calcite-components': 1.0.0-beta.82
'@popperjs/core': 2.11.5 '@popperjs/core': 2.11.6
focus-trap: 6.9.4 focus-trap: 6.9.4
luxon: 2.4.0 luxon: 2.4.0
sortablejs: 1.15.0 sortablejs: 1.15.0
@ -620,6 +620,10 @@ packages:
resolution: {integrity: sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==} resolution: {integrity: sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==}
dev: false dev: false
/@popperjs/core/2.11.6:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@protobuf-ts/runtime/2.7.0: /@protobuf-ts/runtime/2.7.0:
resolution: {integrity: sha512-CzunTplg81oMkF9MnSYiX2WOdHnAyHKnokZncgOHm5bt8iEfFNnUsMSbsWUus/ulRNIZ9VAVMjutd96vmySQaw==} resolution: {integrity: sha512-CzunTplg81oMkF9MnSYiX2WOdHnAyHKnokZncgOHm5bt8iEfFNnUsMSbsWUus/ulRNIZ9VAVMjutd96vmySQaw==}
dev: false dev: false
@ -822,8 +826,8 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: false dev: false
/@types/node/18.6.5: /@types/node/18.7.1:
resolution: {integrity: sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==} resolution: {integrity: sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==}
dev: true dev: true
/@types/normalize-package-data/2.4.1: /@types/normalize-package-data/2.4.1:
@ -1013,7 +1017,7 @@ packages:
'@typescript-eslint/types': 5.33.0 '@typescript-eslint/types': 5.33.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
/@vitejs/plugin-react/[email protected].4: /@vitejs/plugin-react/[email protected].5:
resolution: {integrity: sha512-zHkRR+X4zqEPNBbKV2FvWSxK7Q6crjMBVIAYroSU8Nbb4M3E5x4qOiLoqJBHtXgr27kfednXjkwr3lr8jS6Wrw==} resolution: {integrity: sha512-zHkRR+X4zqEPNBbKV2FvWSxK7Q6crjMBVIAYroSU8Nbb4M3E5x4qOiLoqJBHtXgr27kfednXjkwr3lr8jS6Wrw==}
engines: {node: '>=14.18.0'} engines: {node: '>=14.18.0'}
peerDependencies: peerDependencies:
@ -1026,7 +1030,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.18.6_@[email protected] '@babel/plugin-transform-react-jsx-source': 7.18.6_@[email protected]
magic-string: 0.26.2 magic-string: 0.26.2
react-refresh: 0.14.0 react-refresh: 0.14.0
vite: 3.0.4 vite: 3.0.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -1197,8 +1201,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.30001374 caniuse-lite: 1.0.30001375
electron-to-chromium: 1.4.212 electron-to-chromium: 1.4.215
node-releases: 2.0.6 node-releases: 2.0.6
update-browserslist-db: 1.0[email protected] update-browserslist-db: 1.0[email protected]
dev: true dev: true
@ -1224,8 +1228,8 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
/caniuse-lite/1.0.30001374: /caniuse-lite/1.0.30001375:
resolution: {integrity: sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==} resolution: {integrity: sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==}
dev: true dev: true
/chalk/2.4.2: /chalk/2.4.2:
@ -1256,7 +1260,7 @@ packages:
/class-validator/0.13.2: /class-validator/0.13.2:
resolution: {integrity: sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==} resolution: {integrity: sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==}
dependencies: dependencies:
libphonenumber-js: 1.10.11 libphonenumber-js: 1.10.12
validator: 13.7.0 validator: 13.7.0
dev: false dev: false
@ -1501,8 +1505,8 @@ packages:
stream-shift: 1.0.1 stream-shift: 1.0.1
dev: true dev: true
/electron-to-chromium/1.4.212: /electron-to-chromium/1.4.215:
resolution: {integrity: sha512-LjQUg1SpLj2GfyaPDVBUHdhmlDU1vDB4f0mJWSGkISoXQrn5/lH3ECPCuo2Bkvf6Y30wO+b69te+rZK/llZmjg==} resolution: {integrity: sha512-vqZxT8C5mlDZ//hQFhneHmOLnj1LhbzxV0+I1yqHV8SB1Oo4Y5Ne9+qQhwHl7O1s9s9cRuo2l5CoLEHdhMTwZg==}
dev: true dev: true
/emoji-regex/8.0.0: /emoji-regex/8.0.0:
@ -2778,8 +2782,8 @@ packages:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-check: 0.4.0 type-check: 0.4.0
/libphonenumber-js/1.10.11: /libphonenumber-js/1.10.12:
resolution: {integrity: sha512-ehoihx4HpRXO6FH/uJ0EnaEV4dVU+FDny+jv0S6k9JPyPsAIr0eXDAFvGRMBKE1daCtyHAaFSKCiuCxrOjVAzQ==} resolution: {integrity: sha512-xTFBs3ipFQNmjCUkDj6ZzRJvs97IyazFHBKWtrQrLiYs0Zk0GANob1hkMRlQUQXbJrpQGwnI+/yU4oyD4ohvpw==}
dev: false dev: false
/lines-and-columns/1.2.4: /lines-and-columns/1.2.4:
@ -3517,8 +3521,8 @@ packages:
yargs: 17.5.1 yargs: 17.5.1
dev: true dev: true
/rollup/2.77.2: /rollup/2.77.3:
resolution: {integrity: sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==} resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
@ -4027,16 +4031,16 @@ packages:
- rollup - rollup
dev: true dev: true
/vite-plugin-environment/[email protected].4: /vite-plugin-environment/[email protected].5:
resolution: {integrity: sha512-WFgM/ibceOEIuficZVaLcmJvcMZiyTkGzeS8+pzfByGYRdewqil7LSLDV1DwJfFQIx/YzcW9YRSWQG7cJ2XT1w==} resolution: {integrity: sha512-WFgM/ibceOEIuficZVaLcmJvcMZiyTkGzeS8+pzfByGYRdewqil7LSLDV1DwJfFQIx/YzcW9YRSWQG7cJ2XT1w==}
peerDependencies: peerDependencies:
vite: '>= 2.7' vite: '>= 2.7'
dependencies: dependencies:
vite: 3.0.4 vite: 3.0.5
dev: false dev: false
/vite/3.0.4: /vite/3.0.5:
resolution: {integrity: sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==} resolution: {integrity: sha512-bRvrt9Tw8EGW4jj64aYFTnVg134E8hgDxyl/eEHnxiGqYk7/pTPss6CWlurqPOUzqvEoZkZ58Ws+Iu8MB87iMA==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -4057,7 +4061,7 @@ packages:
esbuild: 0.14.54 esbuild: 0.14.54
postcss: 8.4.16 postcss: 8.4.16
resolve: 1.22.1 resolve: 1.22.1
rollup: 2.77.2 rollup: 2.77.3
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2

10
src/components/Dialog/PeersDialog.tsx

@ -25,7 +25,8 @@ export const PeersDialog = ({
isOpen, isOpen,
close, close,
}: PeersDialogProps): JSX.Element => { }: PeersDialogProps): JSX.Element => {
const { hardware, nodes, connection } = useDevice(); const { hardware, nodes, connection, setPeerInfoOpen, setActivePeer } =
useDevice();
return ( return (
<Dialog <Dialog
@ -48,6 +49,7 @@ export const PeersDialog = ({
SNR SNR
</Table.TextHeaderCell> </Table.TextHeaderCell>
<Table.TextHeaderCell>Location</Table.TextHeaderCell> <Table.TextHeaderCell>Location</Table.TextHeaderCell>
<Table.TextHeaderCell>Telemetry</Table.TextHeaderCell>
<Table.TextHeaderCell>Last Heard</Table.TextHeaderCell> <Table.TextHeaderCell>Last Heard</Table.TextHeaderCell>
<Table.TextHeaderCell>Actions</Table.TextHeaderCell> <Table.TextHeaderCell>Actions</Table.TextHeaderCell>
</Table.Head> </Table.Head>
@ -58,7 +60,10 @@ export const PeersDialog = ({
<Table.Row <Table.Row
key={node.data.num} key={node.data.num}
isSelectable isSelectable
onSelect={() => alert(node.data.num)} onSelect={() => {
setActivePeer(node.data.num);
setPeerInfoOpen(true);
}}
> >
<Table.Cell flexBasis={48} flexShrink={0} flexGrow={0}> <Table.Cell flexBasis={48} flexShrink={0} flexGrow={0}>
<Hashicon <Hashicon
@ -81,6 +86,7 @@ export const PeersDialog = ({
node.data.position?.longitudeI node.data.position?.longitudeI
)} )}
</Table.TextCell> </Table.TextCell>
<Table.TextCell>Tmp</Table.TextCell>
<Table.TextCell> <Table.TextCell>
{new Date(node.data.lastHeard * 1000).toLocaleString()} {new Date(node.data.lastHeard * 1000).toLocaleString()}
</Table.TextCell> </Table.TextCell>

61
src/components/SlideSheets/PeerInfo.tsx

@ -0,0 +1,61 @@
import type React from "react";
import { useEffect, useState } from "react";
import { GeolocationIcon, Pane, PropertyIcon, SideSheet } from "evergreen-ui";
import { Node, useDevice } from "@app/core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { SlideSheetTabbedContent } from "../layout/page/SlideSheetTabbedContent.js";
import type { TabType } from "../layout/page/TabbedContent.js";
import { Location } from "./tabs/nodes/Location.js";
import { Overview } from "./tabs/nodes/Overview.js";
export const PeerInfo = () => {
const { peerInfoOpen, activePeer, setPeerInfoOpen, nodes } = useDevice();
const [selectedTab, setSelectedTab] = useState(0);
const [node, setNode] = useState<Node | undefined>();
useEffect(() => {
setNode(nodes.find((n) => n.data.num === activePeer));
}, [nodes, activePeer]);
const tabs: TabType[] = [
{
name: "Info",
icon: PropertyIcon,
element: () => <Overview node={node} />,
},
{
name: "Location",
icon: GeolocationIcon,
element: () => <Location node={node} />,
},
];
return (
<SideSheet
isShown={peerInfoOpen}
onCloseComplete={() => {
setPeerInfoOpen(false);
}}
containerProps={{
display: "flex",
flex: "1",
flexDirection: "column",
}}
>
<SlideSheetTabbedContent
heading={node?.data.user?.longName ?? "UNK"}
description={Protobuf.HardwareModel[node?.data.user?.hwModel ?? 0]}
tabs={tabs}
tabIcon={
<Pane marginY="auto">
<Hashicon size={32} value={(node?.data.num ?? 0).toString()} />
</Pane>
}
/>
</SideSheet>
);
};

13
src/components/connect/BLE.tsx → src/components/SlideSheets/tabs/connect/BLE.tsx

@ -4,29 +4,23 @@ import { useCallback, useEffect, useState } from "react";
import { Button, majorScale, Pane } from "evergreen-ui"; import { Button, majorScale, Pane } from "evergreen-ui";
import { FiPlusCircle } from "react-icons/fi"; import { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js"; import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { subscribeAll } from "@app/core/subscriptions.js"; import { subscribeAll } from "@app/core/subscriptions.js";
import { randId } from "@app/core/utils/randId.js"; import { randId } from "@app/core/utils/randId.js";
import { Constants, IBLEConnection } from "@meshtastic/meshtasticjs"; import { Constants, IBLEConnection } from "@meshtastic/meshtasticjs";
import type { CloseProps } from "../SlideSheets/NewDevice.js"; import type { CloseProps } from "../../NewDevice.js";
export const BLE = ({ close }: CloseProps): JSX.Element => { export const BLE = ({ close }: CloseProps): JSX.Element => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]); const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateBleDeviceList = useCallback(async (): Promise<void> => { const updateBleDeviceList = useCallback(async (): Promise<void> => {
setBleDevices(await navigator.bluetooth.getDevices()); setBleDevices(await navigator.bluetooth.getDevices());
}, []); }, []);
navigator.bluetooth.addEventListener("advertisementreceived", (e) => {
console.log(e);
});
navigator.bluetooth.addEventListener("availabilitychanged", (e) => {
console.log(e);
});
useEffect(() => { useEffect(() => {
void updateBleDeviceList(); void updateBleDeviceList();
}, [updateBleDeviceList]); }, [updateBleDeviceList]);
@ -34,6 +28,7 @@ export const BLE = ({ close }: CloseProps): JSX.Element => {
const onConnect = async (BLEDevice: BluetoothDevice) => { const onConnect = async (BLEDevice: BluetoothDevice) => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id);
const connection = new IBLEConnection(id); const connection = new IBLEConnection(id);
await connection.connect({ await connection.connect({
device: BLEDevice, device: BLEDevice,

5
src/components/connect/HTTP.tsx → src/components/SlideSheets/tabs/connect/HTTP.tsx

@ -4,6 +4,7 @@ import { Button, majorScale, Pane, TextInputField } from "evergreen-ui";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { FiPlusCircle } from "react-icons/fi"; import { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js"; import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { subscribeAll } from "@app/core/subscriptions.js"; import { subscribeAll } from "@app/core/subscriptions.js";
import { randId } from "@app/core/utils/randId.js"; import { randId } from "@app/core/utils/randId.js";
@ -15,6 +16,7 @@ export interface HTTPProps {
export const HTTP = ({ close }: HTTPProps): JSX.Element => { export const HTTP = ({ close }: HTTPProps): JSX.Element => {
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const { register, handleSubmit } = useForm<{ const { register, handleSubmit } = useForm<{
ip: string; ip: string;
tls: boolean; tls: boolean;
@ -25,9 +27,10 @@ export const HTTP = ({ close }: HTTPProps): JSX.Element => {
}, },
}); });
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit((data) => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id);
const connection = new IHTTPConnection(id); const connection = new IHTTPConnection(id);
// TODO: Promise never resolves // TODO: Promise never resolves
void connection.connect({ void connection.connect({

5
src/components/connect/Serial.tsx → src/components/SlideSheets/tabs/connect/Serial.tsx

@ -4,12 +4,13 @@ import { useCallback, useEffect, useState } from "react";
import { Button, majorScale, Pane } from "evergreen-ui"; import { Button, majorScale, Pane } from "evergreen-ui";
import { FiPlusCircle } from "react-icons/fi"; import { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { subscribeAll } from "@app/core/subscriptions.js"; import { subscribeAll } from "@app/core/subscriptions.js";
import { useDeviceStore } from "@core/stores/deviceStore.js"; import { useDeviceStore } from "@core/stores/deviceStore.js";
import { randId } from "@core/utils/randId.js"; import { randId } from "@core/utils/randId.js";
import { ISerialConnection } from "@meshtastic/meshtasticjs"; import { ISerialConnection } from "@meshtastic/meshtasticjs";
import type { CloseProps } from "../SlideSheets/NewDevice.js"; import type { CloseProps } from "../../NewDevice.js";
interface USBID { interface USBID {
id: number; id: number;
@ -19,6 +20,7 @@ interface USBID {
export const Serial = ({ close }: CloseProps): JSX.Element => { export const Serial = ({ close }: CloseProps): JSX.Element => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]); const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateSerialPortList = useCallback(async () => { const updateSerialPortList = useCallback(async () => {
setSerialPorts(await navigator.serial.getPorts()); setSerialPorts(await navigator.serial.getPorts());
@ -37,6 +39,7 @@ export const Serial = ({ close }: CloseProps): JSX.Element => {
const onConnect = async (port: SerialPort) => { const onConnect = async (port: SerialPort) => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id);
const connection = new ISerialConnection(id); const connection = new ISerialConnection(id);
await connection.connect({ await connection.connect({
port, port,

18
src/components/SlideSheets/tabs/nodes/Location.tsx

@ -0,0 +1,18 @@
import type React from "react";
import { Pane } from "evergreen-ui";
import JSONPretty from "react-json-pretty";
import type { Node } from "@app/core/stores/deviceStore.js";
export interface LocationProps {
node?: Node;
}
export const Location = ({ node }: LocationProps): JSX.Element => {
return (
<Pane>
<JSONPretty data={node.data.position} />
</Pane>
);
};

17
src/components/SlideSheets/tabs/nodes/Overview.tsx

@ -0,0 +1,17 @@
import type React from "react";
import { Pane } from "evergreen-ui";
import JSONPretty from "react-json-pretty";
import type { Node } from "@app/core/stores/deviceStore.js";
export interface OverviewProps {
node?: Node;
}
export const Overview = ({ node }: OverviewProps): JSX.Element => {
return (
<Pane>
<JSONPretty data={node.data.user} />
</Pane>
);
};

8
src/components/layout/AppLayout.tsx

@ -8,6 +8,7 @@ import { useDeviceStore } from "@core/stores/deviceStore.js";
import { NoDevice } from "../misc/NoDevice.js"; import { NoDevice } from "../misc/NoDevice.js";
import { Progress } from "../Progress.js"; import { Progress } from "../Progress.js";
import { PeerInfo } from "../SlideSheets/PeerInfo.js";
import { Header } from "./Header.js"; import { Header } from "./Header.js";
import { Sidebar } from "./Sidebar/index.js"; import { Sidebar } from "./Sidebar/index.js";
@ -32,12 +33,12 @@ export const AppLayout = ({ children }: AppLayoutProps): JSX.Element => {
<Header /> <Header />
<Pane display="flex" flex={1} height="100%" width="100%"> <Pane display="flex" flex={1} height="100%" width="100%">
{devices.length ? ( {devices.length ? (
devices.map((device, index) => ( devices.map((device) => (
<Pane <Pane
key={index} key={device.id}
width="100%" width="100%"
height="100%" height="100%"
display={index === selectedDevice ? "grid" : "none"} display={device.id === selectedDevice ? "grid" : "none"}
gap={majorScale(3)} gap={majorScale(3)}
gridTemplateColumns="16rem 1fr" gridTemplateColumns="16rem 1fr"
> >
@ -45,6 +46,7 @@ export const AppLayout = ({ children }: AppLayoutProps): JSX.Element => {
{device && device.ready ? ( {device && device.ready ? (
<> <>
<Sidebar /> <Sidebar />
<PeerInfo />
<Pane height="100%" display="flex"> <Pane height="100%" display="flex">
{children} {children}
</Pane> </Pane>

13
src/components/layout/Header.tsx

@ -5,6 +5,7 @@ import {
Button, Button,
CrossIcon, CrossIcon,
GlobeIcon, GlobeIcon,
HelpIcon,
IconButton, IconButton,
Link, Link,
majorScale, majorScale,
@ -65,13 +66,13 @@ export const Header = (): JSX.Element => {
</Link> </Link>
</Pane> </Pane>
<Tablist display="flex" marginX={majorScale(4)}> <Tablist display="flex" marginX={majorScale(4)}>
{getDevices().map((device, index) => ( {getDevices().map((device) => (
<Tab <Tab
key={index} key={device.id}
gap={majorScale(1)} gap={majorScale(1)}
isSelected={index === selectedDevice} isSelected={device.id === selectedDevice}
onSelect={() => { onSelect={() => {
setSelectedDevice(index); setSelectedDevice(device.id);
}} }}
> >
<Hashicon value={device.hardware.myNodeNum.toString()} size={20} /> <Hashicon value={device.hardware.myNodeNum.toString()} size={20} />
@ -121,6 +122,9 @@ export const Header = (): JSX.Element => {
<Button <Button
iconBefore={CrossIcon} iconBefore={CrossIcon}
onClick={() => { onClick={() => {
void getDevices()
.find((d) => d.id === selectedDevice)
?.connection?.disconnect();
removeDevice(selectedDevice ?? 0); removeDevice(selectedDevice ?? 0);
}} }}
> >
@ -138,6 +142,7 @@ export const Header = (): JSX.Element => {
</Button> </Button>
</Link> </Link>
</Tooltip> </Tooltip>
<IconButton icon={HelpIcon} />
<Tooltip content="Visit Meshtastic.org"> <Tooltip content="Visit Meshtastic.org">
<Link target="_blank" href="https://meshtastic.org/"> <Link target="_blank" href="https://meshtastic.org/">
<IconButton icon={GlobeIcon} /> <IconButton icon={GlobeIcon} />

20
src/components/layout/page/SlideSheetTabbedContent.tsx

@ -23,23 +23,33 @@ export interface SlideSheetTabbedContentProps {
heading: string; heading: string;
description: string; description: string;
tabs: TabType[]; tabs: TabType[];
tabIcon?: React.ReactNode;
} }
export const SlideSheetTabbedContent = ({ export const SlideSheetTabbedContent = ({
heading, heading,
description, description,
tabs, tabs,
tabIcon,
}: SlideSheetTabbedContentProps): JSX.Element => { }: SlideSheetTabbedContentProps): JSX.Element => {
const [selectedTab, setSelectedTab] = useState(0); const [selectedTab, setSelectedTab] = useState(0);
return ( return (
<> <>
<Pane zIndex={1} flexShrink={0} elevation={1} backgroundColor="white"> <Pane zIndex={1} flexShrink={0} elevation={1} backgroundColor="white">
<Pane padding={16} borderBottom="muted"> <Pane
<Heading size={600}>{heading}</Heading> display="flex"
<Paragraph size={400} color="muted"> padding={16}
{description} borderBottom="muted"
</Paragraph> gap={majorScale(1)}
>
{tabIcon}
<Pane>
<Heading size={600}>{heading}</Heading>
<Paragraph size={400} color="muted">
{description}
</Paragraph>
</Pane>
</Pane> </Pane>
<Pane display="flex" padding={8}> <Pane display="flex" padding={8}>
<Tablist> <Tablist>

14
src/core/stores/deviceStore.ts

@ -32,6 +32,7 @@ export interface Node {
} }
export interface Device { export interface Device {
id: number;
ready: boolean; ready: boolean;
status: Types.DeviceStatusEnum; status: Types.DeviceStatusEnum;
channels: Channel[]; channels: Channel[];
@ -79,6 +80,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
set( set(
produce<DeviceState>((draft) => { produce<DeviceState>((draft) => {
draft.devices.set(id, { draft.devices.set(id, {
id,
ready: false, ready: false,
status: Types.DeviceStatusEnum.DEVICE_DISCONNECTED, status: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
channels: [], channels: [],
@ -417,10 +419,14 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
} }
return device; return device;
}, },
removeDevice: (id) => removeDevice: (id) => {
produce<DeviceState>((draft) => { set(
draft.devices.delete(id); produce<DeviceState>((draft) => {
}), draft.devices.delete(id);
})
);
},
getDevices: () => Array.from(get().devices.values()), getDevices: () => Array.from(get().devices.values()),
})); }));

63
src/pages/Messages/ChannelChat.tsx

@ -2,9 +2,11 @@ import type React from "react";
import { ChangeEvent, useState } from "react"; import { ChangeEvent, useState } from "react";
import { import {
AddLocationIcon,
IconButton, IconButton,
majorScale, majorScale,
Pane, Pane,
Popover,
SendMessageIcon, SendMessageIcon,
TextInputField, TextInputField,
Tooltip, Tooltip,
@ -14,6 +16,7 @@ import type { Channel } from "@core/stores/deviceStore.js";
import { useDevice } from "@core/stores/deviceStore.js"; import { useDevice } from "@core/stores/deviceStore.js";
import { Message } from "./Message.js"; import { Message } from "./Message.js";
import { NewLocationMessage } from "./NewLocationMessage.js";
export interface ChannelChatProps { export interface ChannelChatProps {
channel: Channel; channel: Channel;
@ -60,34 +63,46 @@ export const ChannelChat = ({ channel }: ChannelChatProps): JSX.Element => {
/> />
))} ))}
</Pane> </Pane>
<form <Pane display="flex" gap={majorScale(1)}>
onSubmit={(e): void => { <form
e.preventDefault(); style={{ display: "flex", flexGrow: 1 }}
sendMessage(); onSubmit={(e): void => {
}} e.preventDefault();
> sendMessage();
<Pane display="flex" gap={majorScale(1)}> }}
<TextInputField >
marginTop="auto" <Pane display="flex" flexGrow={1} gap={majorScale(1)}>
minLength={2} <TextInputField
width="100%" marginTop="auto"
label="" minLength={2}
placeholder="Enter Message" width="100%"
marginBottom={0} label=""
value={currentMessage} placeholder="Enter Message"
onChange={(e: ChangeEvent<HTMLInputElement>): void => { marginBottom={0}
setCurrentMessage(e.target.value); value={currentMessage}
}} onChange={(e: ChangeEvent<HTMLInputElement>): void => {
/> setCurrentMessage(e.target.value);
<Tooltip content="Send"> }}
/>
<Tooltip content="Send">
<IconButton
icon={SendMessageIcon}
marginTop={majorScale(2)}
width={majorScale(8)}
/>
</Tooltip>
</Pane>
</form>
<Tooltip content="Send Location">
<Popover content={<NewLocationMessage />}>
<IconButton <IconButton
icon={SendMessageIcon} icon={AddLocationIcon}
marginTop={majorScale(2)} marginTop={majorScale(2)}
width={majorScale(8)} width={majorScale(8)}
/> />
</Tooltip> </Popover>
</Pane> </Tooltip>
</form> </Pane>
</Pane> </Pane>
); );
}; };

12
src/pages/Messages/Message.tsx

@ -10,6 +10,7 @@ import {
Text, Text,
} from "evergreen-ui"; } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react"; import { Hashicon } from "@emeraldpay/hashicon-react";
import type { Protobuf, Types } from "@meshtastic/meshtasticjs"; import type { Protobuf, Types } from "@meshtastic/meshtasticjs";
@ -30,6 +31,13 @@ export const Message = ({
rxTime, rxTime,
sender, sender,
}: MessageProps): JSX.Element => { }: MessageProps): JSX.Element => {
const { setPeerInfoOpen, setActivePeer } = useDevice();
const openPeer = (): void => {
setActivePeer(messagePacket.packet.from);
setPeerInfoOpen(true);
};
return lastMsgSameUser ? ( return lastMsgSameUser ? (
<Pane display="flex" marginLeft={majorScale(3)}> <Pane display="flex" marginLeft={majorScale(3)}>
{ack ? ( {ack ? (
@ -53,10 +61,10 @@ export const Message = ({
) : ( ) : (
<Pane marginX={majorScale(2)} gap={majorScale(1)} marginTop={majorScale(1)}> <Pane marginX={majorScale(2)} gap={majorScale(1)} marginTop={majorScale(1)}>
<Pane display="flex" gap={majorScale(1)}> <Pane display="flex" gap={majorScale(1)}>
<Pane width={majorScale(3)}> <Pane onClick={openPeer} cursor="pointer" width={majorScale(3)}>
<Hashicon value={(sender?.num ?? 0).toString()} size={32} /> <Hashicon value={(sender?.num ?? 0).toString()} size={32} />
</Pane> </Pane>
<Strong cursor="default" size={500}> <Strong onClick={openPeer} cursor="pointer" size={500}>
{sender?.user?.longName ?? "UNK"} {sender?.user?.longName ?? "UNK"}
</Strong> </Strong>
<Small> <Small>

55
src/pages/Messages/NewLocationMessage.tsx

@ -0,0 +1,55 @@
import type React from "react";
import {
Button,
majorScale,
Pane,
SelectField,
TextInputField,
} from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
enum LocationType {
MGRS,
LatLng,
DecimalDegrees,
}
export const NewLocationMessage = (): JSX.Element => {
const { connection } = useDevice();
return (
<Pane width={240} margin={majorScale(2)}>
<form
onSubmit={(e): void => {
e.preventDefault();
}}
>
<TextInputField label="Name" />
<TextInputField label="Description" />
<SelectField label="Type" value={LocationType.MGRS}>
{renderOptions(LocationType)}
</SelectField>
<TextInputField label="Coordinates" />
<Button
width="100%"
onClick={() => {
void connection?.sendLocation(
Protobuf.Location.create({
latitudeI: Math.floor(3.89103 * 1e7),
longitudeI: Math.floor(105.87005 * 1e7),
name: "TEST",
description: "This is a description",
})
);
}}
>
Send
</Button>
</form>
</Pane>
);
};
Loading…
Cancel
Save