mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
* [refactor] Improve renderer architecture organization Building on PR #5388, this refines the renderer domain structure: **Key improvements:** - Group all transform utilities in `transform/` subdirectory for better cohesion - Move canvas state to dedicated `renderer/core/canvas/` domain - Consolidate coordinate system logic (TransformPane, useTransformState, sync utilities) **File organization:** - `renderer/core/canvas/canvasStore.ts` (was `stores/graphStore.ts`) - `renderer/core/layout/transform/` contains all coordinate system utilities - Transform sync utilities co-located with core transform logic This creates clearer domain boundaries and groups related functionality while building on the foundation established in PR #5388. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Clean up linter-modified files * Fix import paths and clean up unused imports after rebase - Update all remaining @/stores/graphStore references to @/renderer/core/canvas/canvasStore - Remove unused imports from selection toolbox components - All tests pass, only reka-ui upstream issue remains in typecheck 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [auto-fix] Apply ESLint and Prettier fixes --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
222 lines
5.9 KiB
TypeScript
222 lines
5.9 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 type { Ref } from 'vue'
|
|
|
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
|
|
|
|
interface NodeManager {
|
|
getNode: (id: string) => any
|
|
}
|
|
|
|
export function useNodeEventHandlers(nodeManager: Ref<NodeManager | null>) {
|
|
const canvasStore = useCanvasStore()
|
|
const { bringNodeToFront } = useNodeZIndex()
|
|
|
|
/**
|
|
* Handle node selection events
|
|
* Supports single selection and multi-select with Ctrl/Cmd
|
|
*/
|
|
const handleNodeSelect = (
|
|
event: PointerEvent,
|
|
nodeData: VueNodeData,
|
|
wasDragging: boolean
|
|
) => {
|
|
if (!canvasStore.canvas || !nodeManager.value) return
|
|
|
|
const node = nodeManager.value.getNode(nodeData.id)
|
|
if (!node) return
|
|
|
|
const isMultiSelect = event.ctrlKey || event.metaKey
|
|
|
|
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 (!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 (!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 (!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 (!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 (!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 (!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 (!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
|
|
}
|
|
}
|