Fixed State Persistence Issues in Edge Toggle Node

This commit is contained in:
Nicole Rappe 2025-06-01 15:31:06 -06:00
parent 47e80bea59
commit b3edb92275

View File

@ -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()
};