From 7cbd41ab7294bc6b5b19bc37e31925d6c0aca21d Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Sun, 9 Jun 2024 20:42:09 -0400 Subject: [PATCH] Add ability to remove nodes from device --- src/components/Dialog/DialogManager.tsx | 7 +++ src/components/Dialog/RemoveNodeDialog.tsx | 52 ++++++++++++++++++++++ src/core/stores/appStore.ts | 7 +++ src/core/stores/deviceStore.ts | 17 ++++++- src/pages/Nodes.tsx | 16 ++++++- 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/components/Dialog/RemoveNodeDialog.tsx diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 4fa84dc3..877eec4e 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -3,6 +3,7 @@ import { ImportDialog } from "@components/Dialog/ImportDialog.js"; import { QRDialog } from "@components/Dialog/QRDialog.js"; import { RebootDialog } from "@components/Dialog/RebootDialog.js"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.js"; +import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.js" import { useDevice } from "@core/stores/deviceStore.js"; export const DialogManager = (): JSX.Element => { @@ -42,6 +43,12 @@ export const DialogManager = (): JSX.Element => { setDialogOpen("deviceName", open); }} /> + { + setDialogOpen("nodeRemoval", open); + }} + /> ); }; diff --git a/src/components/Dialog/RemoveNodeDialog.tsx b/src/components/Dialog/RemoveNodeDialog.tsx new file mode 100644 index 00000000..789388db --- /dev/null +++ b/src/components/Dialog/RemoveNodeDialog.tsx @@ -0,0 +1,52 @@ +import { useAppStore } from "@app/core/stores/appStore"; +import { useDevice } from "@app/core/stores/deviceStore.js"; +import { Button } from "@components/UI/Button.js"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.js"; +import { Label } from "@components/UI/Label.js"; + +export interface RemoveNodeDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const RemoveNodeDialog = ({ + open, + onOpenChange, +}: RemoveNodeDialogProps): JSX.Element => { + const { connection, nodes, removeNode } = useDevice(); + const { nodeNumToBeRemoved } = useAppStore(); + + const onSubmit = () => { + connection?.removeNodeByNum(nodeNumToBeRemoved); + removeNode(nodeNumToBeRemoved); + onOpenChange(false); + }; + + return ( + + + + Remove Node? + + Are you sure you want to remove this Node? + + +
+
+ +
+
+ + + +
+
+ ); +}; diff --git a/src/core/stores/appStore.ts b/src/core/stores/appStore.ts index fa46cb57..2098c17b 100644 --- a/src/core/stores/appStore.ts +++ b/src/core/stores/appStore.ts @@ -26,6 +26,7 @@ interface AppState { rasterSources: RasterSource[]; commandPaletteOpen: boolean; darkMode: boolean; + nodeNumToBeRemoved: number; accent: AccentColor; connectDialogOpen: boolean; @@ -38,6 +39,7 @@ interface AppState { removeDevice: (deviceId: number) => void; setCommandPaletteOpen: (open: boolean) => void; setDarkMode: (enabled: boolean) => void; + setNodeNumToBeRemoved: (nodeNum: number) => void; setAccent: (color: AccentColor) => void; setConnectDialogOpen: (open: boolean) => void; } @@ -51,6 +53,7 @@ export const useAppStore = create()((set) => ({ darkMode: window.matchMedia("(prefers-color-scheme: dark)").matches, accent: "orange", connectDialogOpen: false, + nodeNumToBeRemoved: 0, setRasterSources: (sources: RasterSource[]) => { set( @@ -99,6 +102,10 @@ export const useAppStore = create()((set) => ({ }), ); }, + setNodeNumToBeRemoved: (nodeNum) => + set((state) => ({ + nodeNumToBeRemoved: nodeNum + })), setAccent(color) { set( produce((draft) => { diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index fee9fb8f..bfd927fc 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -24,7 +24,8 @@ export type DialogVariant = | "QR" | "shutdown" | "reboot" - | "deviceName"; + | "deviceName" + | "nodeRemoval"; export interface Device { id: number; @@ -54,6 +55,7 @@ export interface Device { shutdown: boolean; reboot: boolean; deviceName: boolean; + nodeRemoval: boolean; }; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -74,6 +76,7 @@ export interface Device { addConnection: (connection: Types.ConnectionType) => void; addMessage: (message: MessageWithState) => void; addMetadata: (from: number, metadata: Protobuf.Mesh.DeviceMetadata) => void; + removeNode: (nodeNum: number) => void; setMessageState: ( type: "direct" | "broadcast", channelIndex: Types.ChannelNumber, @@ -130,6 +133,7 @@ export const useDeviceStore = create((set, get) => ({ shutdown: false, reboot: false, deviceName: false, + nodeRemoval: false, }, pendingSettingsChanges: false, messageDraft: "", @@ -494,6 +498,17 @@ export const useDeviceStore = create((set, get) => ({ }), ); }, + removeNode: (nodeNum) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + device.nodes.delete(nodeNum); + }) + ) + }, setMessageState: ( type: "direct" | "broadcast", channelIndex: Types.ChannelNumber, diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx index 8b1c03a6..c272dc6d 100644 --- a/src/pages/Nodes.tsx +++ b/src/pages/Nodes.tsx @@ -6,9 +6,18 @@ import { useDevice } from "@core/stores/deviceStore.js"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { Protobuf } from "@meshtastic/js"; import { base16 } from "rfc4648"; +import { Button } from "@components/UI/Button.js"; +import { TrashIcon } from "lucide-react"; +import { useAppStore } from "@app/core/stores/appStore"; + +export interface DeleteNoteDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} export const NodesPage = (): JSX.Element => { - const { nodes, hardware } = useDevice(); + const { nodes, hardware, setDialogOpen } = useDevice(); + const { setNodeNumToBeRemoved } = useAppStore(); const filteredNodes = Array.from(nodes.values()).filter( (n) => n.num !== hardware.myNodeNum, @@ -26,6 +35,7 @@ export const NodesPage = (): JSX.Element => { { title: "MAC Address", type: "normal", sortable: true }, { title: "Last Heard", type: "normal", sortable: true }, { title: "SNR", type: "normal", sortable: true }, + { title: "Remove", type: "normal", sortable: false }, ]} rows={filteredNodes.map((node) => [ , @@ -55,6 +65,10 @@ export const NodesPage = (): JSX.Element => { {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/ {(node.snr + 10) * 5}raw , + ])} />