2.0 Overhaul start

This commit is contained in:
Sacha Weatherstone
2022-09-22 16:35:40 +10:00
parent 4e45990ef5
commit a73eab1ea6
87 changed files with 2880 additions and 3573 deletions
@@ -1,14 +1,13 @@
import type React from "react";
import { useState } from "react";
import { Button, LayerIcon, LayerOutlineIcon, Tooltip } from "evergreen-ui";
import { IoQrCodeOutline } from "react-icons/io5";
import { Channel } from "@app/components/PageComponents/Channel.js";
import { Button } from "@components/Button.js";
import { QRDialog } from "@components/Dialog/QRDialog.js";
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
import { useDevice } from "@core/providers/useDevice.js";
import { QrCodeIcon } from "@heroicons/react/24/outline";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Channel } from "@pages/Channels/Channel.js";
export const ChannelsPage = (): JSX.Element => {
const { channels, config } = useDevice();
@@ -21,10 +20,6 @@ export const ChannelsPage = (): JSX.Element => {
: channel.config.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.config.index}`,
icon:
channel.config.role !== Protobuf.Channel_Role.DISABLED
? LayerIcon
: LayerOutlineIcon,
element: () => <Channel channel={channel.config} />,
};
});
@@ -43,16 +38,15 @@ export const ChannelsPage = (): JSX.Element => {
tabs={tabs}
actions={[
() => (
<Tooltip content="Open QR code generator">
<Button
onClick={() => {
setQRDialogOpen(true);
}}
iconBefore={IoQrCodeOutline}
>
QR Code
</Button>
</Tooltip>
<Button
variant="secondary"
iconBefore={<QrCodeIcon className="w-4" />}
onClick={() => {
setQRDialogOpen(true);
}}
>
QR Code
</Button>
),
]}
/>
-205
View File
@@ -1,205 +0,0 @@
import type React from "react";
import { useEffect, useState } from "react";
import { fromByteArray, toByteArray } from "base64-js";
import {
Button,
EyeOffIcon,
EyeOpenIcon,
FormField,
IconButton,
majorScale,
Pane,
RefreshIcon,
SelectField,
Switch,
TextInputField,
Tooltip,
} from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
export interface SettingsPanelProps {
channel: Protobuf.Channel;
}
export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
const { connection } = useDevice();
const [loading, setLoading] = useState(false);
const [keySize, setKeySize] = useState<128 | 256>(256);
const [pskHidden, setPskHidden] = useState(true);
const {
register,
handleSubmit,
formState: { errors, isDirty },
reset,
control,
setValue,
} = useForm<
Omit<Protobuf.ChannelSettings, "psk"> & { psk: string; enabled: boolean }
>({
defaultValues: {
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0)),
},
});
useEffect(() => {
reset({
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0)),
});
}, [channel, reset]);
const onSubmit = handleSubmit(async (data) => {
setLoading(true);
const channelData = Protobuf.Channel.create({
role:
channel?.role === Protobuf.Channel_Role.PRIMARY
? Protobuf.Channel_Role.PRIMARY
: data.enabled
? Protobuf.Channel_Role.SECONDARY
: Protobuf.Channel_Role.DISABLED,
index: channel?.index,
settings: {
...data,
psk: toByteArray(data.psk ?? ""),
},
});
await connection?.setChannel(channelData, (): Promise<void> => {
reset({ ...data });
setLoading(false);
return Promise.resolve();
});
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
{channel?.index !== 0 && (
<>
<FormField
label="Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch
height={24}
marginLeft="auto"
checked={value}
{...field}
/>
)}
/>
</FormField>
<TextInputField
label="Name"
description="Max transmit power in dBm"
{...register("name")}
/>
</>
)}
<Pane display="flex" gap={majorScale(1)}>
<SelectField
width="100%"
label="Key Size"
description="Desired size of generated key."
value={keySize}
onChange={(e): void => {
setKeySize(parseInt(e.target.value) as 128 | 256);
}}
>
<option value={128}>128 Bit</option>
<option value={256}>256 Bit</option>
</SelectField>
<Tooltip content="Generate new key">
<IconButton
marginTop={majorScale(6)}
onClick={(
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
e.preventDefault();
const key = new Uint8Array(keySize / 8);
crypto.getRandomValues(key);
setValue("psk", fromByteArray(key));
}}
icon={<RefreshIcon />}
/>
</Tooltip>
</Pane>
<Pane display="flex" gap={majorScale(1)}>
<TextInputField
width="100%"
label="Pre-Shared Key"
description="Max transmit power in dBm"
type={pskHidden ? "password" : "text"}
{...register("psk")}
/>
<Tooltip content={pskHidden ? "Show key" : "Hide key"}>
<Button
marginTop={majorScale(6)}
width={majorScale(12)}
onClick={(
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
e.preventDefault();
setPskHidden(!pskHidden);
}}
iconBefore={pskHidden ? <EyeOpenIcon /> : <EyeOffIcon />}
>
{pskHidden ? "Show" : "Hide"}
</Button>
</Tooltip>
</Pane>
<FormField
label="Uplink Enabled"
description="Description"
isInvalid={!!errors.uplinkEnabled?.message}
validationMessage={errors.uplinkEnabled?.message}
>
<Controller
name="uplinkEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Downlink Enabled"
description="Description"
isInvalid={!!errors.downlinkEnabled?.message}
validationMessage={errors.downlinkEnabled?.message}
>
<Controller
name="downlinkEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
</Form>
);
};
+26 -29
View File
@@ -1,15 +1,12 @@
import type React from "react";
import { useState } from "react";
import { Pane, Tab, Tablist } from "evergreen-ui";
import { Fragment } from "react";
import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.js";
import { MQTT } from "@components/PageComponents/ModuleConfig/MQTT.js";
import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js";
import { Tab } from "@headlessui/react";
export const AppConfig = (): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState(0);
const configSections = [
{
label: "Interface",
@@ -26,31 +23,31 @@ export const AppConfig = (): JSX.Element => {
];
return (
<Pane display="flex">
<Pane flexBasis={150} marginRight={24}>
<Tablist>
{configSections.map((Config, index) => (
<Tab
key={index}
direction="vertical"
isSelected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
>
{Config.label}
</Tab>
))}
</Tablist>
</Pane>
<Pane flex="1">
<Tab.Group as="div" className="flex gap-3 w-full">
<Tab.List className="flex flex-col w-44 gap-1">
{configSections.map((Config, index) => (
<Pane
key={index}
display={index === selectedIndex ? "block" : "none"}
>
<Config.element />
</Pane>
<Tab key={index} as={Fragment}>
{({ selected }) => (
<div
className={`flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer ${
selected
? "bg-gray-100 text-gray-900"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
}`}
>
{Config.label}
</div>
)}
</Tab>
))}
</Pane>
</Pane>
</Tab.List>
<Tab.Panels as={Fragment}>
{configSections.map((Config, index) => (
<Tab.Panel key={index} as={Fragment}>
<Config.element />
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
);
};
+26 -29
View File
@@ -1,7 +1,5 @@
import type React from "react";
import { useState } from "react";
import { Pane, Tab, Tablist } from "evergreen-ui";
import { Fragment } from "react";
import { Network } from "@app/components/PageComponents/Config/Network.js";
import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.js";
@@ -12,9 +10,9 @@ import { Position } from "@components/PageComponents/Config/Position.js";
import { Power } from "@components/PageComponents/Config/Power.js";
import { User } from "@components/PageComponents/Config/User.js";
import { useDevice } from "@core/providers/useDevice.js";
import { Tab } from "@headlessui/react";
export const DeviceConfig = (): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState(0);
const { hardware } = useDevice();
const configSections = [
@@ -54,32 +52,31 @@ export const DeviceConfig = (): JSX.Element => {
];
return (
<Pane display="flex">
<Pane flexBasis={150} marginRight={24}>
<Tablist>
{configSections.map((Config, index) => (
<Tab
key={index}
direction="vertical"
isSelected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
disabled={Config.disabled}
>
{Config.label}
</Tab>
))}
</Tablist>
</Pane>
<Pane flex="1">
<Tab.Group as="div" className="flex gap-3 w-full">
<Tab.List className="flex flex-col w-44 gap-1">
{configSections.map((Config, index) => (
<Pane
key={index}
display={index === selectedIndex ? "block" : "none"}
>
<Config.element />
</Pane>
<Tab key={index} as={Fragment}>
{({ selected }) => (
<div
className={`flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer ${
selected
? "bg-gray-100 text-gray-900"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
}`}
>
{Config.label}
</div>
)}
</Tab>
))}
</Pane>
</Pane>
</Tab.List>
<Tab.Panels as={Fragment}>
{configSections.map((Config, index) => (
<Tab.Panel key={index} as={Fragment}>
<Config.element />
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
);
};
+26 -27
View File
@@ -1,7 +1,5 @@
import type React from "react";
import { useState } from "react";
import { Pane, Tab, Tablist } from "evergreen-ui";
import { Fragment, useState } from "react";
import { CannedMessage } from "@components/PageComponents/ModuleConfig/CannedMessage";
import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.js";
@@ -10,6 +8,7 @@ import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.js"
import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js";
import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.js";
import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.js";
import { Tab } from "@headlessui/react";
export const ModuleConfig = (): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState(0);
@@ -46,31 +45,31 @@ export const ModuleConfig = (): JSX.Element => {
];
return (
<Pane display="flex">
<Pane flexBasis={150} marginRight={24}>
<Tablist>
{configSections.map((Config, index) => (
<Tab
key={index}
direction="vertical"
isSelected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
>
{Config.label}
</Tab>
))}
</Tablist>
</Pane>
<Pane flex="1">
<Tab.Group as="div" className="flex gap-3 w-full">
<Tab.List className="flex flex-col gap-1 w-44">
{configSections.map((Config, index) => (
<Pane
key={index}
display={index === selectedIndex ? "block" : "none"}
>
<Config.element />
</Pane>
<Tab key={index} as={Fragment}>
{({ selected }) => (
<div
className={`flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer ${
selected
? "bg-gray-100 text-gray-900"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
}`}
>
{Config.label}
</div>
)}
</Tab>
))}
</Pane>
</Pane>
</Tab.List>
<Tab.Panels as={Fragment}>
{configSections.map((Config, index) => (
<Tab.Panel key={index} as={Fragment}>
<Config.element />
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
);
};
+8 -5
View File
@@ -1,8 +1,11 @@
import type React from "react";
import { ApplicationsIcon, CogIcon, CubeIcon } from "evergreen-ui";
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
import {
Cog8ToothIcon,
CubeTransparentIcon,
WindowIcon,
} from "@heroicons/react/24/outline";
import { AppConfig } from "@pages/Config/AppConfig.js";
import { DeviceConfig } from "@pages/Config/DeviceConfig.js";
import { ModuleConfig } from "@pages/Config/ModuleConfig.js";
@@ -11,17 +14,17 @@ export const ConfigPage = (): JSX.Element => {
const tabs: TabType[] = [
{
name: "Device Config",
icon: CogIcon,
icon: <Cog8ToothIcon className="h-4" />,
element: DeviceConfig,
},
{
name: "Module Config",
icon: CubeIcon,
icon: <CubeTransparentIcon className="h-4" />,
element: ModuleConfig,
},
{
name: "App Config",
icon: ApplicationsIcon,
icon: <WindowIcon className="h-4" />,
element: AppConfig,
},
];
+3 -5
View File
@@ -1,17 +1,15 @@
import type React from "react";
import { Pane } from "evergreen-ui";
import { useDevice } from "@core/providers/useDevice.js";
export const Environment = (): JSX.Element => {
const { nodes } = useDevice();
return (
<Pane>
<div>
{nodes.map((node, index) => (
<Pane key={index}>{JSON.stringify(node.environmentMetrics)}</Pane>
<div key={index}>{JSON.stringify(node.environmentMetrics)}</div>
))}
</Pane>
</div>
);
};
+4 -6
View File
@@ -1,8 +1,6 @@
import type React from "react";
import { useEffect, useState } from "react";
import { Pane } from "evergreen-ui";
export interface File {
nameModified: string;
name: string;
@@ -33,9 +31,9 @@ export const FileBrowser = (): JSX.Element => {
});
return (
<Pane>
<div>
{data?.data.files.map((file) => (
<Pane key={file.name}>
<div key={file.name}>
<a
target="_blank"
rel="noopener noreferrer"
@@ -43,8 +41,8 @@ export const FileBrowser = (): JSX.Element => {
>
{file.name.replace("static/", "").replace(".gz", "")}
</a>
</Pane>
</div>
))}
</Pane>
</div>
);
};
+8 -5
View File
@@ -1,9 +1,12 @@
import type React from "react";
import { DocumentIcon, GanttChartIcon, RainIcon } from "evergreen-ui";
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
import { useDevice } from "@core/providers/useDevice.js";
import {
CloudIcon,
DocumentIcon,
SignalIcon,
} from "@heroicons/react/24/outline";
import { Environment } from "@pages/Extensions/Environment.js";
import { FileBrowser } from "@pages/Extensions/FileBrowser";
@@ -13,19 +16,19 @@ export const ExtensionsPage = (): JSX.Element => {
const tabs: TabType[] = [
{
name: "File Browser",
icon: DocumentIcon,
icon: <DocumentIcon className="h-4" />,
element: FileBrowser,
disabled: !hardware.hasWifi,
},
{
name: "Range Test",
icon: GanttChartIcon,
icon: <SignalIcon className="h-4" />,
element: FileBrowser,
disabled: !hardware.hasWifi,
},
{
name: "Environment",
icon: RainIcon,
icon: <CloudIcon className="h-4" />,
element: Environment,
},
];
@@ -1,6 +1,5 @@
import type React from "react";
import { Pane } from "evergreen-ui";
import JSONPretty from "react-json-pretty";
import { useDevice } from "@core/providers/useDevice.js";
@@ -11,9 +10,9 @@ export const InfoPage = (): JSX.Element => {
const myNode = nodes.find((n) => n.data.num === hardware.myNodeNum);
return (
<Pane>
<div>
<JSONPretty data={myNode} />
<JSONPretty data={hardware} />
</Pane>
</div>
);
};
+17 -49
View File
@@ -1,61 +1,29 @@
import type React from "react";
import {
Heading,
IconButton,
LocateIcon,
majorScale,
MapMarkerIcon,
Pane,
Text,
} from "evergreen-ui";
import maplibregl from "maplibre-gl";
import { Map, Marker, useMap } from "react-map-gl";
import { IconButton } from "@app/components/IconButton.js";
import { useDevice } from "@core/providers/useDevice.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { MapPinIcon } from "@heroicons/react/24/outline";
export const MapPage = (): JSX.Element => {
const { nodes, waypoints } = useDevice();
const { current: map } = useMap();
return (
<Pane
margin={majorScale(3)}
borderRadius={majorScale(1)}
elevation={1}
display="flex"
flexGrow={1}
flexDirection="column"
gap={majorScale(2)}
overflow="hidden"
position="relative"
>
<Pane
position="absolute"
zIndex={10}
right={0}
top={0}
borderRadius={majorScale(1)}
padding={majorScale(1)}
margin={majorScale(1)}
background="tint1"
width={majorScale(28)}
elevation={1}
overflow="hidden"
>
<Pane padding={majorScale(1)} background="tint2">
<Heading>Title</Heading>
</Pane>
<Pane display="flex" flexDirection="column" gap={majorScale(1)}>
<div className="flex-grow">
<div className="absolute z-10 right-0 top-0 m-2 rounded-md p-2 shadow-md bg-white">
<div className="font-medium text-lg p-1">Title</div>
<div className="flex flex-col gap-2">
{nodes.map((n) => (
<Pane key={n.data.num} display="flex" gap={majorScale(1)}>
<div key={n.data.num} className="flex gap-2">
<Hashicon value={n.data.num.toString()} size={24} />
<Text>{n.data.user?.longName}</Text>
<div>{n.data.user?.longName}</div>
<IconButton
icon={LocateIcon}
marginLeft="auto"
size="small"
icon={<MapPinIcon className="h-4" />}
size="sm"
onClick={() => {
if (n.data.position?.latitudeI) {
map?.flyTo({
@@ -68,10 +36,10 @@ export const MapPage = (): JSX.Element => {
}
}}
/>
</Pane>
</div>
))}
</Pane>
</Pane>
</div>
</div>
<Map
mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json"
mapLib={maplibregl}
@@ -84,9 +52,9 @@ export const MapPage = (): JSX.Element => {
latitude={wp.latitudeI / 1e7}
anchor="bottom"
>
<Pane>
<MapMarkerIcon />
</Pane>
<div>
<MapPinIcon className="h-4" />
</div>
</Marker>
))}
{nodes.map((n) => {
@@ -104,6 +72,6 @@ export const MapPage = (): JSX.Element => {
}
})}
</Map>
</Pane>
</div>
);
};
@@ -1,23 +1,19 @@
import type React from "react";
import {
CircleIcon,
EditIcon,
IconButton,
Pane,
RingIcon,
Tooltip,
} from "evergreen-ui";
import { IconButton } from "@app/components/IconButton.js";
import {
TabbedContent,
TabType,
} from "@components/layout/page/TabbedContent.js";
import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.js";
import { useDevice } from "@core/providers/useDevice.js";
import {
EllipsisHorizontalCircleIcon,
PencilIcon,
XCircleIcon,
} from "@heroicons/react/24/outline";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { ChannelChat } from "./ChannelChat.js";
export const MessagesPage = (): JSX.Element => {
const { channels, setActivePage } = useDevice();
@@ -28,29 +24,32 @@ export const MessagesPage = (): JSX.Element => {
: channel.config.index === 0
? "Primary"
: `Ch ${channel.config.index}`,
icon: channel.messages.length ? RingIcon : CircleIcon,
icon: channel.messages.length ? (
<EllipsisHorizontalCircleIcon className="h-4" />
) : (
<XCircleIcon className="h-4" />
),
element: () => <ChannelChat channel={channel} />,
disabled: channel.config.role === Protobuf.Channel_Role.DISABLED,
};
});
return (
<Pane display="flex" flexDirection="column" width="100%">
<div className="flex flex-col w-full">
<TabbedContent
tabs={tabs}
actions={[
() => (
<Tooltip content="Edit Channels">
<IconButton
icon={EditIcon}
onClick={() => {
setActivePage("channels");
}}
/>
</Tooltip>
<IconButton
variant="secondary"
icon={<PencilIcon className="h-4" />}
onClick={() => {
setActivePage("channels");
}}
/>
),
]}
/>
</Pane>
</div>
);
};
-103
View File
@@ -1,103 +0,0 @@
import type React from "react";
import { ChangeEvent, useState } from "react";
import {
AddLocationIcon,
IconButton,
majorScale,
Pane,
Popover,
SendMessageIcon,
TextInputField,
Tooltip,
} from "evergreen-ui";
import { useDevice } from "@core/providers/useDevice.js";
import type { Channel } from "@core/stores/deviceStore.js";
import { Message } from "@pages/Messages/Message.js";
import { NewLocationMessage } from "@pages/Messages/NewLocationMessage.js";
export interface ChannelChatProps {
channel: Channel;
}
export const ChannelChat = ({ channel }: ChannelChatProps): JSX.Element => {
const { nodes, connection, ackMessage } = useDevice();
const [currentMessage, setCurrentMessage] = useState("");
const sendMessage = (): void => {
void connection?.sendText(
currentMessage,
undefined,
true,
channel.config.index,
(id) => {
ackMessage(channel.config.index, id);
return Promise.resolve();
}
);
setCurrentMessage("");
};
return (
<Pane display="flex" flexDirection="column" flexGrow={1}>
<Pane display="flex" flexDirection="column" flexGrow={1}>
{channel.messages.map((message, index) => (
<Message
key={index}
message={message}
lastMsgSameUser={
index === 0
? false
: channel.messages[index - 1].packet.from ===
message.packet.from
}
sender={
nodes.find((node) => node.data.num === message.packet.from)?.data
}
/>
))}
</Pane>
<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={AddLocationIcon}
marginTop={majorScale(2)}
width={majorScale(8)}
/>
</Popover>
</Tooltip>
</Pane>
</Pane>
);
};
-94
View File
@@ -1,94 +0,0 @@
import type React from "react";
import {
CircleIcon,
FullCircleIcon,
majorScale,
Pane,
Small,
Strong,
Text,
} from "evergreen-ui";
import type { AllMessageTypes } from "@app/core/stores/deviceStore.js";
import { WaypointMessage } from "@app/pages/Messages/WaypointMessage.js";
import { useDevice } from "@core/providers/useDevice.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import type { Protobuf } from "@meshtastic/meshtasticjs";
export interface MessageProps {
lastMsgSameUser: boolean;
message: AllMessageTypes;
sender?: Protobuf.NodeInfo;
}
export const Message = ({
lastMsgSameUser,
message,
sender,
}: MessageProps): JSX.Element => {
const { setPeerInfoOpen, setActivePeer } = useDevice();
const openPeer = (): void => {
setActivePeer(message.packet.from);
setPeerInfoOpen(true);
};
return lastMsgSameUser ? (
<Pane display="flex" marginLeft={majorScale(3)}>
{message.ack ? (
<FullCircleIcon color="#9c9fab" marginY="auto" size={8} />
) : (
<CircleIcon color="#9c9fab" marginY="auto" size={8} />
)}
{"waypointID" in message ? (
<WaypointMessage waypointID={message.waypointID} />
) : (
<Text
color={message.ack ? "#474d66" : "#9c9fab"}
marginLeft={majorScale(2)}
paddingLeft={majorScale(1)}
borderLeft="3px solid #e6e6e6"
>
{message.text}
</Text>
)}
</Pane>
) : (
<Pane marginX={majorScale(2)} gap={majorScale(1)} marginTop={majorScale(1)}>
<Pane display="flex" gap={majorScale(1)}>
<Pane onClick={openPeer} cursor="pointer" width={majorScale(3)}>
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
</Pane>
<Strong onClick={openPeer} cursor="pointer" size={500}>
{sender?.user?.longName ?? "UNK"}
</Strong>
<Small>
{new Date(message.packet.rxTime).toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
})}
</Small>
</Pane>
<Pane display="flex" marginLeft={majorScale(1)}>
{message.ack ? (
<FullCircleIcon color="#9c9fab" marginY="auto" size={8} />
) : (
<CircleIcon color="#9c9fab" marginY="auto" size={8} />
)}
{"waypointID" in message ? (
<WaypointMessage waypointID={message.waypointID} />
) : (
<Text
color={message.ack ? "#474d66" : "#9c9fab"}
marginLeft={majorScale(2)}
paddingLeft={majorScale(1)}
borderLeft="3px solid #e6e6e6"
>
{message.text}
</Text>
)}
</Pane>
</Pane>
);
};
-55
View File
@@ -1,55 +0,0 @@
import type React from "react";
import {
Button,
majorScale,
Pane,
SelectField,
TextInputField,
} from "evergreen-ui";
import { useDevice } from "@core/providers/useDevice.js";
import { renderOptions } from "@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?.sendWaypoint(
Protobuf.Waypoint.create({
latitudeI: Math.floor(3.89103 * 1e7),
longitudeI: Math.floor(105.87005 * 1e7),
name: "TEST",
description: "This is a description",
})
);
}}
>
Send
</Button>
</form>
</Pane>
);
};
-52
View File
@@ -1,52 +0,0 @@
import type React from "react";
import {
Heading,
LocateIcon,
majorScale,
minorScale,
Pane,
Small,
Text,
} from "evergreen-ui";
import { useDevice } from "@app/core/providers/useDevice.js";
import { toMGRS } from "@core/utils/toMGRS.js";
export interface WaypointMessageProps {
waypointID: number;
}
export const WaypointMessage = ({
waypointID,
}: WaypointMessageProps): JSX.Element => {
const { waypoints } = useDevice();
const waypoint = waypoints.find((wp) => wp.id === waypointID);
return (
<Pane
marginLeft={majorScale(2)}
paddingLeft={majorScale(1)}
borderLeft="3px solid #e6e6e6"
>
<Pane
gap={majorScale(1)}
display="flex"
borderRadius={majorScale(1)}
elevation={1}
padding={minorScale(1)}
>
<LocateIcon color="#474d66" marginY="auto" />
<Pane>
<Pane display="flex" gap={majorScale(1)}>
<Heading>{waypoint?.name}</Heading>
<Text color="orange">
{toMGRS(waypoint?.latitudeI, waypoint?.longitudeI)}
</Text>
</Pane>
<Small>{waypoint?.description}</Small>
</Pane>
</Pane>
</Pane>
);
};