Fixed State Persistence Issues in Edge Toggle Node
This commit is contained in:
parent
47e80bea59
commit
b3edb92275
@ -1,41 +1,73 @@
|
|||||||
|
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Edge_Toggle.jsx
|
||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Borealis - Edge Toggle Node
|
Borealis - Edge Toggle Node
|
||||||
* ===========================
|
===========================
|
||||||
* Allows users to toggle data flow between upstream and downstream nodes.
|
Allows users to toggle data flow between upstream and downstream nodes.
|
||||||
* - When enabled: propagates upstream value.
|
- When enabled: propagates upstream value.
|
||||||
* - When disabled: outputs "0" (or null/blank) so downstream sees a cleared value.
|
- When disabled: outputs "0" (or null/blank) so downstream sees a cleared value.
|
||||||
*/
|
|
||||||
|
Fully captures and restores toggle state ("enabled"/"disabled") from imported workflow JSON,
|
||||||
|
so state is always restored as last persisted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Init shared value bus if needed
|
||||||
|
if (!window.BorealisValueBus) window.BorealisValueBus = {};
|
||||||
|
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
|
||||||
|
|
||||||
const EdgeToggleNode = ({ id, data }) => {
|
const EdgeToggleNode = ({ id, data }) => {
|
||||||
const { setNodes } = useReactFlow();
|
const { setNodes } = useReactFlow();
|
||||||
const edges = useStore((state) => state.edges);
|
const edges = useStore((state) => state.edges);
|
||||||
|
|
||||||
// Local state for switch
|
// === CAPTURE persisted toggle state on load/rehydrate ===
|
||||||
const [enabled, setEnabled] = useState(data?.enabled ?? true);
|
// Restore "enabled" from node data if present, otherwise true
|
||||||
// What was last passed to output
|
const [enabled, setEnabled] = useState(
|
||||||
const [outputValue, setOutputValue] = useState(undefined);
|
typeof data?.enabled === "boolean"
|
||||||
|
? data.enabled
|
||||||
|
: data?.enabled === "false"
|
||||||
|
? false
|
||||||
|
: data?.enabled === "true"
|
||||||
|
? true
|
||||||
|
: data?.enabled !== undefined
|
||||||
|
? !!data.enabled
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
// Store last output value
|
||||||
|
const [outputValue, setOutputValue] = useState(
|
||||||
|
typeof data?.value !== "undefined" ? data.value : undefined
|
||||||
|
);
|
||||||
const outputRef = useRef(outputValue);
|
const outputRef = useRef(outputValue);
|
||||||
|
|
||||||
// Store enabled state persistently
|
// === Persist toggle state back to node data when toggled ===
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodes(nds =>
|
setNodes((nds) =>
|
||||||
nds.map(n =>
|
nds.map((n) =>
|
||||||
n.id === id ? { ...n, data: { ...n.data, enabled } } : n
|
n.id === id ? { ...n, data: { ...n.data, enabled } } : n
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [enabled, id, setNodes]);
|
}, [enabled, id, setNodes]);
|
||||||
|
|
||||||
// Main logic: propagate upstream if enabled, else always set downstream to "0"
|
// === On mount: restore BorealisValueBus from loaded node data if present ===
|
||||||
|
useEffect(() => {
|
||||||
|
// Only run on first mount
|
||||||
|
if (typeof data?.value !== "undefined") {
|
||||||
|
window.BorealisValueBus[id] = data.value;
|
||||||
|
setOutputValue(data.value);
|
||||||
|
outputRef.current = data.value;
|
||||||
|
}
|
||||||
|
}, [id, data?.value]);
|
||||||
|
|
||||||
|
// === Main interval logic: live propagate upstream/clear if off ===
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval = null;
|
let interval = null;
|
||||||
let currentRate = window.BorealisUpdateRate || 100;
|
let currentRate = window.BorealisUpdateRate || 100;
|
||||||
|
|
||||||
const runNodeLogic = () => {
|
const runNodeLogic = () => {
|
||||||
const inputEdge = edges.find(e => e.target === id);
|
const inputEdge = edges.find((e) => e.target === id);
|
||||||
const hasInput = Boolean(inputEdge && inputEdge.source);
|
const hasInput = Boolean(inputEdge && inputEdge.source);
|
||||||
|
|
||||||
if (enabled && hasInput) {
|
if (enabled && hasInput) {
|
||||||
@ -44,8 +76,8 @@ const EdgeToggleNode = ({ id, data }) => {
|
|||||||
outputRef.current = upstreamValue;
|
outputRef.current = upstreamValue;
|
||||||
setOutputValue(upstreamValue);
|
setOutputValue(upstreamValue);
|
||||||
window.BorealisValueBus[id] = upstreamValue;
|
window.BorealisValueBus[id] = upstreamValue;
|
||||||
setNodes(nds =>
|
setNodes((nds) =>
|
||||||
nds.map(n =>
|
nds.map((n) =>
|
||||||
n.id === id
|
n.id === id
|
||||||
? { ...n, data: { ...n.data, value: upstreamValue } }
|
? { ...n, data: { ...n.data, value: upstreamValue } }
|
||||||
: n
|
: n
|
||||||
@ -58,11 +90,9 @@ const EdgeToggleNode = ({ id, data }) => {
|
|||||||
outputRef.current = 0;
|
outputRef.current = 0;
|
||||||
setOutputValue(0);
|
setOutputValue(0);
|
||||||
window.BorealisValueBus[id] = 0;
|
window.BorealisValueBus[id] = 0;
|
||||||
setNodes(nds =>
|
setNodes((nds) =>
|
||||||
nds.map(n =>
|
nds.map((n) =>
|
||||||
n.id === id
|
n.id === id ? { ...n, data: { ...n.data, value: 0 } } : n
|
||||||
? { ...n, data: { ...n.data, value: 0 } }
|
|
||||||
: n
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -87,13 +117,17 @@ const EdgeToggleNode = ({ id, data }) => {
|
|||||||
}, [id, edges, enabled, setNodes]);
|
}, [id, edges, enabled, setNodes]);
|
||||||
|
|
||||||
// Edge input detection
|
// Edge input detection
|
||||||
const inputEdge = edges.find(e => e.target === id);
|
const inputEdge = edges.find((e) => e.target === id);
|
||||||
const hasInput = Boolean(inputEdge && inputEdge.source);
|
const hasInput = Boolean(inputEdge && inputEdge.source);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="borealis-node">
|
<div className="borealis-node">
|
||||||
{/* Input handle */}
|
{/* Input handle */}
|
||||||
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
className="borealis-handle"
|
||||||
|
/>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="borealis-node-header">
|
<div className="borealis-node-header">
|
||||||
{data?.label || "Edge Toggle"}
|
{data?.label || "Edge Toggle"}
|
||||||
@ -101,34 +135,43 @@ const EdgeToggleNode = ({ id, data }) => {
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="borealis-node-content">
|
<div className="borealis-node-content">
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
<Tooltip title={enabled ? "Turn Off / Send Zero" : "Turn On / Allow Data"} arrow>
|
<Tooltip
|
||||||
|
title={enabled ? "Turn Off / Send Zero" : "Turn On / Allow Data"}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
size="small"
|
size="small"
|
||||||
onChange={() => setEnabled(e => !e)}
|
onChange={() => setEnabled((e) => !e)}
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiSwitch-switchBase.Mui-checked": {
|
"& .MuiSwitch-switchBase.Mui-checked": {
|
||||||
color: "#58a6ff",
|
color: "#58a6ff",
|
||||||
},
|
},
|
||||||
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": {
|
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": {
|
||||||
backgroundColor: "#58a6ff",
|
backgroundColor: "#58a6ff",
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span style={{
|
<span
|
||||||
fontSize: 9,
|
style={{
|
||||||
color: enabled ? "#00d18c" : "#ff4f4f",
|
fontSize: 9,
|
||||||
fontWeight: "bold",
|
color: enabled ? "#00d18c" : "#ff4f4f",
|
||||||
marginLeft: 4,
|
fontWeight: "bold",
|
||||||
userSelect: "none"
|
marginLeft: 4,
|
||||||
}}>
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{enabled ? "Flow Enabled" : "Flow Disabled"}
|
{enabled ? "Flow Enabled" : "Flow Disabled"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Output handle */}
|
{/* Output handle */}
|
||||||
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
className="borealis-handle"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -144,5 +187,32 @@ Toggles edge data flow ON/OFF using a switch.
|
|||||||
- Use to quickly enable/disable parts of your workflow without unlinking edges.
|
- Use to quickly enable/disable parts of your workflow without unlinking edges.
|
||||||
`.trim(),
|
`.trim(),
|
||||||
content: "Toggle ON/OFF to allow or send zero downstream",
|
content: "Toggle ON/OFF to allow or send zero downstream",
|
||||||
component: EdgeToggleNode
|
component: EdgeToggleNode,
|
||||||
};
|
config: [
|
||||||
|
{
|
||||||
|
key: "enabled",
|
||||||
|
label: "Toggle Enabled",
|
||||||
|
type: "select",
|
||||||
|
options: ["true", "false"],
|
||||||
|
defaultValue: "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
usage_documentation: `
|
||||||
|
### Edge Toggle Node
|
||||||
|
|
||||||
|
**Purpose:**
|
||||||
|
Allows you to control data flow along a workflow edge without disconnecting the wire.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- When **Enabled**: passes upstream value downstream as usual.
|
||||||
|
- When **Disabled**: pushes \`0\` (zero) so that downstream logic always sees a cleared value (acts as an instant "mute" switch).
|
||||||
|
|
||||||
|
**Persistence:**
|
||||||
|
- Toggle state is saved in the workflow and restored on load/import.
|
||||||
|
|
||||||
|
**Tips:**
|
||||||
|
- Use for debug toggling, feature gating, or for rapid workflow prototyping.
|
||||||
|
|
||||||
|
---
|
||||||
|
`.trim()
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user