Browse Source
- Created dimmingUtils.ts with computeDimmedNodes function - DFS traversal finds all downstream nodes of disabled Switch outputs - Smart cascade: nodes with active inputs stay active - Added dimmedNodeIds state to workflowStore - Added recomputeDimmedNodes action with set equality check - Recomputes on: switch toggle change, edge add/remove, workflow load - Resets dimmedNodeIds on clearWorkflow - Added execution skip logic in executeSingleNode - Dimmed nodes skip execution but preserve previous outputhandoff-20260429-1057
2 changed files with 134 additions and 0 deletions
@ -0,0 +1,83 @@ |
|||
import type { WorkflowNode, WorkflowEdge, SwitchNodeData } from "@/types"; |
|||
|
|||
/** |
|||
* Compute set of node IDs that should be visually dimmed. |
|||
* A node is dimmed if ALL its input paths trace back to disabled Switch outputs. |
|||
* Smart cascade: if a node has at least one active input from a non-disabled source, it stays active. |
|||
*/ |
|||
export function computeDimmedNodes( |
|||
nodes: WorkflowNode[], |
|||
edges: WorkflowEdge[] |
|||
): Set<string> { |
|||
// Step 1: Find all nodes that are downstream of disabled Switch outputs
|
|||
const potentiallyDimmed = new Set<string>(); |
|||
|
|||
nodes.forEach(node => { |
|||
if (node.type !== "switch") return; |
|||
const switchData = node.data as SwitchNodeData; |
|||
if (!switchData.switches) return; |
|||
|
|||
switchData.switches.forEach(sw => { |
|||
if (sw.enabled) return; // Only process disabled switches
|
|||
|
|||
// Find edges from this disabled output handle
|
|||
const disabledEdges = edges.filter( |
|||
e => e.source === node.id && e.sourceHandle === sw.id |
|||
); |
|||
|
|||
// DFS traverse downstream from each disabled edge target
|
|||
disabledEdges.forEach(edge => { |
|||
traverseDownstream(edge.target, edges, potentiallyDimmed); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
// Step 2: Smart cascade — remove nodes that have at least one active input
|
|||
// A node stays active if any incoming edge comes from a source NOT in potentiallyDimmed
|
|||
// AND that source is not a Switch with a disabled output pointing to this node
|
|||
const finalDimmed = new Set<string>(); |
|||
|
|||
potentiallyDimmed.forEach(nodeId => { |
|||
const incomingEdges = edges.filter(e => e.target === nodeId); |
|||
|
|||
// Check if any incoming edge provides an active path
|
|||
const hasActiveInput = incomingEdges.some(edge => { |
|||
// Source is not potentially dimmed — it's an active source
|
|||
if (!potentiallyDimmed.has(edge.source)) { |
|||
// But also check: is this edge coming from a disabled Switch output?
|
|||
const sourceNode = nodes.find(n => n.id === edge.source); |
|||
if (sourceNode?.type === "switch") { |
|||
const switchData = sourceNode.data as SwitchNodeData; |
|||
const switchEntry = switchData.switches?.find(s => s.id === edge.sourceHandle); |
|||
// If this specific switch output is disabled, it's not an active path
|
|||
if (switchEntry && !switchEntry.enabled) return false; |
|||
} |
|||
return true; // Active source, not dimmed
|
|||
} |
|||
return false; // Source is also dimmed
|
|||
}); |
|||
|
|||
if (!hasActiveInput) { |
|||
finalDimmed.add(nodeId); |
|||
} |
|||
}); |
|||
|
|||
return finalDimmed; |
|||
} |
|||
|
|||
/** |
|||
* DFS traversal to find all downstream nodes from a starting node. |
|||
* Uses visited set for cycle detection. |
|||
*/ |
|||
function traverseDownstream( |
|||
nodeId: string, |
|||
edges: WorkflowEdge[], |
|||
visited: Set<string> |
|||
): void { |
|||
if (visited.has(nodeId)) return; // Cycle detection
|
|||
visited.add(nodeId); |
|||
|
|||
edges |
|||
.filter(e => e.source === nodeId) |
|||
.forEach(edge => traverseDownstream(edge.target, edges, visited)); |
|||
} |
|||
Loading…
Reference in new issue