feat(editor): Add an option to sync canvas with log view (#15391)

This commit is contained in:
Suguru Inoue
2025-05-22 17:25:58 +02:00
committed by GitHub
parent b1da30f493
commit 9938e63a66
19 changed files with 383 additions and 49 deletions

View File

@@ -9,6 +9,7 @@ import { NodeConnectionTypes } from 'n8n-workflow';
import type { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
import { useVueFlow } from '@vue-flow/core';
import { SIMULATE_NODE_TYPE } from '@/constants';
import { canvasEventBus } from '@/event-bus/canvas';
const matchMedia = global.window.matchMedia;
// @ts-expect-error Initialize window object
@@ -156,6 +157,24 @@ describe('Canvas', () => {
expect(emitted()['update:node:name']).toEqual([['1']]);
});
it('should update viewport if nodes:select event is received with panIntoView=true', async () => {
const node = createCanvasNodeElement({ position: { x: -1000, y: -500 } });
renderComponent({
props: {
id: 'c0',
nodes: [node],
eventBus: canvasEventBus,
},
});
const { getViewport } = useVueFlow('c0');
expect(getViewport()).toEqual({ x: 0, y: 0, zoom: 1 });
canvasEventBus.emit('nodes:select', { ids: [node.id], panIntoView: true });
await waitFor(() => expect(getViewport()).toEqual({ x: 1100, y: 600, zoom: 1 }));
});
it('should not emit `update:node:name` event if long key press', async () => {
vi.useFakeTimers();

View File

@@ -19,7 +19,7 @@ import type {
CanvasNodeData,
} from '@/types';
import { CanvasNodeRenderType } from '@/types';
import { getMousePosition, GRID_SIZE } from '@/utils/nodeViewUtils';
import { updateViewportToContainNodes, getMousePosition, GRID_SIZE } from '@/utils/nodeViewUtils';
import { isPresent } from '@/utils/typesUtils';
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
import { useShortKeyPress } from '@n8n/composables/useShortKeyPress';
@@ -72,6 +72,7 @@ const emit = defineEmits<{
'update:logs-open': [open?: boolean];
'update:logs:input-open': [open?: boolean];
'update:logs:output-open': [open?: boolean];
'update:has-range-selection': [isActive: boolean];
'click:node': [id: string, position: XYPosition];
'click:node:add': [id: string, handle: string];
'run:node': [id: string];
@@ -153,6 +154,7 @@ const {
viewport,
dimensions,
nodesSelectionActive,
userSelectionRect,
setViewport,
onEdgeMouseLeave,
onEdgeMouseEnter,
@@ -402,9 +404,21 @@ function onSelectNode() {
emit('update:node:selected', lastSelectedNode.value?.id);
}
function onSelectNodes({ ids }: CanvasEventBusEvents['nodes:select']) {
function onSelectNodes({ ids, panIntoView }: CanvasEventBusEvents['nodes:select']) {
clearSelectedNodes();
addSelectedNodes(ids.map(findNode).filter(isPresent));
if (panIntoView) {
const nodes = ids.map(findNode).filter(isPresent);
if (nodes.length === 0) {
return;
}
const newViewport = updateViewportToContainNodes(viewport.value, dimensions.value, nodes, 100);
void setViewport(newViewport, { duration: 200 });
}
}
function onToggleNodeEnabled(id: string) {
@@ -798,6 +812,10 @@ watch(() => props.readOnly, setReadonly, {
immediate: true,
});
watch([nodesSelectionActive, userSelectionRect], ([isActive, rect]) =>
emit('update:has-range-selection', isActive || (rect?.width ?? 0) > 0 || (rect?.height ?? 0) > 0),
);
/**
* Provide
*/