1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-06 01:27:49 +03:00

feat(editor): Improve edges and connection lines rendering on new canvas (no-changelog) (#11036)

This commit is contained in:
Alex Grozav 2024-10-01 16:43:47 +03:00 committed by GitHub
parent e7199dbfcc
commit e04b980dff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 78 additions and 22 deletions

View File

@ -50,7 +50,7 @@ describe('CanvasConnectionLine', () => {
expect(edge).toHaveAttribute(
'd',
'M0 0L 32,0Q 40,0 40,8L 40,132Q 40,140 32,140L1 140L0 140M0 140L-40 140L -132,140Q -140,140 -140,132L -140,-92Q -140,-100 -132,-100L-100 -100',
'M0 0L 24,0Q 40,0 40,16L 40,124Q 40,140 24,140L1 140L0 140M0 140L-40 140L -124,140Q -140,140 -140,124L -140,-84Q -140,-100 -124,-100L-100 -100',
);
});
@ -71,7 +71,7 @@ describe('CanvasConnectionLine', () => {
expect(edge).toHaveAttribute(
'd',
'M-72 -290L -57,-290Q -52,-290 -52,-285L -52,-165Q -52,-160 -57,-160L -359,-160Q -364,-160 -364,-155L -364,-35Q -364,-30 -359,-30L-344 -30',
'M-72 -290L -62,-290Q -52,-290 -52,-280L -52,-176Q -52,-160 -68,-160L -348,-160Q -364,-160 -364,-144L -364,-40Q -364,-30 -354,-30L-344 -30',
);
});
});

View File

@ -4,16 +4,35 @@ import type { ConnectionLineProps } from '@vue-flow/core';
import { BaseEdge } from '@vue-flow/core';
import { computed, useCssModule } from 'vue';
import { getCustomPath } from './utils/edgePath';
import { useCanvas } from '@/composables/useCanvas';
import { NodeConnectionType } from 'n8n-workflow';
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
const props = defineProps<ConnectionLineProps>();
const $style = useCssModule();
const { connectingHandle } = useCanvas();
const connectionType = computed(
() => parseCanvasConnectionHandleString(connectingHandle.value?.handleId).type,
);
const edgeColor = computed(() => {
if (connectionType.value !== NodeConnectionType.Main) {
return 'var(--node-type-supplemental-color)';
} else {
return 'var(--color-foreground-xdark)';
}
});
const edgeStyle = computed(() => ({
...(connectionType.value === NodeConnectionType.Main ? {} : { strokeDasharray: '8,8' }),
strokeWidth: 2,
stroke: edgeColor.value,
}));
const path = computed(() => getCustomPath(props));
const path = computed(() => getCustomPath(props, { connectionType: connectionType.value }));
</script>
<template>

View File

@ -124,7 +124,31 @@ describe('CanvasEdge', () => {
expect(edge).toHaveAttribute(
'd',
'M0 0L 32,0Q 40,0 40,8L 40,132Q 40,140 32,140L1 140L0 140M0 140L-40 140L -132,140Q -140,140 -140,132L -140,-92Q -140,-100 -132,-100L-100 -100',
'M0 0L 24,0Q 40,0 40,16L 40,124Q 40,140 24,140L1 140L0 140M0 140L-40 140L -124,140Q -140,140 -140,124L -140,-84Q -140,-100 -124,-100L-100 -100',
);
});
it('should render a correct bezier path when the connection is backwards and node connection type is non-main', () => {
const { container } = renderComponent({
props: {
...DEFAULT_PROPS,
data: {
...DEFAULT_PROPS.data,
source: {
type: NodeConnectionType.AiTool,
},
},
sourceX: 0,
sourceY: 0,
sourcePosition: Position.Right,
targetX: -100,
targetY: -100,
targetPosition: Position.Left,
},
});
const edge = container.querySelector('.vue-flow__edge-path');
expect(edge).toHaveAttribute('d', 'M0,0 C62.5,0 -162.5,-100 -100,-100');
});
});

View File

@ -49,7 +49,8 @@ const renderToolbar = computed(() => (props.selected || isHovered.value) && !pro
const isMainConnection = computed(() => data.value.source.type === NodeConnectionType.Main);
const status = computed(() => props.data.status);
const statusColor = computed(() => {
const edgeColor = computed(() => {
if (props.selected) {
return 'var(--color-background-dark)';
} else if (status.value === 'success') {
@ -69,10 +70,10 @@ const edgeStyle = computed(() => ({
...props.style,
...(isMainConnection.value ? {} : { strokeDasharray: '8,8' }),
strokeWidth: 2,
stroke: isHovered.value ? 'var(--color-primary)' : statusColor.value,
stroke: isHovered.value ? 'var(--color-primary)' : edgeColor.value,
}));
const edgeLabelStyle = computed(() => ({ color: statusColor.value }));
const edgeLabelStyle = computed(() => ({ color: edgeColor.value }));
const edgeToolbarStyle = computed(() => {
const [, labelX, labelY] = path.value;
@ -81,7 +82,11 @@ const edgeToolbarStyle = computed(() => {
};
});
const path = computed(() => getCustomPath(props));
const path = computed(() =>
getCustomPath(props, {
connectionType: connectionType.value,
}),
);
const connection = computed<Connection>(() => ({
source: props.source,

View File

@ -1,38 +1,43 @@
import type { EdgeProps } from '@vue-flow/core';
import { getBezierPath, getSmoothStepPath, Position } from '@vue-flow/core';
import { NodeConnectionType } from 'n8n-workflow';
const EDGE_PADDING_Y = 140;
const EDGE_PADDING_Y_TOP = 80;
const EDGE_BORDER_RADIUS = 8;
const EDGE_OFFSET = 40;
// const HANDLE_SIZE = 16;
const EDGE_PADDING_TOP = 80;
const EDGE_PADDING_BOTTOM = 140;
const EDGE_PADDING_X = 40;
const EDGE_BORDER_RADIUS = 16;
const HANDLE_SIZE = 20; // Required to avoid connection line glitching when initially interacting with the handle
const isTargetHandlePositionedLeftOfSourceTarget = (sourceX: number, targetX: number) =>
sourceX > targetX;
const isRightOfSourceHandle = (sourceX: number, targetX: number) => sourceX - HANDLE_SIZE > targetX;
const pathIntersectsNodes = (targetY: number, sourceY: number) =>
Math.abs(targetY - sourceY) < EDGE_PADDING_Y;
Math.abs(targetY - sourceY) < EDGE_PADDING_BOTTOM;
export function getCustomPath(
props: Pick<
EdgeProps,
'sourceX' | 'sourceY' | 'sourcePosition' | 'targetX' | 'targetY' | 'targetPosition'
>,
{
connectionType = NodeConnectionType.Main,
}: {
connectionType?: NodeConnectionType;
} = {},
) {
const { targetX, targetY, sourceX, sourceY, sourcePosition, targetPosition } = props;
const yDiff = targetY - sourceY;
if (!isTargetHandlePositionedLeftOfSourceTarget(sourceX, targetX)) {
if (!isRightOfSourceHandle(sourceX, targetX) || connectionType !== NodeConnectionType.Main) {
return getBezierPath(props);
}
// Connection is backwards and the source is on the right side
// -> We need to avoid overlapping the source node
if (pathIntersectsNodes(targetY, sourceY)) {
const direction = yDiff < -EDGE_PADDING_Y || yDiff > 0 ? 'up' : 'down';
const direction = yDiff < -EDGE_PADDING_BOTTOM || yDiff > 0 ? 'up' : 'down';
const firstSegmentTargetX = sourceX;
const firstSegmentTargetY =
sourceY + (direction === 'up' ? -EDGE_PADDING_Y_TOP : EDGE_PADDING_Y);
sourceY + (direction === 'up' ? -EDGE_PADDING_TOP : EDGE_PADDING_BOTTOM);
const [firstSegmentPath] = getSmoothStepPath({
sourceX,
sourceY,
@ -41,7 +46,7 @@ export function getCustomPath(
sourcePosition,
targetPosition: Position.Right,
borderRadius: EDGE_BORDER_RADIUS,
offset: EDGE_OFFSET,
offset: EDGE_PADDING_X,
});
const path = getSmoothStepPath({
sourceX: firstSegmentTargetX,
@ -51,12 +56,15 @@ export function getCustomPath(
sourcePosition: Position.Left,
targetPosition,
borderRadius: EDGE_BORDER_RADIUS,
offset: EDGE_OFFSET,
offset: EDGE_PADDING_X,
});
path[0] = firstSegmentPath + path[0];
return path;
}
return getSmoothStepPath(props);
return getSmoothStepPath({
...props,
borderRadius: EDGE_BORDER_RADIUS,
});
}