refactor: implement centralized node mode management and update related components

This commit is contained in:
Rizumu Ayaka
2026-01-14 16:23:17 +08:00
parent 6382b1e099
commit c5509a0838
4 changed files with 132 additions and 24 deletions

View File

@@ -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')
}
})

View 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
}
}

View File

@@ -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 }

View File

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