diff --git a/packages/editor-ui/src/components/canvas/Canvas.spec.ts b/packages/editor-ui/src/components/canvas/Canvas.spec.ts index 8768e97798..5ab4c6e96e 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.spec.ts +++ b/packages/editor-ui/src/components/canvas/Canvas.spec.ts @@ -87,6 +87,8 @@ describe('Canvas', () => { }); it('should handle `update:nodes:position` event', async () => { + vi.useFakeTimers(); + const nodes = [createCanvasNodeElement()]; const { container, emitted } = renderComponent({ props: { @@ -110,22 +112,9 @@ describe('Canvas', () => { }); await fireEvent.mouseUp(node, { view: window }); + vi.advanceTimersByTime(250); // Event debounce time + expect(emitted()['update:nodes:position']).toEqual([ - [ - [ - { - id: '1', - type: 'position', - dragging: true, - from: { - x: 100, - y: 100, - z: 0, - }, - position: { x: 80, y: 80 }, - }, - ], - ], [ [ { diff --git a/packages/editor-ui/src/components/canvas/Canvas.vue b/packages/editor-ui/src/components/canvas/Canvas.vue index c68e9fac69..64a9e5647a 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.vue +++ b/packages/editor-ui/src/components/canvas/Canvas.vue @@ -29,7 +29,7 @@ import type { PinDataSource } from '@/composables/usePinnedData'; import { isPresent } from '@/utils/typesUtils'; import { GRID_SIZE } from '@/utils/nodeViewUtils'; import { CanvasKey } from '@/constants'; -import { onKeyDown, onKeyUp } from '@vueuse/core'; +import { onKeyDown, onKeyUp, useDebounceFn } from '@vueuse/core'; import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue'; const $style = useCssModule(); @@ -180,21 +180,22 @@ function onClickNodeAdd(id: string, handle: string) { emit('click:node:add', id, handle); } -function onUpdateNodesPosition(events: NodePositionChange[]) { +// Debounced to prevent emitting too many events, necessary for undo/redo +const onUpdateNodesPosition = useDebounceFn((events: NodePositionChange[]) => { emit('update:nodes:position', events); -} +}, 200); function onUpdateNodePosition(id: string, position: XYPosition) { emit('update:node:position', id, position); } -function onNodesChange(events: NodeChange[]) { - const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange => - event.type === 'position' && 'position' in event; +const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange => + event.type === 'position' && 'position' in event; +function onNodesChange(events: NodeChange[]) { const positionChangeEndEvents = events.filter(isPositionChangeEvent); if (positionChangeEndEvents.length > 0) { - onUpdateNodesPosition(positionChangeEndEvents); + void onUpdateNodesPosition(positionChangeEndEvents); } } diff --git a/packages/editor-ui/src/composables/useCanvasOperations.ts b/packages/editor-ui/src/composables/useCanvasOperations.ts index 00071e5a99..3cae57885b 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.ts @@ -164,7 +164,7 @@ export function useCanvasOperations({ router }: { router: ReturnType deleteNode(id, { trackHistory: true, trackBulk: false })); - historyStore.stopRecordingUndo(); + function deleteNodes(ids: string[], { trackHistory = true, trackBulk = true } = {}) { + if (trackHistory && trackBulk) { + historyStore.startRecordingUndo(); + } + + ids.forEach((id) => deleteNode(id, { trackHistory, trackBulk: false })); + + if (trackHistory && trackBulk) { + historyStore.stopRecordingUndo(); + } } function revertDeleteNode(node: INodeUi) { @@ -401,18 +412,15 @@ export function useCanvasOperations({ router }: { router: ReturnType !workflowsStore.pinDataByNodeName(node.name)); @@ -442,7 +456,9 @@ export function useCanvasOperations({ router }: { router: ReturnType { const typeVersion = node.typeVersion ?? resolveNodeVersion(requireNodeTypeDescription(node.type)); @@ -493,7 +505,7 @@ export function useCanvasOperations({ router }: { router: ReturnType { - if (options.trackHistory) { - historyStore.pushCommandToUndo(new AddNodeCommand(nodeData)); - } + if (options.trackHistory) { + historyStore.pushCommandToUndo(new AddNodeCommand(nodeData)); + } + void nextTick(() => { workflowsStore.setNodePristine(nodeData.name, true); nodeHelpers.matchCredentials(nodeData); @@ -1376,7 +1388,7 @@ export function useCanvasOperations({ router }: { router: ReturnType { uiStore.resetLastInteractedWith(); @@ -1731,7 +1744,7 @@ export function useCanvasOperations({ router }: { router: ReturnType