Files
ComfyUI_frontend/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts
Alexander Brown 1611c7a224 Refactor: Further state management cleanup (#5727)
## Summary

Going through the GraphNodeManager and VueNodeLifecycle one property at
a time and removing the pieces that are not currently wired up or used
by the rest of the application

Fixes paste location by updating the layoutStore in LGraphCanvas (which
already mutates layoutStore elsewhere)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5727-WIP-Refactor-Further-state-management-cleanup-2766d73d36508173b379c6009c194a5a)
by [Unito](https://www.unito.io)
2025-09-22 18:47:26 -07:00

242 lines
6.7 KiB
TypeScript

/**
* Node Event Handlers Composable
*
* Handles all Vue node interaction events including:
* - Node selection with multi-select support
* - Node collapse/expand state management
* - Node title editing and updates
* - Layout mutations for visual feedback
* - Integration with LiteGraph canvas selection system
*/
import { createSharedComposable } from '@vueuse/core'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
function useNodeEventHandlersIndividual() {
const canvasStore = useCanvasStore()
const { nodeManager } = useVueNodeLifecycle()
const { bringNodeToFront } = useNodeZIndex()
const { shouldHandleNodePointerEvents } = useCanvasInteractions()
/**
* Handle node selection events
* Supports single selection and multi-select with Ctrl/Cmd
*/
const handleNodeSelect = (
event: PointerEvent,
nodeData: VueNodeData,
wasDragging: boolean
) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
const node = nodeManager.value.getNode(nodeData.id)
if (!node) return
const isMultiSelect = event.ctrlKey || event.metaKey || event.shiftKey
if (isMultiSelect) {
// Ctrl/Cmd+click -> toggle selection
if (node.selected) {
canvasStore.canvas.deselect(node)
} else {
canvasStore.canvas.select(node)
}
} else {
// If it wasn't a drag: single-select the node
if (!wasDragging) {
canvasStore.canvas.deselectAll()
canvasStore.canvas.select(node)
}
// Regular click -> single select
}
// Bring node to front when clicked (similar to LiteGraph behavior)
// Skip if node is pinned to avoid unwanted movement
if (!node.flags?.pinned) {
bringNodeToFront(nodeData.id)
}
// Update canvas selection tracking
canvasStore.updateSelectedItems()
}
/**
* Handle node collapse/expand state changes
* Uses LiteGraph's native collapse method for proper state management
*/
const handleNodeCollapse = (nodeId: string, collapsed: boolean) => {
if (!shouldHandleNodePointerEvents.value) return
if (!nodeManager.value) return
const node = nodeManager.value.getNode(nodeId)
if (!node) return
// Use LiteGraph's collapse method if the state needs to change
const currentCollapsed = node.flags?.collapsed ?? false
if (currentCollapsed !== collapsed) {
node.collapse()
}
}
/**
* Handle node title updates
* Updates the title in LiteGraph for persistence across sessions
*/
const handleNodeTitleUpdate = (nodeId: string, newTitle: string) => {
if (!shouldHandleNodePointerEvents.value) return
if (!nodeManager.value) return
const node = nodeManager.value.getNode(nodeId)
if (!node) return
// Update the node title in LiteGraph for persistence
node.title = newTitle
}
/**
* Handle node double-click events
* Can be used for custom actions like opening node editor
*/
const handleNodeDoubleClick = (
event: PointerEvent,
nodeData: VueNodeData
) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
const node = nodeManager.value.getNode(nodeData.id)
if (!node) return
// Prevent default browser behavior
event.preventDefault()
// TODO: add custom double-click behavior here
// For now, ensure node is selected
if (!node.selected) {
handleNodeSelect(event, nodeData, false)
}
}
/**
* Handle node right-click context menu events
* Integrates with LiteGraph's context menu system
*/
const handleNodeRightClick = (event: PointerEvent, nodeData: VueNodeData) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
const node = nodeManager.value.getNode(nodeData.id)
if (!node) return
// Prevent default context menu
event.preventDefault()
// Select the node if not already selected
if (!node.selected) {
handleNodeSelect(event, nodeData, false)
}
// Let LiteGraph handle the context menu
// The canvas will handle showing the appropriate context menu
}
/**
* Handle node drag start events
* Prepares node for dragging and sets appropriate visual state
*/
const handleNodeDragStart = (event: DragEvent, nodeData: VueNodeData) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
const node = nodeManager.value.getNode(nodeData.id)
if (!node) return
// Ensure node is selected before dragging
if (!node.selected) {
// Create a synthetic pointer event for selection
const syntheticEvent = new PointerEvent('pointerdown', {
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
bubbles: true
})
handleNodeSelect(syntheticEvent, nodeData, false)
}
// Set drag data for potential drop operations
if (event.dataTransfer) {
event.dataTransfer.setData('application/comfy-node-id', nodeData.id)
event.dataTransfer.effectAllowed = 'move'
}
}
/**
* Batch select multiple nodes
* Useful for selection toolbox or area selection
*/
const selectNodes = (nodeIds: string[], addToSelection = false) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
if (!addToSelection) {
canvasStore.canvas.deselectAll()
}
nodeIds.forEach((nodeId) => {
const node = nodeManager.value?.getNode(nodeId)
if (node && canvasStore.canvas) {
canvasStore.canvas.select(node)
}
})
canvasStore.updateSelectedItems()
}
/**
* Deselect specific nodes
*/
const deselectNodes = (nodeIds: string[]) => {
if (!shouldHandleNodePointerEvents.value) return
if (!canvasStore.canvas || !nodeManager.value) return
nodeIds.forEach((nodeId) => {
const node = nodeManager.value?.getNode(nodeId)
if (node) {
node.selected = false
}
})
canvasStore.updateSelectedItems()
}
return {
// Core event handlers
handleNodeSelect,
handleNodeCollapse,
handleNodeTitleUpdate,
handleNodeDoubleClick,
handleNodeRightClick,
handleNodeDragStart,
// Batch operations
selectNodes,
deselectNodes
}
}
export const useNodeEventHandlers = createSharedComposable(
useNodeEventHandlersIndividual
)