mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
feat(editor): Improve edges and connection lines rendering on new canvas (no-changelog) (#11036)
This commit is contained in:
parent
e7199dbfcc
commit
e04b980dff
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue