mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 20:51:58 +00:00
Select Vue Nodes After Drag (#5863)
This pull request refactors the node selection logic in the Vue nodes event handler composable to simplify the function signature and improve single vs. multi-selection behavior. The main change is the removal of the `wasDragging` parameter from the `handleNodeSelect` function, with selection logic now determined by the current selection state. Related test code is updated to match the new function signature. **Node selection logic improvements:** * Refactored the `handleNodeSelect` function in `useNodeEventHandlersIndividual` to remove the `wasDragging` parameter, making the function signature simpler and relying on selection state to handle single vs. multi-selection. * Updated the selection logic to check if multiple nodes are already selected using `isLGraphNode`, and only perform single selection if not. **Code and test updates:** * Updated all calls to `handleNodeSelect` in the composable to remove the `wasDragging` argument, ensuring consistent usage throughout the codebase. [[1]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L125-R123) [[2]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L146-R144) [[3]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L173-R171) * Updated all related test cases to use the new `handleNodeSelect` signature without the third parameter. [[1]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL105-R105) [[2]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL125-R125) [[3]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL144-R144) [[4]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL162-R162) [[5]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL174-R174) [[6]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL187-R187) **Utility import:** * Added an import for `isLGraphNode` from `@/utils/litegraphUtil` to support the updated selection logic.## Summary <!-- One sentence describing what changed and why. --> ## Screenshots (if applicable) https://github.com/user-attachments/assets/71e856d3-afc2-497d-826e-5b485066e7fe --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
committed by
GitHub
parent
eeb0977738
commit
4cb03cf052
@@ -69,125 +69,85 @@ const createMouseEvent = (
|
||||
}
|
||||
|
||||
describe('useNodePointerInteractions', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
// Reset layout store state between tests
|
||||
const { layoutStore } = await import(
|
||||
'@/renderer/core/layout/store/layoutStore'
|
||||
)
|
||||
layoutStore.isDraggingVueNodes.value = false
|
||||
})
|
||||
|
||||
it('should only start drag on left-click', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Right-click should not start drag
|
||||
// Right-click should not trigger selection
|
||||
const rightClickEvent = createPointerEvent('pointerdown', { button: 2 })
|
||||
pointerHandlers.onPointerdown(rightClickEvent)
|
||||
|
||||
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||
expect(mockOnNodeSelect).not.toHaveBeenCalled()
|
||||
|
||||
// Left-click should start drag and emit callback
|
||||
// Left-click should trigger selection on pointer down
|
||||
const leftClickEvent = createPointerEvent('pointerdown', { button: 0 })
|
||||
pointerHandlers.onPointerdown(leftClickEvent)
|
||||
|
||||
const pointerUpEvent = createPointerEvent('pointerup')
|
||||
pointerHandlers.onPointerup(pointerUpEvent)
|
||||
|
||||
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||
pointerUpEvent,
|
||||
mockNodeData,
|
||||
false // wasDragging = false (same position)
|
||||
)
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledWith(leftClickEvent, mockNodeData)
|
||||
})
|
||||
|
||||
it('forwards middle mouse interactions to the canvas', () => {
|
||||
it('should call onNodeSelect on pointer down', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
const middlePointerDown = createPointerEvent('pointerdown', { button: 1 })
|
||||
pointerHandlers.onPointerdown(middlePointerDown)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerDown)
|
||||
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
|
||||
const middlePointerMove = createPointerEvent('pointermove', { buttons: 4 })
|
||||
pointerHandlers.onPointermove(middlePointerMove)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerMove)
|
||||
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
|
||||
const middlePointerUp = createPointerEvent('pointerup', { button: 1 })
|
||||
pointerHandlers.onPointerup(middlePointerUp)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerUp)
|
||||
|
||||
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should distinguish drag from click based on distance threshold', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
)
|
||||
|
||||
// Test drag (distance > 4px)
|
||||
pointerHandlers.onPointerdown(
|
||||
createPointerEvent('pointerdown', { clientX: 100, clientY: 100 })
|
||||
)
|
||||
|
||||
const dragUpEvent = createPointerEvent('pointerup', {
|
||||
clientX: 200,
|
||||
clientY: 200
|
||||
// Selection should happen on pointer down
|
||||
const downEvent = createPointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 100
|
||||
})
|
||||
pointerHandlers.onPointerup(dragUpEvent)
|
||||
pointerHandlers.onPointerdown(downEvent)
|
||||
|
||||
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||
dragUpEvent,
|
||||
mockNodeData,
|
||||
true
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledWith(downEvent, mockNodeData)
|
||||
|
||||
mockOnNodeSelect.mockClear()
|
||||
|
||||
// Even if we drag, selection already happened on pointer down
|
||||
pointerHandlers.onPointerup(
|
||||
createPointerEvent('pointerup', { clientX: 200, clientY: 200 })
|
||||
)
|
||||
|
||||
mockOnPointerUp.mockClear()
|
||||
|
||||
// Test click (same position)
|
||||
const samePos = { clientX: 100, clientY: 100 }
|
||||
pointerHandlers.onPointerdown(createPointerEvent('pointerdown', samePos))
|
||||
|
||||
const clickUpEvent = createPointerEvent('pointerup', samePos)
|
||||
pointerHandlers.onPointerup(clickUpEvent)
|
||||
|
||||
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||
clickUpEvent,
|
||||
mockNodeData,
|
||||
false
|
||||
)
|
||||
// onNodeSelect should not be called again on pointer up
|
||||
expect(mockOnNodeSelect).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle drag termination via cancel and context menu', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Test pointer cancel
|
||||
// Test pointer cancel - selection happens on pointer down
|
||||
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
|
||||
pointerHandlers.onPointercancel(createPointerEvent('pointercancel'))
|
||||
|
||||
// Should not emit callback on cancel
|
||||
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||
// Selection should have been called on pointer down only
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
|
||||
mockOnNodeSelect.mockClear()
|
||||
|
||||
// Test context menu during drag prevents default
|
||||
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||
@@ -200,36 +160,35 @@ describe('useNodePointerInteractions', () => {
|
||||
expect(preventDefaultSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not emit callback when nodeData becomes null', async () => {
|
||||
it('should not call onNodeSelect when nodeData is null', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
const nodeDataRef = ref<VueNodeData | null>(mockNodeData)
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
nodeDataRef,
|
||||
mockOnPointerUp
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Clear nodeData before pointer down
|
||||
nodeDataRef.value = null
|
||||
await nextTick()
|
||||
|
||||
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||
|
||||
// Clear nodeData before pointerup
|
||||
nodeDataRef.value = null
|
||||
|
||||
pointerHandlers.onPointerup(createPointerEvent('pointerup'))
|
||||
|
||||
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||
expect(mockOnNodeSelect).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should integrate with layout store dragging state', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
const { layoutStore } = await import(
|
||||
'@/renderer/core/layout/store/layoutStore'
|
||||
)
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Start drag
|
||||
@@ -242,4 +201,93 @@ describe('useNodePointerInteractions', () => {
|
||||
await nextTick()
|
||||
expect(layoutStore.isDraggingVueNodes.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should select node on pointer down with ctrl key for multi-select', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Pointer down with ctrl key should pass the event with ctrl key set
|
||||
const ctrlDownEvent = createPointerEvent('pointerdown', {
|
||||
ctrlKey: true,
|
||||
clientX: 100,
|
||||
clientY: 100
|
||||
})
|
||||
pointerHandlers.onPointerdown(ctrlDownEvent)
|
||||
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledWith(ctrlDownEvent, mockNodeData)
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should select pinned node on pointer down but not start drag', async () => {
|
||||
const mockNodeData = createMockVueNodeData({
|
||||
flags: { pinned: true }
|
||||
})
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
const { layoutStore } = await import(
|
||||
'@/renderer/core/layout/store/layoutStore'
|
||||
)
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Pointer down on pinned node
|
||||
const downEvent = createPointerEvent('pointerdown')
|
||||
pointerHandlers.onPointerdown(downEvent)
|
||||
|
||||
// Should select the node
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledWith(downEvent, mockNodeData)
|
||||
|
||||
// But should not start dragging
|
||||
expect(layoutStore.isDraggingVueNodes.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should select node immediately when drag starts', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnNodeSelect = vi.fn()
|
||||
const { layoutStore } = await import(
|
||||
'@/renderer/core/layout/store/layoutStore'
|
||||
)
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnNodeSelect
|
||||
)
|
||||
|
||||
// Pointer down should select node immediately
|
||||
const downEvent = createPointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 100
|
||||
})
|
||||
pointerHandlers.onPointerdown(downEvent)
|
||||
|
||||
// Selection should happen on pointer down (before move)
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledWith(downEvent, mockNodeData)
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Dragging state should be active
|
||||
expect(layoutStore.isDraggingVueNodes.value).toBe(true)
|
||||
|
||||
// Move the pointer (start dragging)
|
||||
pointerHandlers.onPointermove(
|
||||
createPointerEvent('pointermove', { clientX: 150, clientY: 150 })
|
||||
)
|
||||
|
||||
// Selection should still only have been called once (on pointer down)
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
|
||||
// End drag
|
||||
pointerHandlers.onPointerup(
|
||||
createPointerEvent('pointerup', { clientX: 150, clientY: 150 })
|
||||
)
|
||||
|
||||
// Selection should still only have been called once
|
||||
expect(mockOnNodeSelect).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user