mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-23 07:50:15 +00:00
[feat] sync subgraph node titles with breadcrumb renaming (#4565)
This commit is contained in:
@@ -42,6 +42,7 @@ import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import { forEachSubgraphNode } from '@/utils/graphTraversalUtil'
|
||||
|
||||
const MIN_WIDTH = 28
|
||||
const ITEM_GAP = 8
|
||||
@@ -71,6 +72,14 @@ const items = computed(() => {
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
|
||||
canvas.setGraph(subgraph)
|
||||
},
|
||||
updateTitle: (title: string) => {
|
||||
const rootGraph = useCanvasStore().getCanvas().graph?.rootGraph
|
||||
if (!rootGraph) return
|
||||
|
||||
forEachSubgraphNode(rootGraph, subgraph.id, (node) => {
|
||||
node.title = title
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ const rename = async (
|
||||
initialName: string
|
||||
) => {
|
||||
if (newName && newName !== initialName) {
|
||||
// Synchronize the node titles with the new name
|
||||
props.item.updateTitle?.(newName)
|
||||
|
||||
if (workflowStore.activeSubgraph) {
|
||||
workflowStore.activeSubgraph.name = newName
|
||||
} else if (workflowStore.activeWorkflow) {
|
||||
|
||||
@@ -86,13 +86,7 @@ export function triggerCallbackOnAllNodes(
|
||||
graph: LGraph | Subgraph,
|
||||
callbackProperty: keyof LGraphNode
|
||||
): void {
|
||||
visitGraphNodes(graph, (node) => {
|
||||
// Recursively process subgraphs first
|
||||
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||
triggerCallbackOnAllNodes(node.subgraph, callbackProperty)
|
||||
}
|
||||
|
||||
// Invoke callback if it exists on the node
|
||||
forEachNode(graph, (node) => {
|
||||
const callback = node[callbackProperty]
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(node)
|
||||
@@ -100,6 +94,58 @@ export function triggerCallbackOnAllNodes(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a function over all nodes in a graph hierarchy (including subgraphs).
|
||||
* This is a pure functional traversal that doesn't mutate the graph.
|
||||
*
|
||||
* @param graph - The root graph to traverse
|
||||
* @param mapFn - Function to apply to each node
|
||||
* @returns Array of mapped results (excluding undefined values)
|
||||
*/
|
||||
export function mapAllNodes<T>(
|
||||
graph: LGraph | Subgraph,
|
||||
mapFn: (node: LGraphNode) => T | undefined
|
||||
): T[] {
|
||||
const results: T[] = []
|
||||
|
||||
visitGraphNodes(graph, (node) => {
|
||||
// Recursively map over subgraphs first
|
||||
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||
results.push(...mapAllNodes(node.subgraph, mapFn))
|
||||
}
|
||||
|
||||
// Apply map function to current node
|
||||
const result = mapFn(node)
|
||||
if (result !== undefined) {
|
||||
results.push(result)
|
||||
}
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a side-effect function on all nodes in a graph hierarchy.
|
||||
* This is for operations that modify nodes or perform side effects.
|
||||
*
|
||||
* @param graph - The root graph to traverse
|
||||
* @param fn - Function to execute on each node
|
||||
*/
|
||||
export function forEachNode(
|
||||
graph: LGraph | Subgraph,
|
||||
fn: (node: LGraphNode) => void
|
||||
): void {
|
||||
visitGraphNodes(graph, (node) => {
|
||||
// Recursively process subgraphs first
|
||||
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||
forEachNode(node.subgraph, fn)
|
||||
}
|
||||
|
||||
// Execute function on current node
|
||||
fn(node)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all nodes in a graph hierarchy (including subgraphs) into a flat array.
|
||||
*
|
||||
@@ -111,21 +157,12 @@ export function collectAllNodes(
|
||||
graph: LGraph | Subgraph,
|
||||
filter?: (node: LGraphNode) => boolean
|
||||
): LGraphNode[] {
|
||||
const nodes: LGraphNode[] = []
|
||||
|
||||
visitGraphNodes(graph, (node) => {
|
||||
// Recursively collect from subgraphs
|
||||
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||
nodes.push(...collectAllNodes(node.subgraph, filter))
|
||||
}
|
||||
|
||||
// Add node if it passes the filter (or no filter provided)
|
||||
return mapAllNodes(graph, (node) => {
|
||||
if (!filter || filter(node)) {
|
||||
nodes.push(node)
|
||||
return node
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,3 +278,63 @@ export function getNodeByLocatorId(
|
||||
|
||||
return targetSubgraph.getNodeById(localNodeId) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the root graph from any graph in the hierarchy.
|
||||
*
|
||||
* @param graph - Any graph or subgraph in the hierarchy
|
||||
* @returns The root graph
|
||||
*/
|
||||
export function getRootGraph(graph: LGraph | Subgraph): LGraph | Subgraph {
|
||||
let current: LGraph | Subgraph = graph
|
||||
while ('rootGraph' in current && current.rootGraph) {
|
||||
current = current.rootGraph
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a function to all nodes whose type matches a subgraph ID.
|
||||
* Operates on the entire graph hierarchy starting from the root.
|
||||
*
|
||||
* @param rootGraph - The root graph to search in
|
||||
* @param subgraphId - The ID/type of the subgraph to match nodes against
|
||||
* @param fn - Function to apply to each matching node
|
||||
*/
|
||||
export function forEachSubgraphNode(
|
||||
rootGraph: LGraph | Subgraph | null | undefined,
|
||||
subgraphId: string | null | undefined,
|
||||
fn: (node: LGraphNode) => void
|
||||
): void {
|
||||
if (!rootGraph || !subgraphId) return
|
||||
|
||||
forEachNode(rootGraph, (node) => {
|
||||
if (node.type === subgraphId) {
|
||||
fn(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a function over all nodes whose type matches a subgraph ID.
|
||||
* Operates on the entire graph hierarchy starting from the root.
|
||||
*
|
||||
* @param rootGraph - The root graph to search in
|
||||
* @param subgraphId - The ID/type of the subgraph to match nodes against
|
||||
* @param mapFn - Function to apply to each matching node
|
||||
* @returns Array of mapped results
|
||||
*/
|
||||
export function mapSubgraphNodes<T>(
|
||||
rootGraph: LGraph | Subgraph | null | undefined,
|
||||
subgraphId: string | null | undefined,
|
||||
mapFn: (node: LGraphNode) => T
|
||||
): T[] {
|
||||
if (!rootGraph || !subgraphId) return []
|
||||
|
||||
return mapAllNodes(rootGraph, (node) => {
|
||||
if (node.type === subgraphId) {
|
||||
return mapFn(node)
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user