add new dialogs & cleanup

This commit is contained in:
Sacha Weatherstone
2022-12-11 14:38:55 +10:00
parent fad08fe806
commit 916dcdb600
19 changed files with 388 additions and 259 deletions
+32 -6
View File
@@ -40,6 +40,7 @@ import {
MapIcon,
MoonIcon,
PlusIcon,
PowerIcon,
QrCodeIcon,
Square3Stack3DIcon,
TrashIcon,
@@ -61,9 +62,16 @@ export interface Command {
export const CommandPalette = (): JSX.Element => {
const [query, setQuery] = useState("");
const [open, setOpen] = useState(false);
const { commandPaletteOpen, setCommandPaletteOpen } = useAppStore();
// const [open, setOpen] = useState(false);
const { setQRDialogOpen, setActivePage, connection } = useDevice();
const {
setQRDialogOpen,
setShutdownDialogOpen,
setRebootDialogOpen,
setActivePage,
connection
} = useDevice();
const { setSelectedDevice, removeDevice, selectedDevice } = useAppStore();
const groups: Group[] = [
@@ -181,6 +189,20 @@ export const CommandPalette = (): JSX.Element => {
setSelectedDevice(0);
removeDevice(selectedDevice ?? 0);
}
},
{
name: "Schedule Shutdown",
icon: PowerIcon,
action() {
setShutdownDialogOpen(true);
}
},
{
name: "Schedule Reboot",
icon: ArrowPathIcon,
action() {
setRebootDialogOpen(true);
}
}
]
},
@@ -222,7 +244,7 @@ export const CommandPalette = (): JSX.Element => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen(true);
setCommandPaletteOpen(true);
}
};
@@ -249,12 +271,16 @@ export const CommandPalette = (): JSX.Element => {
return (
<Transition.Root
show={open}
show={commandPaletteOpen}
as={Fragment}
afterLeave={() => setQuery("")}
appear
>
<Dialog as="div" className="relative z-10" onClose={setOpen}>
<Dialog
as="div"
className="relative z-10"
onClose={setCommandPaletteOpen}
>
<PaletteTransition>
<Dialog.Panel className="mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
<Combobox<Command | string>
@@ -262,7 +288,7 @@ export const CommandPalette = (): JSX.Element => {
if (typeof input === "string") {
setQuery(input);
} else if (input.action) {
setOpen(false);
setCommandPaletteOpen(false);
input.action();
}
}}
+2 -9
View File
@@ -4,7 +4,7 @@ import { useAppStore } from "@app/core/stores/appStore.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { Mono } from "@components/generic/Mono.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { CommandLineIcon, PlusIcon } from "@heroicons/react/24/outline";
import { PlusIcon } from "@heroicons/react/24/outline";
export const DeviceSelector = (): JSX.Element => {
const { getDevices } = useDeviceStore();
@@ -43,14 +43,7 @@ export const DeviceSelector = (): JSX.Element => {
<PlusIcon />
</div>
</span>
<img src="Logo_Black.svg" className="mt-auto px-3" />
<div className="my-4 flex flex-col gap-2 [writing-mode:horizontal-tb]">
<CommandLineIcon className="h-6 text-slate-400" />
<Mono className="text-xs">
<kbd className="rounded-md bg-slate-200 p-0.5 pr-1 italic">Ctrl</kbd>+
<kbd className="rounded-md bg-slate-200 p-0.5 pr-1 italic">K</kbd>
</Mono>
</div>
<img src="Logo_Black.svg" className="mt-auto px-3 py-4" />
</div>
);
};
+25 -1
View File
@@ -3,8 +3,20 @@ import type React from "react";
import { useDevice } from "@app/core/providers/useDevice.js";
import { QRDialog } from "@components/Dialog/QRDialog.js";
import { RebootDialog } from "./RebootDialog.js";
import { ShutdownDialog } from "./ShutdownDialog.js";
export const DialogManager = (): JSX.Element => {
const { channels, config, QRDialogOpen, setQRDialogOpen } = useDevice();
const {
channels,
config,
QRDialogOpen,
setQRDialogOpen,
shutdownDialogOpen,
setShutdownDialogOpen,
rebootDialogOpen,
setRebootDialogOpen
} = useDevice();
return (
<>
<QRDialog
@@ -15,6 +27,18 @@ export const DialogManager = (): JSX.Element => {
channels={channels.map((ch) => ch.config)}
loraConfig={config.lora}
/>
<ShutdownDialog
isOpen={shutdownDialogOpen}
close={() => {
setShutdownDialogOpen(false);
}}
/>
<RebootDialog
isOpen={rebootDialogOpen}
close={() => {
setRebootDialogOpen(false);
}}
/>
</>
);
};
-125
View File
@@ -1,125 +0,0 @@
import type React from "react";
import { useEffect, useState } from "react";
import { fromByteArray } from "base64-js";
import { toast } from "react-hot-toast";
import { QRCode } from "react-qrcode-logo";
import { Checkbox } from "@components/form/Checkbox.js";
import { IconButton } from "@components/form/IconButton.js";
import { Input } from "@components/form/Input.js";
import { Dialog } from "@headlessui/react";
import { ClipboardIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Protobuf } from "@meshtastic/meshtasticjs";
export interface ImportDialogProps {
isOpen: boolean;
close: () => void;
loraConfig?: Protobuf.Config_LoRaConfig;
channels: Protobuf.Channel[];
}
export const ImportDialog = ({
isOpen,
close,
loraConfig,
channels
}: ImportDialogProps): JSX.Element => {
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]);
const [QRCodeURL, setQRCodeURL] = useState<string>("");
useEffect(() => {
const channelsToEncode = channels
.filter((channel) => selectedChannels.includes(channel.index))
.map((channel) => channel.settings)
.filter((ch): ch is Protobuf.ChannelSettings => !!ch);
const encoded = Protobuf.ChannelSet.toBinary({
loraConfig,
settings: channelsToEncode
});
const base64 = fromByteArray(encoded)
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
setQRCodeURL(`https://meshtastic.org/e/#${base64}`);
}, [channels, selectedChannels, loraConfig]);
return (
<Dialog open={isOpen} onClose={close}>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel>
<div className="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
<div className="flex px-4 py-5 sm:px-6">
<div>
<h1 className="text-lg font-bold">Generate QR Code</h1>
<h5 className="text-sm text-slate-600">
The current LoRa configuration will also be shared.
</h5>
</div>
<IconButton
onClick={close}
className="my-auto ml-auto"
size="sm"
variant="secondary"
icon={<XMarkIcon className="h-4" />}
/>
</div>
<div className="flex gap-3 px-4 py-5 sm:p-6">
<div className="flex w-40 flex-col gap-1">
{channels.map((channel) => (
<Checkbox
key={channel.index}
disabled={
channel.index === 0 ||
channel.role === Protobuf.Channel_Role.DISABLED
}
label={
channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`
}
checked={selectedChannels.includes(channel.index)}
onChange={() => {
if (selectedChannels.includes(channel.index)) {
setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index)
);
} else {
setSelectedChannels([
...selectedChannels,
channel.index
]);
}
}}
/>
))}
</div>
<QRCode value={QRCodeURL} size={200} qrStyle="dots" />
</div>
<div className="px-4 py-4 sm:px-6">
<Input
label="Sharable URL"
value={QRCodeURL}
disabled
action={{
icon: <ClipboardIcon className="h-4" />,
action() {
void navigator.clipboard.writeText(QRCodeURL);
toast.success("Copied URL to Clipboard");
}
}}
/>
</div>
{/* </Card> */}
</div>
</Dialog.Panel>
</div>
</Dialog>
);
};
+52 -75
View File
@@ -6,10 +6,9 @@ import { toast } from "react-hot-toast";
import { QRCode } from "react-qrcode-logo";
import { Checkbox } from "@components/form/Checkbox.js";
import { IconButton } from "@components/form/IconButton.js";
import { Input } from "@components/form/Input.js";
import { Dialog } from "@headlessui/react";
import { ClipboardIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Dialog } from "@components/generic/Dialog.js";
import { ClipboardIcon } from "@heroicons/react/24/outline";
import { Protobuf } from "@meshtastic/meshtasticjs";
export interface QRDialogProps {
@@ -48,79 +47,57 @@ export const QRDialog = ({
}, [channels, selectedChannels, loraConfig]);
return (
<Dialog open={isOpen} onClose={close}>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel>
<div className="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
<div className="flex px-4 py-5 sm:px-6">
<div>
<h1 className="text-lg font-bold">Generate QR Code</h1>
<h5 className="text-sm text-slate-600">
The current LoRa configuration will also be shared.
</h5>
</div>
<IconButton
onClick={close}
className="my-auto ml-auto"
size="sm"
variant="secondary"
icon={<XMarkIcon className="h-4" />}
/>
</div>
<div className="flex gap-3 px-4 py-5 sm:p-6">
<div className="flex w-40 flex-col gap-1">
{channels.map((channel) => (
<Checkbox
key={channel.index}
disabled={
channel.index === 0 ||
channel.role === Protobuf.Channel_Role.DISABLED
}
label={
channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`
}
checked={selectedChannels.includes(channel.index)}
onChange={() => {
if (selectedChannels.includes(channel.index)) {
setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index)
);
} else {
setSelectedChannels([
...selectedChannels,
channel.index
]);
}
}}
/>
))}
</div>
<QRCode value={QRCodeURL} size={200} qrStyle="dots" />
</div>
<Dialog
title={"Generate QR Code"}
description={"The current LoRa configuration will also be shared."}
isOpen={isOpen}
close={close}
>
<div className="flex gap-3 px-4 py-5 sm:p-6">
<div className="flex w-40 flex-col gap-1">
{channels.map((channel) => (
<Checkbox
key={channel.index}
disabled={
channel.index === 0 ||
channel.role === Protobuf.Channel_Role.DISABLED
}
label={
channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`
}
checked={selectedChannels.includes(channel.index)}
onChange={() => {
if (selectedChannels.includes(channel.index)) {
setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index)
);
} else {
setSelectedChannels([...selectedChannels, channel.index]);
}
}}
/>
))}
</div>
<QRCode value={QRCodeURL} size={200} qrStyle="dots" />
</div>
<div className="px-4 py-4 sm:px-6">
<Input
label="Sharable URL"
value={QRCodeURL}
disabled
action={{
icon: <ClipboardIcon className="h-4" />,
action() {
void navigator.clipboard.writeText(QRCodeURL);
toast.success("Copied URL to Clipboard");
}
}}
/>
</div>
{/* </Card> */}
</div>
</Dialog.Panel>
<div className="sm:px-6">
<Input
label="Sharable URL"
value={QRCodeURL}
disabled
action={{
icon: <ClipboardIcon className="h-4" />,
action() {
void navigator.clipboard.writeText(QRCodeURL);
toast.success("Copied URL to Clipboard");
}
}}
/>
</div>
</Dialog>
);
+68
View File
@@ -0,0 +1,68 @@
import type React from "react";
import { useState } from "react";
import { useDevice } from "@app/core/providers/useDevice.js";
import { Dialog } from "@components/generic/Dialog.js";
import { ArrowPathIcon, ClockIcon } from "@heroicons/react/24/outline";
import { Button } from "../form/Button.js";
import { Input } from "../form/Input.js";
export interface RebootDialogProps {
isOpen: boolean;
close: () => void;
}
export const RebootDialog = ({
isOpen,
close
}: RebootDialogProps): JSX.Element => {
const { connection, setRebootDialogOpen } = useDevice();
const [time, setTime] = useState<number>(5);
return (
<Dialog
title={"Schedule Reboot"}
description={"Reboot the connected node after x minutes."}
isOpen={isOpen}
close={close}
>
<div className="flex gap-2 p-4">
<Input
type="number"
value={time}
onChange={(e) => setTime(parseInt(e.target.value))}
action={{
icon: <ClockIcon className="w-4" />,
action() {
connection?.reboot({
time: time * 60,
callback: async () => {
setRebootDialogOpen(false);
await Promise.resolve();
}
});
}
}}
/>
<Button
className="w-24"
variant="secondary"
iconAfter={<ArrowPathIcon className="w-4" />}
onClick={() => {
connection?.reboot({
time: 0,
callback: async () => {
setRebootDialogOpen(false);
await Promise.resolve();
}
});
}}
>
Now
</Button>
</div>
</Dialog>
);
};
+68
View File
@@ -0,0 +1,68 @@
import type React from "react";
import { useState } from "react";
import { useDevice } from "@app/core/providers/useDevice.js";
import { Dialog } from "@components/generic/Dialog.js";
import { ClockIcon, PowerIcon } from "@heroicons/react/24/outline";
import { Button } from "../form/Button.js";
import { Input } from "../form/Input.js";
export interface ShutdownDialogProps {
isOpen: boolean;
close: () => void;
}
export const ShutdownDialog = ({
isOpen,
close
}: ShutdownDialogProps): JSX.Element => {
const { connection, setShutdownDialogOpen } = useDevice();
const [time, setTime] = useState<number>(5);
return (
<Dialog
title={"Schedule Shutdown"}
description={"Turn off the connected node after x minutes."}
isOpen={isOpen}
close={close}
>
<div className="flex gap-2 p-4">
<Input
type="number"
value={time}
onChange={(e) => setTime(parseInt(e.target.value))}
action={{
icon: <ClockIcon className="w-4" />,
action() {
connection?.shutdown({
time: time * 60,
callback: async () => {
setShutdownDialogOpen(false);
await Promise.resolve();
}
});
}
}}
/>
<Button
className="w-24"
variant="secondary"
iconAfter={<PowerIcon className="w-4" />}
onClick={() => {
connection?.shutdown({
time: 0,
callback: async () => {
setShutdownDialogOpen(false);
await Promise.resolve();
}
});
}}
>
Now
</Button>
</div>
</Dialog>
);
};
@@ -10,8 +10,8 @@ export const MapControlls = (): JSX.Element => {
const { current: map } = useMap();
return (
<div className="absolute right-0 top-0 z-10 m-2">
<div className="divide-y overflow-hidden rounded-md bg-white">
<div className="absolute right-0 top-0 z-10 m-2 ">
<div className="divide-y divide-orange-300 overflow-hidden rounded-md bg-white shadow-md">
<div
className="cursor-pointer p-3 hover:bg-orange-200 hover:text-orange-700"
onClick={() => {
+18 -2
View File
@@ -8,12 +8,16 @@ import { PeersWidget } from "@components/Widgets/PeersWidget.js";
import { PositionWidget } from "@components/Widgets/PositionWidget.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { CommandLineIcon } from "@heroicons/react/24/outline";
import { Types } from "@meshtastic/meshtasticjs";
import { Input } from "./form/Input.js";
export const Sidebar = (): JSX.Element => {
const { removeDevice } = useDeviceStore();
const { connection, hardware, nodes, status, currentMetrics } = useDevice();
const { selectedDevice, setSelectedDevice } = useAppStore();
const { selectedDevice, setSelectedDevice, setCommandPaletteOpen } =
useAppStore();
const myNode = nodes.find((n) => n.data.num === hardware.myNodeNum);
return (
@@ -35,7 +39,7 @@ export const Sidebar = (): JSX.Element => {
}}
/>
<div className="flex flex-col gap-3">
<div className="flex flex-grow flex-col gap-3">
<BatteryWidget
batteryLevel={currentMetrics.batteryLevel}
voltage={currentMetrics.voltage}
@@ -51,6 +55,18 @@ export const Sidebar = (): JSX.Element => {
myNode?.data.position?.longitudeI
)}
/>
<div className="mt-auto">
<Input
placeholder={"Search for a command"}
onClick={() => setCommandPaletteOpen(true)}
action={{
icon: <CommandLineIcon className="w-4" />,
action() {
setCommandPaletteOpen(true);
}
}}
/>
</div>
</div>
</div>
);
+3 -2
View File
@@ -15,6 +15,7 @@ export const Button = ({
iconAfter,
children,
disabled,
className,
...rest
}: ButtonProps): JSX.Element => {
return (
@@ -22,7 +23,7 @@ export const Button = ({
className={`flex w-full rounded-md border border-transparent px-3 focus:outline-none focus:ring-2 focus:ring-orange-500 ${
variant === "primary"
? "bg-orange-600 text-white shadow-sm hover:bg-orange-700"
: "bg-orange-200 text-orange-700 hover:bg-orange-200"
: "bg-orange-200 text-orange-700 hover:bg-orange-300"
} ${
size === "sm"
? "h-8 text-sm"
@@ -33,7 +34,7 @@ export const Button = ({
disabled
? "cursor-not-allowed bg-gray-400 hover:bg-gray-400 focus:ring-gray-500"
: ""
}`}
} ${className}`}
disabled={disabled}
{...rest}
>
+1 -1
View File
@@ -21,7 +21,7 @@ export const IconButton = ({
className={`flex rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-orange-500 ${
variant === "primary"
? "bg-orange-600 text-white shadow-sm hover:bg-orange-700"
: "bg-orange-100 text-orange-700 hover:bg-orange-200"
: "bg-orange-200 text-orange-700 hover:bg-orange-300"
} ${
size === "sm" ? "h-8 w-8" : size === "md" ? "h-10 w-10" : "h-12 w-12"
} ${disabled ? "cursor-not-allowed bg-red-400 focus:ring-red-500" : ""} ${
+1 -1
View File
@@ -56,7 +56,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
<button
type="button"
onClick={action.action}
className="relative -ml-px inline-flex items-center space-x-2 rounded-r-md bg-orange-200 px-4 py-2 text-sm font-medium hover:bg-orange-300 focus:outline-none focus:ring-2 focus:ring-orange-500"
className="relative -ml-px inline-flex items-center space-x-2 rounded-r-md bg-orange-200 px-4 py-2 text-sm font-medium text-orange-700 hover:bg-orange-300 focus:outline-none focus:ring-2 focus:ring-orange-500"
>
{action.icon}
</button>
+48
View File
@@ -0,0 +1,48 @@
import type React from "react";
import { IconButton } from "@components/form/IconButton.js";
import { Dialog as DialogUI } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline";
export interface DialogProps {
title: string;
description: string;
isOpen: boolean;
close: () => void;
children: React.ReactNode;
}
export const Dialog = ({
title,
description,
isOpen,
close,
children
}: DialogProps): JSX.Element => {
return (
<DialogUI open={isOpen} onClose={close}>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<DialogUI.Panel>
<div className="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
<div className="flex px-4 py-5 sm:px-6">
<div>
<h1 className="text-lg font-bold">{title}</h1>
<h5 className="text-sm text-slate-600">{description}</h5>
</div>
<IconButton
onClick={close}
className="my-auto ml-auto"
size="sm"
variant="secondary"
icon={<XMarkIcon className="h-4" />}
/>
</div>
<div className="p-4">{children}</div>
</div>
</DialogUI.Panel>
</div>
</DialogUI>
);
};
+11 -2
View File
@@ -15,6 +15,7 @@ interface AppState {
num: number;
}[];
rasterSources: RasterSource[];
commandPaletteOpen: boolean;
setRasterSources: (sources: RasterSource[]) => void;
addRasterSource: (source: RasterSource) => void;
@@ -23,6 +24,7 @@ interface AppState {
setSelectedDevice: (deviceId: number) => void;
addDevice: (device: { id: number; num: number }) => void;
removeDevice: (deviceId: number) => void;
setCommandPaletteOpen: (open: boolean) => void;
}
export const useAppStore = create<AppState>()((set) => ({
@@ -30,6 +32,7 @@ export const useAppStore = create<AppState>()((set) => ({
devices: [],
currentPage: "messages",
rasterSources: [],
commandPaletteOpen: false,
setRasterSources: (sources: RasterSource[]) => {
set(
@@ -52,7 +55,6 @@ export const useAppStore = create<AppState>()((set) => ({
})
);
},
setSelectedDevice: (deviceId) =>
set(() => ({
selectedDevice: deviceId
@@ -64,5 +66,12 @@ export const useAppStore = create<AppState>()((set) => ({
removeDevice: (deviceId) =>
set((state) => ({
devices: state.devices.filter((device) => device.id !== deviceId)
}))
})),
setCommandPaletteOpen: (open: boolean) => {
set(
produce<AppState>((draft) => {
draft.commandPaletteOpen = open;
})
);
}
}));
+26
View File
@@ -56,6 +56,8 @@ export interface Device {
regionUnset: boolean;
currentMetrics: Protobuf.DeviceMetrics;
QRDialogOpen: boolean;
shutdownDialogOpen: boolean;
rebootDialogOpen: boolean;
pendingSettingsChanges: boolean;
setReady(ready: boolean): void;
@@ -79,6 +81,8 @@ export interface Device {
addDeviceMetadataMessage: (metadata: Types.DeviceMetadataPacket) => void;
ackMessage: (channelIndex: number, messageId: number) => void;
setQRDialogOpen: (open: boolean) => void;
setShutdownDialogOpen: (open: boolean) => void;
setRebootDialogOpen: (open: boolean) => void;
}
export interface DeviceState {
@@ -115,6 +119,8 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
regionUnset: false,
currentMetrics: Protobuf.DeviceMetrics.create(),
QRDialogOpen: false,
shutdownDialogOpen: false,
rebootDialogOpen: false,
pendingSettingsChanges: false,
setReady: (ready: boolean) => {
@@ -539,6 +545,26 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
}
})
);
},
setShutdownDialogOpen: (open: boolean) => {
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
device.shutdownDialogOpen = open;
}
})
);
},
setRebootDialogOpen: (open: boolean) => {
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
device.rebootDialogOpen = open;
}
})
);
}
});
})
-5
View File
@@ -45,24 +45,19 @@ export const subscribeAll = (
});
connection.onMyNodeInfo.subscribe((nodeInfo) => {
console.log("^^^^^^^ GOT MY NODE INFO");
device.setHardware(nodeInfo);
myNodeNum = nodeInfo.myNodeNum;
});
connection.onUserPacket.subscribe((user) => {
console.log("^^^^^^^ GOT USER");
device.addUser(user);
});
connection.onPositionPacket.subscribe((position) => {
console.log("^^^^^^^ GOT POSITION");
device.addPosition(position);
});
connection.onNodeInfoPacket.subscribe((nodeInfo) => {
console.log("^^^^^^^ GOT NODE INFO");
toast(`New Node Discovered: ${nodeInfo.data.user?.shortName ?? "UNK"}`, {
icon: "🔎"
});
+1
View File
@@ -21,6 +21,7 @@ export class NetworkValidation implements Protobuf.Config_NetworkConfig {
ethEnabled: boolean;
@IsEnum(Protobuf.Config_NetworkConfig_EthMode)
@IsOptional()
ethMode: Protobuf.Config_NetworkConfig_EthMode;
ipv4Config: NetworkValidation_IpV4Config;