mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 08:49:36 +00:00
[fix] Toggle bypass/mute of subgraph nodes applies mode to all children recursively (#4636)
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import { Positionable, Reroute } from '@comfyorg/litegraph'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
LGraphNode,
|
||||
Positionable,
|
||||
Reroute
|
||||
} from '@comfyorg/litegraph'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import {
|
||||
collectFromNodes,
|
||||
traverseNodesDepthFirst
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
|
||||
/**
|
||||
* Composable for handling selected LiteGraph items filtering and operations.
|
||||
@@ -61,11 +71,92 @@ export function useSelectedLiteGraphItems() {
|
||||
return getSelectableItems().size > 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only the selected nodes (LGraphNode instances) from the canvas.
|
||||
* This filters out other types of selected items like groups or reroutes.
|
||||
* If a selected node is a subgraph, this also includes all nodes within it.
|
||||
* @returns Array of selected LGraphNode instances and their descendants.
|
||||
*/
|
||||
const getSelectedNodes = (): LGraphNode[] => {
|
||||
const selectedNodes = app.canvas.selected_nodes
|
||||
if (!selectedNodes) return []
|
||||
|
||||
// Convert selected_nodes object to array, preserving order
|
||||
const nodeArray: LGraphNode[] = []
|
||||
for (const i in selectedNodes) {
|
||||
nodeArray.push(selectedNodes[i])
|
||||
}
|
||||
|
||||
// Check if any selected nodes are subgraphs
|
||||
const hasSubgraphs = nodeArray.some(
|
||||
(node) => node.isSubgraphNode?.() && node.subgraph
|
||||
)
|
||||
|
||||
// If no subgraphs, just return the array directly to preserve order
|
||||
if (!hasSubgraphs) {
|
||||
return nodeArray
|
||||
}
|
||||
|
||||
// Use collectFromNodes to get all nodes including those in subgraphs
|
||||
return collectFromNodes(nodeArray)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the execution mode of all selected nodes with unified subgraph behavior.
|
||||
*
|
||||
* Top-level behavior (selected nodes): Standard toggle logic
|
||||
* - If the selected node is already in the specified mode → set to ALWAYS
|
||||
* - Otherwise → set to the specified mode
|
||||
*
|
||||
* Subgraph behavior (children of selected subgraph nodes): Unified state application
|
||||
* - All children inherit the same mode that their parent subgraph node was set to
|
||||
* - This creates predictable behavior: if you toggle a subgraph to "mute",
|
||||
* ALL nodes inside become muted, regardless of their previous individual states
|
||||
*
|
||||
* @param mode - The LGraphEventMode to toggle to (e.g., NEVER for mute, BYPASS for bypass)
|
||||
*/
|
||||
const toggleSelectedNodesMode = (mode: LGraphEventMode): void => {
|
||||
const selectedNodes = app.canvas.selected_nodes
|
||||
if (!selectedNodes) return
|
||||
|
||||
// Convert selected_nodes object to array
|
||||
const selectedNodeArray: LGraphNode[] = []
|
||||
for (const i in selectedNodes) {
|
||||
selectedNodeArray.push(selectedNodes[i])
|
||||
}
|
||||
|
||||
// Process each selected node independently to determine its target state and apply to children
|
||||
selectedNodeArray.forEach((selectedNode) => {
|
||||
// Apply standard toggle logic to the selected node itself
|
||||
const newModeForSelectedNode =
|
||||
selectedNode.mode === mode ? LGraphEventMode.ALWAYS : mode
|
||||
|
||||
selectedNode.mode = newModeForSelectedNode
|
||||
|
||||
// If this selected node is a subgraph, apply the same mode uniformly to all its children
|
||||
// This ensures predictable behavior: all children get the same state as their parent
|
||||
if (selectedNode.isSubgraphNode?.() && selectedNode.subgraph) {
|
||||
traverseNodesDepthFirst([selectedNode], {
|
||||
visitor: (node) => {
|
||||
// Skip the parent node since we already handled it above
|
||||
if (node === selectedNode) return undefined
|
||||
|
||||
// Apply the parent's new mode to all children uniformly
|
||||
node.mode = newModeForSelectedNode
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isIgnoredItem,
|
||||
filterSelectableItems,
|
||||
getSelectableItems,
|
||||
hasSelectableItems,
|
||||
hasMultipleSelectableItems
|
||||
hasMultipleSelectableItems,
|
||||
getSelectedNodes,
|
||||
toggleSelectedNodesMode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { Point } from '@comfyorg/litegraph'
|
||||
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import {
|
||||
DEFAULT_DARK_COLOR_PALETTE,
|
||||
DEFAULT_LIGHT_COLOR_PALETTE
|
||||
@@ -46,30 +47,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
const toastStore = useToastStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const executionStore = useExecutionStore()
|
||||
const { getSelectedNodes, toggleSelectedNodesMode } =
|
||||
useSelectedLiteGraphItems()
|
||||
const getTracker = () => workflowStore.activeWorkflow?.changeTracker
|
||||
|
||||
const getSelectedNodes = (): LGraphNode[] => {
|
||||
const selectedNodes = app.canvas.selected_nodes
|
||||
const result: LGraphNode[] = []
|
||||
if (selectedNodes) {
|
||||
for (const i in selectedNodes) {
|
||||
const node = selectedNodes[i]
|
||||
result.push(node)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const toggleSelectedNodesMode = (mode: LGraphEventMode) => {
|
||||
getSelectedNodes().forEach((node) => {
|
||||
if (node.mode === mode) {
|
||||
node.mode = LGraphEventMode.ALWAYS
|
||||
} else {
|
||||
node.mode = mode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const moveSelectedNodes = (
|
||||
positionUpdater: (pos: Point, gridSize: number) => Point
|
||||
) => {
|
||||
|
||||
Reference in New Issue
Block a user