format
This commit is contained in:
@@ -73,9 +73,10 @@ export const ImportDialog = ({
|
||||
connection?.setChannel(
|
||||
create(Protobuf.Channel.ChannelSchema, {
|
||||
index,
|
||||
role: index === 0
|
||||
? Protobuf.Channel.Channel_Role.PRIMARY
|
||||
: Protobuf.Channel.Channel_Role.SECONDARY,
|
||||
role:
|
||||
index === 0
|
||||
? Protobuf.Channel.Channel_Role.PRIMARY
|
||||
: Protobuf.Channel.Channel_Role.SECONDARY,
|
||||
settings: ch,
|
||||
}),
|
||||
);
|
||||
@@ -122,25 +123,21 @@ export const ImportDialog = ({
|
||||
checked={channelSet?.loraConfig?.usePreset ?? true}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
/* <Select
|
||||
{/* <Select
|
||||
label="Modem Preset"
|
||||
disabled
|
||||
value={channelSet?.loraConfig?.modemPreset}
|
||||
>
|
||||
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
|
||||
</Select> */
|
||||
}
|
||||
</Select> */}
|
||||
</div>
|
||||
{
|
||||
/* <Select
|
||||
{/* <Select
|
||||
label="Region"
|
||||
disabled
|
||||
value={channelSet?.loraConfig?.region}
|
||||
>
|
||||
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
|
||||
</Select> */
|
||||
}
|
||||
</Select> */}
|
||||
|
||||
<span className="text-md block font-medium text-text-primary">
|
||||
Channels:
|
||||
|
||||
@@ -24,9 +24,11 @@ export const LocationResponseDialog = ({
|
||||
const { nodes } = useDevice();
|
||||
|
||||
const from = nodes.get(location?.from ?? 0);
|
||||
const longName = from?.user?.longName ??
|
||||
const longName =
|
||||
from?.user?.longName ??
|
||||
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
|
||||
const shortName = from?.user?.shortName ??
|
||||
const shortName =
|
||||
from?.user?.shortName ??
|
||||
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
|
||||
|
||||
return (
|
||||
|
||||
@@ -32,159 +32,134 @@ export const NodeDetailsDialog = ({
|
||||
const { nodeNumDetails } = useAppStore();
|
||||
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
|
||||
|
||||
return device
|
||||
? (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Node Details for {device.user?.longName ?? "UNKNOWN"} (
|
||||
{device.user?.shortName ?? "UNK"})
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="w-full">
|
||||
<DeviceImage
|
||||
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
|
||||
deviceType={Protobuf.Mesh
|
||||
.HardwareModel[device.user?.hwModel ?? 0]}
|
||||
/>
|
||||
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Details:
|
||||
</p>
|
||||
<p>
|
||||
Hardware:{" "}
|
||||
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
|
||||
</p>
|
||||
<p>Node Number: {device.num}</p>
|
||||
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
|
||||
<p>
|
||||
Role: {Protobuf.Config.Config_DeviceConfig_Role[
|
||||
return device ? (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Node Details for {device.user?.longName ?? "UNKNOWN"} (
|
||||
{device.user?.shortName ?? "UNK"})
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="w-full">
|
||||
<DeviceImage
|
||||
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
|
||||
deviceType={
|
||||
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]
|
||||
}
|
||||
/>
|
||||
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Details:
|
||||
</p>
|
||||
<p>
|
||||
Hardware:{" "}
|
||||
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
|
||||
</p>
|
||||
<p>Node Number: {device.num}</p>
|
||||
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
|
||||
<p>
|
||||
Role:{" "}
|
||||
{
|
||||
Protobuf.Config.Config_DeviceConfig_Role[
|
||||
device.user?.role ?? 0
|
||||
]}
|
||||
</p>
|
||||
<p>
|
||||
Last Heard: {device.lastHeard === 0
|
||||
? (
|
||||
"Never"
|
||||
)
|
||||
: <TimeAgo timestamp={device.lastHeard * 1000} />}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{device.position
|
||||
? (
|
||||
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Position:
|
||||
</p>
|
||||
{device.position.latitudeI && device.position.longitudeI
|
||||
? (
|
||||
<p>
|
||||
Coordinates:{" "}
|
||||
<a
|
||||
className="text-blue-500 dark:text-blue-400"
|
||||
href={`https://www.openstreetmap.org/?mlat=${
|
||||
device.position.latitudeI / 1e7
|
||||
}&mlon=${
|
||||
device.position.longitudeI / 1e7
|
||||
}&layers=N`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{device.position.latitudeI / 1e7},{" "}
|
||||
{device.position.longitudeI / 1e7}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
{device.position.altitude
|
||||
? <p>Altitude: {device.position.altitude}m</p>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
|
||||
{device.deviceMetrics
|
||||
? (
|
||||
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Device Metrics:
|
||||
</p>
|
||||
{device.deviceMetrics.airUtilTx
|
||||
? (
|
||||
<p>
|
||||
Air TX utilization:{" "}
|
||||
{device.deviceMetrics.airUtilTx.toFixed(2)}%
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
{device.deviceMetrics.channelUtilization
|
||||
? (
|
||||
<p>
|
||||
Channel utilization:{" "}
|
||||
{device.deviceMetrics.channelUtilization.toFixed(2)}%
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
{device.deviceMetrics.batteryLevel
|
||||
? (
|
||||
<p>
|
||||
Battery level:{" "}
|
||||
{device.deviceMetrics.batteryLevel.toFixed(2)}%
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
{device.deviceMetrics.voltage
|
||||
? (
|
||||
<p>
|
||||
Voltage: {device.deviceMetrics.voltage.toFixed(2)}V
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
{device.deviceMetrics.uptimeSeconds
|
||||
? (
|
||||
<p>
|
||||
Uptime:{" "}
|
||||
<Uptime
|
||||
seconds={device.deviceMetrics.uptimeSeconds}
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
|
||||
{device
|
||||
? (
|
||||
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<Accordion
|
||||
className="AccordionRoot"
|
||||
type="single"
|
||||
collapsible
|
||||
>
|
||||
<AccordionItem className="AccordionItem" value="item-1">
|
||||
<AccordionTrigger>
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
All Raw Metrics:
|
||||
</p>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="overflow-x-scroll">
|
||||
<pre className="text-xs w-full">
|
||||
{JSON.stringify(device, null, 2)}
|
||||
</pre>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
]
|
||||
}
|
||||
</p>
|
||||
<p>
|
||||
Last Heard:{" "}
|
||||
{device.lastHeard === 0 ? (
|
||||
"Never"
|
||||
) : (
|
||||
<TimeAgo timestamp={device.lastHeard * 1000} />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
: null;
|
||||
|
||||
{device.position ? (
|
||||
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Position:
|
||||
</p>
|
||||
{device.position.latitudeI && device.position.longitudeI ? (
|
||||
<p>
|
||||
Coordinates:{" "}
|
||||
<a
|
||||
className="text-blue-500 dark:text-blue-400"
|
||||
href={`https://www.openstreetmap.org/?mlat=${
|
||||
device.position.latitudeI / 1e7
|
||||
}&mlon=${device.position.longitudeI / 1e7}&layers=N`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{device.position.latitudeI / 1e7},{" "}
|
||||
{device.position.longitudeI / 1e7}
|
||||
</a>
|
||||
</p>
|
||||
) : null}
|
||||
{device.position.altitude ? (
|
||||
<p>Altitude: {device.position.altitude}m</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{device.deviceMetrics ? (
|
||||
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Device Metrics:
|
||||
</p>
|
||||
{device.deviceMetrics.airUtilTx ? (
|
||||
<p>
|
||||
Air TX utilization:{" "}
|
||||
{device.deviceMetrics.airUtilTx.toFixed(2)}%
|
||||
</p>
|
||||
) : null}
|
||||
{device.deviceMetrics.channelUtilization ? (
|
||||
<p>
|
||||
Channel utilization:{" "}
|
||||
{device.deviceMetrics.channelUtilization.toFixed(2)}%
|
||||
</p>
|
||||
) : null}
|
||||
{device.deviceMetrics.batteryLevel ? (
|
||||
<p>
|
||||
Battery level:{" "}
|
||||
{device.deviceMetrics.batteryLevel.toFixed(2)}%
|
||||
</p>
|
||||
) : null}
|
||||
{device.deviceMetrics.voltage ? (
|
||||
<p>Voltage: {device.deviceMetrics.voltage.toFixed(2)}V</p>
|
||||
) : null}
|
||||
{device.deviceMetrics.uptimeSeconds ? (
|
||||
<p>
|
||||
Uptime:{" "}
|
||||
<Uptime seconds={device.deviceMetrics.uptimeSeconds} />
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{device ? (
|
||||
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
|
||||
<Accordion className="AccordionRoot" type="single" collapsible>
|
||||
<AccordionItem className="AccordionItem" value="item-1">
|
||||
<AccordionTrigger>
|
||||
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
All Raw Metrics:
|
||||
</p>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="overflow-x-scroll">
|
||||
<pre className="text-xs w-full">
|
||||
{JSON.stringify(device, null, 2)}
|
||||
</pre>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -31,9 +31,11 @@ export const NodeOptionsDialog = ({
|
||||
setChatType,
|
||||
setActiveChat,
|
||||
} = useAppStore();
|
||||
const longName = node?.user?.longName ??
|
||||
const longName =
|
||||
node?.user?.longName ??
|
||||
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
|
||||
const shortName = node?.user?.shortName ??
|
||||
const shortName =
|
||||
node?.user?.shortName ??
|
||||
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");
|
||||
|
||||
function handleDirectMessage() {
|
||||
@@ -51,7 +53,7 @@ export const NodeOptionsDialog = ({
|
||||
connection?.requestPosition(node.num).then(() =>
|
||||
toast({
|
||||
title: "Position request sent.",
|
||||
})
|
||||
}),
|
||||
);
|
||||
onOpenChange();
|
||||
}
|
||||
@@ -64,7 +66,7 @@ export const NodeOptionsDialog = ({
|
||||
connection?.traceRoute(node.num).then(() =>
|
||||
toast({
|
||||
title: "Traceroute sent.",
|
||||
})
|
||||
}),
|
||||
);
|
||||
onOpenChange();
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ export const QRDialog = ({
|
||||
{channel.settings?.name.length
|
||||
? channel.settings.name
|
||||
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY
|
||||
? "Primary"
|
||||
: `Channel: ${channel.index}`}
|
||||
? "Primary"
|
||||
: `Channel: ${channel.index}`}
|
||||
</Label>
|
||||
<Checkbox
|
||||
key={channel.index}
|
||||
@@ -86,9 +86,7 @@ export const QRDialog = ({
|
||||
onCheckedChange={() => {
|
||||
if (selectedChannels.includes(channel.index)) {
|
||||
setSelectedChannels(
|
||||
selectedChannels.filter((c) =>
|
||||
c !== channel.index
|
||||
),
|
||||
selectedChannels.filter((c) => c !== channel.index),
|
||||
);
|
||||
} else {
|
||||
setSelectedChannels([
|
||||
|
||||
@@ -28,9 +28,11 @@ export const TracerouteResponseDialog = ({
|
||||
const snrTowards = traceroute?.data.snrTowards ?? [];
|
||||
const snrBack = traceroute?.data.snrBack ?? [];
|
||||
const from = nodes.get(traceroute?.from ?? 0);
|
||||
const longName = from?.user?.longName ??
|
||||
const longName =
|
||||
from?.user?.longName ??
|
||||
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
|
||||
const shortName = from?.user?.shortName ??
|
||||
const shortName =
|
||||
from?.user?.shortName ??
|
||||
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
|
||||
const to = nodes.get(traceroute?.to ?? 0);
|
||||
return (
|
||||
|
||||
@@ -24,9 +24,8 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
channel?.settings?.psk.length ?? 16,
|
||||
);
|
||||
const [validationText, setValidationText] = useState<string>();
|
||||
const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [preSharedDialogOpen, setPreSharedDialogOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const onSubmit = (data: ChannelValidation) => {
|
||||
const channel = create(Protobuf.Channel.ChannelSchema, {
|
||||
@@ -108,7 +107,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
channel?.settings?.moduleSettings?.positionPrecision === 32,
|
||||
positionPrecision:
|
||||
channel?.settings?.moduleSettings?.positionPrecision ===
|
||||
undefined
|
||||
undefined
|
||||
? 10
|
||||
: channel?.settings?.moduleSettings?.positionPrecision,
|
||||
},
|
||||
@@ -127,9 +126,10 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
description:
|
||||
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
||||
properties: {
|
||||
enumValue: channel.index === 0
|
||||
? { PRIMARY: 1 }
|
||||
: { DISABLED: 0, SECONDARY: 2 },
|
||||
enumValue:
|
||||
channel.index === 0
|
||||
? { PRIMARY: 1 }
|
||||
: { DISABLED: 0, SECONDARY: 2 },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -192,31 +192,32 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
description:
|
||||
"If not sharing precise location, position shared on channel will be accurate within this distance",
|
||||
properties: {
|
||||
enumValue: config.display?.units === 0
|
||||
? {
|
||||
"Within 23 km": 10,
|
||||
"Within 12 km": 11,
|
||||
"Within 5.8 km": 12,
|
||||
"Within 2.9 km": 13,
|
||||
"Within 1.5 km": 14,
|
||||
"Within 700 m": 15,
|
||||
"Within 350 m": 16,
|
||||
"Within 200 m": 17,
|
||||
"Within 90 m": 18,
|
||||
"Within 50 m": 19,
|
||||
}
|
||||
: {
|
||||
"Within 15 miles": 10,
|
||||
"Within 7.3 miles": 11,
|
||||
"Within 3.6 miles": 12,
|
||||
"Within 1.8 miles": 13,
|
||||
"Within 0.9 miles": 14,
|
||||
"Within 0.5 miles": 15,
|
||||
"Within 0.2 miles": 16,
|
||||
"Within 600 feet": 17,
|
||||
"Within 300 feet": 18,
|
||||
"Within 150 feet": 19,
|
||||
},
|
||||
enumValue:
|
||||
config.display?.units === 0
|
||||
? {
|
||||
"Within 23 km": 10,
|
||||
"Within 12 km": 11,
|
||||
"Within 5.8 km": 12,
|
||||
"Within 2.9 km": 13,
|
||||
"Within 1.5 km": 14,
|
||||
"Within 700 m": 15,
|
||||
"Within 350 m": 16,
|
||||
"Within 200 m": 17,
|
||||
"Within 90 m": 18,
|
||||
"Within 50 m": 19,
|
||||
}
|
||||
: {
|
||||
"Within 15 miles": 10,
|
||||
"Within 7.3 miles": 11,
|
||||
"Within 3.6 miles": 12,
|
||||
"Within 1.8 miles": 13,
|
||||
"Within 0.9 miles": 14,
|
||||
"Within 0.5 miles": 15,
|
||||
"Within 0.2 miles": 16,
|
||||
"Within 600 feet": 17,
|
||||
"Within 300 feet": 18,
|
||||
"Within 150 feet": 19,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -111,8 +111,9 @@ export const Bluetooth = () => {
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "mode",
|
||||
selector: Protobuf.Config.Config_BluetoothConfig_PairingMode
|
||||
.FIXED_PIN,
|
||||
selector:
|
||||
Protobuf.Config.Config_BluetoothConfig_PairingMode
|
||||
.FIXED_PIN,
|
||||
invert: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -37,23 +37,21 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
||||
<Avatar text={node.user?.shortName} />
|
||||
|
||||
<div>
|
||||
{node.user?.publicKey && node.user?.publicKey.length > 0
|
||||
? (
|
||||
<LockIcon
|
||||
className="text-green-600"
|
||||
size={12}
|
||||
strokeWidth={3}
|
||||
aria-label="Public Key Enabled"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<LockOpenIcon
|
||||
className="text-yellow-500"
|
||||
size={12}
|
||||
strokeWidth={3}
|
||||
aria-label="No Public Key"
|
||||
/>
|
||||
)}
|
||||
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
|
||||
<LockIcon
|
||||
className="text-green-600"
|
||||
size={12}
|
||||
strokeWidth={3}
|
||||
aria-label="Public Key Enabled"
|
||||
/>
|
||||
) : (
|
||||
<LockOpenIcon
|
||||
className="text-yellow-500"
|
||||
size={12}
|
||||
strokeWidth={3}
|
||||
aria-label="No Public Key"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Star
|
||||
@@ -75,13 +73,15 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
||||
node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown"
|
||||
} volts`}
|
||||
>
|
||||
{node.deviceMetrics?.batteryLevel > 100
|
||||
? <BatteryChargingIcon size={22} />
|
||||
: node.deviceMetrics?.batteryLevel > 80
|
||||
? <BatteryFullIcon size={22} />
|
||||
: node.deviceMetrics?.batteryLevel > 20
|
||||
? <BatteryMediumIcon size={22} />
|
||||
: <BatteryLowIcon size={22} />}
|
||||
{node.deviceMetrics?.batteryLevel > 100 ? (
|
||||
<BatteryChargingIcon size={22} />
|
||||
) : node.deviceMetrics?.batteryLevel > 80 ? (
|
||||
<BatteryFullIcon size={22} />
|
||||
) : node.deviceMetrics?.batteryLevel > 20 ? (
|
||||
<BatteryMediumIcon size={22} />
|
||||
) : (
|
||||
<BatteryLowIcon size={22} />
|
||||
)}
|
||||
<Subtle aria-label="Battery">
|
||||
{node.deviceMetrics?.batteryLevel > 100
|
||||
? "Charging"
|
||||
|
||||
@@ -34,7 +34,8 @@ export const ChannelChat = ({
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
if (scrollContainer) {
|
||||
const isNearBottom = scrollContainer.scrollHeight -
|
||||
const isNearBottom =
|
||||
scrollContainer.scrollHeight -
|
||||
scrollContainer.scrollTop -
|
||||
scrollContainer.clientHeight <
|
||||
100;
|
||||
@@ -71,8 +72,9 @@ export const ChannelChat = ({
|
||||
key={message.id}
|
||||
message={message}
|
||||
sender={nodes.get(message.from)}
|
||||
lastMsgSameUser={index > 0 &&
|
||||
messages[index - 1].from === message.from}
|
||||
lastMsgSameUser={
|
||||
index > 0 && messages[index - 1].from === message.from
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const MessageInput = ({
|
||||
myNodeNum,
|
||||
id,
|
||||
"ack",
|
||||
)
|
||||
),
|
||||
)
|
||||
.catch((e: Types.PacketError) =>
|
||||
setMessageState(
|
||||
@@ -61,7 +61,7 @@ export const MessageInput = ({
|
||||
myNodeNum,
|
||||
e.id,
|
||||
e.error,
|
||||
)
|
||||
),
|
||||
);
|
||||
},
|
||||
[channel, connection, myNodeNum, setMessageState, to],
|
||||
|
||||
@@ -38,25 +38,23 @@ export const TraceRoute = ({
|
||||
))}
|
||||
{from?.user?.longName}
|
||||
</span>
|
||||
{routeBack
|
||||
? (
|
||||
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
|
||||
<p className="font-semibold">Route back:</p>
|
||||
<p>{from?.user?.longName}</p>
|
||||
<p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
|
||||
{routeBack.map((hop, i) => (
|
||||
<span key={nodes.get(hop)?.num}>
|
||||
<p>
|
||||
{nodes.get(hop)?.user?.longName ??
|
||||
`!${numberToHexUnpadded(hop)}`}
|
||||
</p>
|
||||
<p>↓ {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
|
||||
</span>
|
||||
))}
|
||||
{to?.user?.longName}
|
||||
</span>
|
||||
)
|
||||
: null}
|
||||
{routeBack ? (
|
||||
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
|
||||
<p className="font-semibold">Route back:</p>
|
||||
<p>{from?.user?.longName}</p>
|
||||
<p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
|
||||
{routeBack.map((hop, i) => (
|
||||
<span key={nodes.get(hop)?.num}>
|
||||
<p>
|
||||
{nodes.get(hop)?.user?.longName ??
|
||||
`!${numberToHexUnpadded(hop)}`}
|
||||
</p>
|
||||
<p>↓ {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
|
||||
</span>
|
||||
))}
|
||||
{to?.user?.longName}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -299,11 +299,11 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
const workingModuleConfigIndex = device?.workingModuleConfig
|
||||
.findIndex(
|
||||
const workingModuleConfigIndex =
|
||||
device?.workingModuleConfig.findIndex(
|
||||
(wmc) =>
|
||||
wmc.payloadVariant.case ===
|
||||
moduleConfig.payloadVariant.case,
|
||||
moduleConfig.payloadVariant.case,
|
||||
);
|
||||
if (workingModuleConfigIndex !== -1) {
|
||||
device.workingModuleConfig[workingModuleConfigIndex] =
|
||||
@@ -445,7 +445,8 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
const currentNode = device.nodes.get(user.from) ??
|
||||
const currentNode =
|
||||
device.nodes.get(user.from) ??
|
||||
create(Protobuf.Mesh.NodeInfoSchema);
|
||||
currentNode.user = user.data;
|
||||
device.nodes.set(user.from, currentNode);
|
||||
@@ -459,7 +460,8 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
const currentNode = device.nodes.get(position.from) ??
|
||||
const currentNode =
|
||||
device.nodes.get(position.from) ??
|
||||
create(Protobuf.Mesh.NodeInfoSchema);
|
||||
currentNode.position = position.data;
|
||||
device.nodes.set(position.from, currentNode);
|
||||
@@ -484,11 +486,12 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
|
||||
return;
|
||||
}
|
||||
const messageGroup = device.messages[message.type];
|
||||
const messageIndex = message.type === "direct"
|
||||
? message.from === device.hardware.myNodeNum
|
||||
? message.to
|
||||
: message.from
|
||||
: message.channel;
|
||||
const messageIndex =
|
||||
message.type === "direct"
|
||||
? message.from === device.hardware.myNodeNum
|
||||
? message.to
|
||||
: message.from
|
||||
: message.channel;
|
||||
const messages = messageGroup.get(messageIndex);
|
||||
|
||||
if (messages) {
|
||||
@@ -561,9 +564,12 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
|
||||
}
|
||||
const messageGroup = device.messages[type];
|
||||
|
||||
const messageIndex = type === "direct"
|
||||
? from === device.hardware.myNodeNum ? to : from
|
||||
: channelIndex;
|
||||
const messageIndex =
|
||||
type === "direct"
|
||||
? from === device.hardware.myNodeNum
|
||||
? to
|
||||
: from
|
||||
: channelIndex;
|
||||
const messages = messageGroup.get(messageIndex);
|
||||
|
||||
if (!messages) {
|
||||
|
||||
@@ -17,8 +17,8 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) =>
|
||||
channel.settings?.name.length
|
||||
? channel.settings?.name
|
||||
: channel.index === 0
|
||||
? "Primary"
|
||||
: `Ch ${channel.index}`;
|
||||
? "Primary"
|
||||
: `Ch ${channel.index}`;
|
||||
|
||||
const ChannelsPage = () => {
|
||||
const { channels, setDialogOpen } = useDevice();
|
||||
|
||||
+35
-30
@@ -39,11 +39,13 @@ export const MessagesPage = () => {
|
||||
{filteredChannels.map((channel) => (
|
||||
<SidebarButton
|
||||
key={channel.index}
|
||||
label={channel.settings?.name.length
|
||||
? channel.settings?.name
|
||||
: channel.index === 0
|
||||
? "Primary"
|
||||
: `Ch ${channel.index}`}
|
||||
label={
|
||||
channel.settings?.name.length
|
||||
? channel.settings?.name
|
||||
: channel.index === 0
|
||||
? "Primary"
|
||||
: `Ch ${channel.index}`
|
||||
}
|
||||
active={activeChat === channel.index}
|
||||
onClick={() => {
|
||||
setChatType("broadcast");
|
||||
@@ -67,8 +69,9 @@ export const MessagesPage = () => {
|
||||
{filteredNodes.map((node) => (
|
||||
<SidebarButton
|
||||
key={node.num}
|
||||
label={node.user?.longName ??
|
||||
`!${numberToHexUnpadded(node.num)}`}
|
||||
label={
|
||||
node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`
|
||||
}
|
||||
active={activeChat === node.num}
|
||||
onClick={() => {
|
||||
setChatType("direct");
|
||||
@@ -91,30 +94,32 @@ export const MessagesPage = () => {
|
||||
chatType === "broadcast" && currentChannel
|
||||
? getChannelName(currentChannel)
|
||||
: chatType === "direct" && nodes.get(activeChat)
|
||||
? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
|
||||
: "Loading..."
|
||||
? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
|
||||
: "Loading..."
|
||||
}`}
|
||||
actions={chatType === "direct"
|
||||
? [
|
||||
{
|
||||
icon: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? LockIcon
|
||||
: LockOpenIcon,
|
||||
iconClasses: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? "text-green-600"
|
||||
: "text-yellow-300",
|
||||
async onClick() {
|
||||
const targetNode = nodes.get(activeChat)?.num;
|
||||
if (targetNode === undefined) return;
|
||||
toast({
|
||||
title: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? "Chat is using PKI encryption."
|
||||
: "Chat is using PSK encryption.",
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: []}
|
||||
actions={
|
||||
chatType === "direct"
|
||||
? [
|
||||
{
|
||||
icon: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? LockIcon
|
||||
: LockOpenIcon,
|
||||
iconClasses: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? "text-green-600"
|
||||
: "text-yellow-300",
|
||||
async onClick() {
|
||||
const targetNode = nodes.get(activeChat)?.num;
|
||||
if (targetNode === undefined) return;
|
||||
toast({
|
||||
title: nodes.get(activeChat)?.user?.publicKey.length
|
||||
? "Chat is using PKI encryption."
|
||||
: "Chat is using PSK encryption.",
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
>
|
||||
{allChannels.map(
|
||||
(channel) =>
|
||||
|
||||
+16
-16
@@ -107,11 +107,9 @@ const NodesPage = (): JSX.Element => {
|
||||
>
|
||||
{node.user?.shortName ??
|
||||
(node.user?.macaddr
|
||||
? `${
|
||||
base16
|
||||
? `${base16
|
||||
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
||||
.toLowerCase()
|
||||
}`
|
||||
.toLowerCase()}`
|
||||
: `${numberToHexUnpadded(node.num).slice(-4)}`)}
|
||||
</h1>,
|
||||
|
||||
@@ -122,11 +120,9 @@ const NodesPage = (): JSX.Element => {
|
||||
>
|
||||
{node.user?.longName ??
|
||||
(node.user?.macaddr
|
||||
? `Meshtastic ${
|
||||
base16
|
||||
? `Meshtastic ${base16
|
||||
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
||||
.toLowerCase()
|
||||
}`
|
||||
.toLowerCase()}`
|
||||
: `!${numberToHexUnpadded(node.num)}`)}
|
||||
</h1>,
|
||||
|
||||
@@ -140,9 +136,11 @@ const NodesPage = (): JSX.Element => {
|
||||
?.join(":") ?? "UNK"}
|
||||
</Mono>,
|
||||
<Fragment key="lastHeard">
|
||||
{node.lastHeard === 0
|
||||
? <p>Never</p>
|
||||
: <TimeAgo timestamp={node.lastHeard * 1000} />}
|
||||
{node.lastHeard === 0 ? (
|
||||
<p>Never</p>
|
||||
) : (
|
||||
<TimeAgo timestamp={node.lastHeard * 1000} />
|
||||
)}
|
||||
</Fragment>,
|
||||
<Mono key="snr">
|
||||
{node.snr}db/
|
||||
@@ -150,17 +148,19 @@ const NodesPage = (): JSX.Element => {
|
||||
{(node.snr + 10) * 5}raw
|
||||
</Mono>,
|
||||
<Mono key="pki">
|
||||
{node.user?.publicKey && node.user?.publicKey.length > 0
|
||||
? <LockIcon className="text-green-600" />
|
||||
: <LockOpenIcon className="text-yellow-300 mx-auto" />}
|
||||
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
|
||||
<LockIcon className="text-green-600" />
|
||||
) : (
|
||||
<LockOpenIcon className="text-yellow-300 mx-auto" />
|
||||
)}
|
||||
</Mono>,
|
||||
<Mono key="hops">
|
||||
{node.lastHeard !== 0
|
||||
? node.viaMqtt === false && node.hopsAway === 0
|
||||
? "Direct"
|
||||
: `${node.hopsAway.toString()} ${
|
||||
node.hopsAway > 1 ? "hops" : "hop"
|
||||
} away`
|
||||
node.hopsAway > 1 ? "hops" : "hop"
|
||||
} away`
|
||||
: "-"}
|
||||
{node.viaMqtt === true ? ", via MQTT" : ""}
|
||||
</Mono>,
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
} from "class-validator";
|
||||
|
||||
export class ChannelValidation
|
||||
implements Omit<Protobuf.Channel.Channel, keyof Message | "settings"> {
|
||||
implements Omit<Protobuf.Channel.Channel, keyof Message | "settings">
|
||||
{
|
||||
@IsNumber()
|
||||
index: number;
|
||||
|
||||
@@ -21,7 +22,8 @@ export class ChannelValidation
|
||||
}
|
||||
|
||||
export class Channel_SettingsValidation
|
||||
implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk"> {
|
||||
implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk">
|
||||
{
|
||||
@IsNumber()
|
||||
channelNum: number;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { IsBoolean, IsEnum, IsInt } from "class-validator";
|
||||
|
||||
export class BluetoothValidation implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_BluetoothConfig,
|
||||
keyof Message | "deviceLoggingEnabled"
|
||||
> {
|
||||
export class BluetoothValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_BluetoothConfig,
|
||||
keyof Message | "deviceLoggingEnabled"
|
||||
>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ import { IsArray, IsBoolean, IsEnum, IsInt } from "class-validator";
|
||||
|
||||
const DeprecatedPositionValidationFields = ["gpsEnabled", "gpsAttemptTime"];
|
||||
|
||||
export class PositionValidation implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_PositionConfig,
|
||||
keyof Message | (typeof DeprecatedPositionValidationFields)[number]
|
||||
> {
|
||||
export class PositionValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_PositionConfig,
|
||||
keyof Message | (typeof DeprecatedPositionValidationFields)[number]
|
||||
>
|
||||
{
|
||||
@IsInt()
|
||||
positionBroadcastSecs: number;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
import type { Protobuf } from "@meshtastic/core";
|
||||
import { IsBoolean, IsString } from "class-validator";
|
||||
|
||||
export class SecurityValidation implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_SecurityConfig,
|
||||
keyof Message | "adminKey" | "privateKey" | "publicKey"
|
||||
> {
|
||||
export class SecurityValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.Config.Config_SecurityConfig,
|
||||
keyof Message | "adminKey" | "privateKey" | "publicKey"
|
||||
>
|
||||
{
|
||||
@IsBoolean()
|
||||
adminChannelEnabled: boolean;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
import type { Protobuf } from "@meshtastic/core";
|
||||
import { IsBoolean, IsInt } from "class-validator";
|
||||
|
||||
export class AmbientLightingValidation implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig,
|
||||
keyof Message
|
||||
> {
|
||||
export class AmbientLightingValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig,
|
||||
keyof Message
|
||||
>
|
||||
{
|
||||
@IsBoolean()
|
||||
ledState: boolean;
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@ import { IsBoolean, IsEnum, IsInt, Length } from "class-validator";
|
||||
|
||||
export class CannedMessageValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig,
|
||||
keyof Message
|
||||
> {
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig, keyof Message>
|
||||
{
|
||||
@IsBoolean()
|
||||
rotary1Enabled: boolean;
|
||||
|
||||
@@ -21,16 +19,13 @@ export class CannedMessageValidation
|
||||
inputbrokerPinPress: number;
|
||||
|
||||
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
|
||||
inputbrokerEventCw:
|
||||
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
inputbrokerEventCw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
|
||||
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
|
||||
inputbrokerEventCcw:
|
||||
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
inputbrokerEventCcw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
|
||||
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
|
||||
inputbrokerEventPress:
|
||||
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
inputbrokerEventPress: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
|
||||
@IsBoolean()
|
||||
updown1Enabled: boolean;
|
||||
|
||||
@@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
import type { Protobuf } from "@meshtastic/core";
|
||||
import { IsBoolean, IsInt, Length } from "class-validator";
|
||||
|
||||
export class DetectionSensorValidation implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig,
|
||||
keyof Message
|
||||
> {
|
||||
export class DetectionSensorValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig,
|
||||
keyof Message
|
||||
>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
import type { Protobuf } from "@meshtastic/core";
|
||||
import { IsBoolean, IsInt } from "class-validator";
|
||||
|
||||
export class ExternalNotificationValidation implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig,
|
||||
keyof Message
|
||||
> {
|
||||
export class ExternalNotificationValidation
|
||||
implements
|
||||
Omit<
|
||||
Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig,
|
||||
keyof Message
|
||||
>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
|
||||
|
||||
export class NeighborInfoValidation
|
||||
implements
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message> {
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
|
||||
|
||||
export class PaxcounterValidation
|
||||
implements
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message> {
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { IsBoolean, IsEnum, IsInt } from "class-validator";
|
||||
|
||||
export class SerialValidation
|
||||
implements
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message> {
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message>
|
||||
{
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
|
||||
|
||||
export class TelemetryValidation
|
||||
implements
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message> {
|
||||
Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message>
|
||||
{
|
||||
@IsInt()
|
||||
deviceUpdateInterval: number;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user