WIP changes
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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({
|
||||
+4
-1
@@ -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,
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()),
|
||||
}));
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user