mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
## 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)
242 lines
6.7 KiB
TypeScript
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
|
|
)
|