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

58
pnpm-lock.yaml

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

10
src/components/Dialog/PeersDialog.tsx

@ -25,7 +25,8 @@ export const PeersDialog = ({
isOpen,
close,
}: PeersDialogProps): JSX.Element => {
const { hardware, nodes, connection } = useDevice();
const { hardware, nodes, connection, setPeerInfoOpen, setActivePeer } =
useDevice();
return (
<Dialog
@ -48,6 +49,7 @@ export const PeersDialog = ({
SNR
</Table.TextHeaderCell>
<Table.TextHeaderCell>Location</Table.TextHeaderCell>
<Table.TextHeaderCell>Telemetry</Table.TextHeaderCell>
<Table.TextHeaderCell>Last Heard</Table.TextHeaderCell>
<Table.TextHeaderCell>Actions</Table.TextHeaderCell>
</Table.Head>
@ -58,7 +60,10 @@ export const PeersDialog = ({
<Table.Row
key={node.data.num}
isSelectable
onSelect={() => alert(node.data.num)}
onSelect={() => {
setActivePeer(node.data.num);
setPeerInfoOpen(true);
}}
>
<Table.Cell flexBasis={48} flexShrink={0} flexGrow={0}>
<Hashicon
@ -81,6 +86,7 @@ export const PeersDialog = ({
node.data.position?.longitudeI
)}
</Table.TextCell>
<Table.TextCell>Tmp</Table.TextCell>
<Table.TextCell>
{new Date(node.data.lastHeard * 1000).toLocaleString()}
</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 { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { subscribeAll } from "@app/core/subscriptions.js";
import { randId } from "@app/core/utils/randId.js";
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 => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateBleDeviceList = useCallback(async (): Promise<void> => {
setBleDevices(await navigator.bluetooth.getDevices());
}, []);
navigator.bluetooth.addEventListener("advertisementreceived", (e) => {
console.log(e);
});
navigator.bluetooth.addEventListener("availabilitychanged", (e) => {
console.log(e);
});
useEffect(() => {
void updateBleDeviceList();
}, [updateBleDeviceList]);
@ -34,6 +28,7 @@ export const BLE = ({ close }: CloseProps): JSX.Element => {
const onConnect = async (BLEDevice: BluetoothDevice) => {
const id = randId();
const device = addDevice(id);
setSelectedDevice(id);
const connection = new IBLEConnection(id);
await connection.connect({
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 { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { subscribeAll } from "@app/core/subscriptions.js";
import { randId } from "@app/core/utils/randId.js";
@ -15,6 +16,7 @@ export interface HTTPProps {
export const HTTP = ({ close }: HTTPProps): JSX.Element => {
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const { register, handleSubmit } = useForm<{
ip: string;
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 device = addDevice(id);
setSelectedDevice(id);
const connection = new IHTTPConnection(id);
// TODO: Promise never resolves
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 { FiPlusCircle } from "react-icons/fi";
import { useAppStore } from "@app/core/stores/appStore.js";
import { subscribeAll } from "@app/core/subscriptions.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { randId } from "@core/utils/randId.js";
import { ISerialConnection } from "@meshtastic/meshtasticjs";
import type { CloseProps } from "../SlideSheets/NewDevice.js";
import type { CloseProps } from "../../NewDevice.js";
interface USBID {
id: number;
@ -19,6 +20,7 @@ interface USBID {
export const Serial = ({ close }: CloseProps): JSX.Element => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateSerialPortList = useCallback(async () => {
setSerialPorts(await navigator.serial.getPorts());
@ -37,6 +39,7 @@ export const Serial = ({ close }: CloseProps): JSX.Element => {
const onConnect = async (port: SerialPort) => {
const id = randId();
const device = addDevice(id);
setSelectedDevice(id);
const connection = new ISerialConnection(id);
await connection.connect({
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 { Progress } from "../Progress.js";
import { PeerInfo } from "../SlideSheets/PeerInfo.js";
import { Header } from "./Header.js";
import { Sidebar } from "./Sidebar/index.js";
@ -32,12 +33,12 @@ export const AppLayout = ({ children }: AppLayoutProps): JSX.Element => {
<Header />
<Pane display="flex" flex={1} height="100%" width="100%">
{devices.length ? (
devices.map((device, index) => (
devices.map((device) => (
<Pane
key={index}
key={device.id}
width="100%"
height="100%"
display={index === selectedDevice ? "grid" : "none"}
display={device.id === selectedDevice ? "grid" : "none"}
gap={majorScale(3)}
gridTemplateColumns="16rem 1fr"
>
@ -45,6 +46,7 @@ export const AppLayout = ({ children }: AppLayoutProps): JSX.Element => {
{device && device.ready ? (
<>
<Sidebar />
<PeerInfo />
<Pane height="100%" display="flex">
{children}
</Pane>

13
src/components/layout/Header.tsx

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

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

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

14
src/core/stores/deviceStore.ts

@ -32,6 +32,7 @@ export interface Node {
}
export interface Device {
id: number;
ready: boolean;
status: Types.DeviceStatusEnum;
channels: Channel[];
@ -79,6 +80,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
set(
produce<DeviceState>((draft) => {
draft.devices.set(id, {
id,
ready: false,
status: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
channels: [],
@ -417,10 +419,14 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
}
return device;
},
removeDevice: (id) =>
produce<DeviceState>((draft) => {
draft.devices.delete(id);
}),
removeDevice: (id) => {
set(
produce<DeviceState>((draft) => {
draft.devices.delete(id);
})
);
},
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 {
AddLocationIcon,
IconButton,
majorScale,
Pane,
Popover,
SendMessageIcon,
TextInputField,
Tooltip,
@ -14,6 +16,7 @@ import type { Channel } from "@core/stores/deviceStore.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Message } from "./Message.js";
import { NewLocationMessage } from "./NewLocationMessage.js";
export interface ChannelChatProps {
channel: Channel;
@ -60,34 +63,46 @@ export const ChannelChat = ({ channel }: ChannelChatProps): JSX.Element => {
/>
))}
</Pane>
<form
onSubmit={(e): void => {
e.preventDefault();
sendMessage();
}}
>
<Pane display="flex" gap={majorScale(1)}>
<TextInputField
marginTop="auto"
minLength={2}
width="100%"
label=""
placeholder="Enter Message"
marginBottom={0}
value={currentMessage}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setCurrentMessage(e.target.value);
}}
/>
<Tooltip content="Send">
<Pane display="flex" gap={majorScale(1)}>
<form
style={{ display: "flex", flexGrow: 1 }}
onSubmit={(e): void => {
e.preventDefault();
sendMessage();
}}
>
<Pane display="flex" flexGrow={1} gap={majorScale(1)}>
<TextInputField
marginTop="auto"
minLength={2}
width="100%"
label=""
placeholder="Enter Message"
marginBottom={0}
value={currentMessage}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setCurrentMessage(e.target.value);
}}
/>
<Tooltip content="Send">
<IconButton
icon={SendMessageIcon}
marginTop={majorScale(2)}
width={majorScale(8)}
/>
</Tooltip>
</Pane>
</form>
<Tooltip content="Send Location">
<Popover content={<NewLocationMessage />}>
<IconButton
icon={SendMessageIcon}
icon={AddLocationIcon}
marginTop={majorScale(2)}
width={majorScale(8)}
/>
</Tooltip>
</Pane>
</form>
</Popover>
</Tooltip>
</Pane>
</Pane>
);
};

12
src/pages/Messages/Message.tsx

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