mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 08:14:06 +00:00
refactor: implement centralized node mode management and update related components
This commit is contained in:
@@ -2,23 +2,18 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useNodeMode } from '@/composables/canvas/useNodeMode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
||||
import FormSelectButton from '@/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.vue'
|
||||
|
||||
import LayoutField from './LayoutField.vue'
|
||||
|
||||
/**
|
||||
* Good design limits dependencies and simplifies the interface of the abstraction layer.
|
||||
* Here, we only care about the mode method,
|
||||
* and do not concern ourselves with other methods.
|
||||
*/
|
||||
type PickedNode = Pick<LGraphNode, 'mode'>
|
||||
|
||||
const { nodes } = defineProps<{ nodes: PickedNode[] }>()
|
||||
const { nodes } = defineProps<{ nodes: LGraphNode[] }>()
|
||||
const emit = defineEmits<{ (e: 'changed'): void }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { setNodesMode } = useNodeMode()
|
||||
|
||||
const nodeState = computed({
|
||||
get() {
|
||||
@@ -39,9 +34,7 @@ const nodeState = computed({
|
||||
return mode
|
||||
},
|
||||
set(value: LGraphNode['mode']) {
|
||||
nodes.forEach((node) => {
|
||||
node.mode = value
|
||||
})
|
||||
setNodesMode(nodes, value)
|
||||
emit('changed')
|
||||
}
|
||||
})
|
||||
|
||||
39
src/composables/canvas/useNodeMode.ts
Normal file
39
src/composables/canvas/useNodeMode.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Composable for managing node execution modes.
|
||||
* Provides a centralized way to change node modes while ensuring
|
||||
* proper graph state updates and side effects.
|
||||
*/
|
||||
export function useNodeMode() {
|
||||
/**
|
||||
* Sets the execution mode for a single node.
|
||||
* Uses the node's changeMode method to handle mode-specific side effects
|
||||
* and notifies the graph of the change.
|
||||
*/
|
||||
function setNodeMode(node: LGraphNode, mode: LGraphEventMode): void {
|
||||
node.changeMode(mode)
|
||||
node.graph?.change()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the execution mode for multiple nodes.
|
||||
* Applies the mode to all nodes and notifies the graph once.
|
||||
*/
|
||||
function setNodesMode(nodes: LGraphNode[], mode: LGraphEventMode): void {
|
||||
if (nodes.length === 0) return
|
||||
|
||||
nodes.forEach((node) => {
|
||||
node.changeMode(mode)
|
||||
})
|
||||
|
||||
// Only call change once for all nodes
|
||||
nodes[0].graph?.change()
|
||||
}
|
||||
|
||||
return {
|
||||
setNodeMode,
|
||||
setNodesMode
|
||||
}
|
||||
}
|
||||
@@ -224,8 +224,23 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should toggle node modes correctly', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const node1 = { id: 1, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const node2 = { id: 2, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const mockGraph = { change: vi.fn() }
|
||||
const node1 = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
node1.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const node2 = {
|
||||
id: 2,
|
||||
mode: LGraphEventMode.NEVER,
|
||||
changeMode: vi.fn((mode) => {
|
||||
node2.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
|
||||
app.canvas.selected_nodes = { '0': node1, '1': node2 }
|
||||
|
||||
@@ -236,11 +251,22 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
// node2 should stay NEVER (since a selected node exists which is not NEVER)
|
||||
expect(node1.mode).toBe(LGraphEventMode.NEVER)
|
||||
expect(node2.mode).toBe(LGraphEventMode.NEVER)
|
||||
expect(node1.changeMode).toHaveBeenCalledWith(LGraphEventMode.NEVER)
|
||||
expect(node2.changeMode).toHaveBeenCalledWith(LGraphEventMode.NEVER)
|
||||
expect(mockGraph.change).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('toggleSelectedNodesMode should set mode to ALWAYS when already in target mode', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const node = { id: 1, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
const mockGraph = { change: vi.fn() }
|
||||
const node = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.BYPASS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
node.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
|
||||
app.canvas.selected_nodes = { '0': node }
|
||||
|
||||
@@ -249,6 +275,8 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
// Should change to ALWAYS
|
||||
expect(node.mode).toBe(LGraphEventMode.ALWAYS)
|
||||
expect(node.changeMode).toHaveBeenCalledWith(LGraphEventMode.ALWAYS)
|
||||
expect(mockGraph.change).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('getSelectedNodes should include nodes from subgraphs', () => {
|
||||
@@ -277,17 +305,43 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should apply unified state to subgraph children', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const subNode2 = { id: 12, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const mockGraph = { change: vi.fn() }
|
||||
const subNode1 = {
|
||||
id: 11,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
subNode1.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const subNode2 = {
|
||||
id: 12,
|
||||
mode: LGraphEventMode.NEVER,
|
||||
changeMode: vi.fn((mode) => {
|
||||
subNode2.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const subgraphNode = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
isSubgraphNode: () => true,
|
||||
subgraph: {
|
||||
nodes: [subNode1, subNode2]
|
||||
}
|
||||
},
|
||||
changeMode: vi.fn((mode) => {
|
||||
subgraphNode.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const regularNode = {
|
||||
id: 2,
|
||||
mode: LGraphEventMode.BYPASS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
regularNode.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const regularNode = { id: 2, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
|
||||
app.canvas.selected_nodes = { '0': subgraphNode, '1': regularNode }
|
||||
|
||||
@@ -308,15 +362,34 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should toggle to ALWAYS when subgraph is already in target mode', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const subNode2 = { id: 12, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
const mockGraph = { change: vi.fn() }
|
||||
const subNode1 = {
|
||||
id: 11,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
subNode1.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const subNode2 = {
|
||||
id: 12,
|
||||
mode: LGraphEventMode.BYPASS,
|
||||
changeMode: vi.fn((mode) => {
|
||||
subNode2.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
const subgraphNode = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.NEVER, // Already in NEVER mode
|
||||
isSubgraphNode: () => true,
|
||||
subgraph: {
|
||||
nodes: [subNode1, subNode2]
|
||||
}
|
||||
},
|
||||
changeMode: vi.fn((mode) => {
|
||||
subgraphNode.mode = mode
|
||||
}),
|
||||
graph: mockGraph
|
||||
} as unknown as LGraphNode
|
||||
|
||||
app.canvas.selected_nodes = { '0': subgraphNode }
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
traverseNodesDepthFirst
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
|
||||
import { useNodeMode } from './useNodeMode'
|
||||
|
||||
/**
|
||||
* Composable for handling selected LiteGraph items filtering and operations.
|
||||
* This provides utilities for working with selected items on the canvas,
|
||||
@@ -114,6 +116,8 @@ export function useSelectedLiteGraphItems() {
|
||||
const selectedNodes = app.canvas.selected_nodes
|
||||
if (!selectedNodes) return
|
||||
|
||||
const { setNodeMode } = useNodeMode()
|
||||
|
||||
// Convert selected_nodes object to array
|
||||
const selectedNodeArray: LGraphNode[] = []
|
||||
for (const i in selectedNodes) {
|
||||
@@ -127,8 +131,7 @@ export function useSelectedLiteGraphItems() {
|
||||
// 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
|
||||
|
||||
selectedNode.mode = newModeForSelectedNode
|
||||
setNodeMode(selectedNode, 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
|
||||
@@ -139,7 +142,7 @@ export function useSelectedLiteGraphItems() {
|
||||
if (node === selectedNode) return undefined
|
||||
|
||||
// Apply the parent's new mode to all children uniformly
|
||||
node.mode = newModeForSelectedNode
|
||||
setNodeMode(node, newModeForSelectedNode)
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user