fix(editor): Tweak configurable node width (#17512)

This commit is contained in:
Suguru Inoue
2025-07-23 17:08:52 +02:00
committed by GitHub
parent d520059ec3
commit 3825f8a806
6 changed files with 26 additions and 23 deletions

View File

@@ -111,8 +111,8 @@ describe('CanvasNode', () => {
const inputHandles = getAllByTestId('canvas-node-input-handle'); const inputHandles = getAllByTestId('canvas-node-input-handle');
expect(inputHandles[1]).toHaveStyle('left: 40px'); expect(inputHandles[1]).toHaveStyle('left: 40px');
expect(inputHandles[2]).toHaveStyle('left: 168px'); expect(inputHandles[2]).toHaveStyle('left: 136px');
expect(inputHandles[3]).toHaveStyle('left: 232px'); expect(inputHandles[3]).toHaveStyle('left: 184px');
}); });
}); });

View File

@@ -34,7 +34,7 @@ import type { EventBus } from '@n8n/utils/event-bus';
import { createEventBus } from '@n8n/utils/event-bus'; import { createEventBus } from '@n8n/utils/event-bus';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue'; import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue';
import { CONFIGURATION_NODE_OFFSET, GRID_SIZE } from '@/utils/nodeViewUtils'; import { CONFIGURATION_NODE_RADIUS, GRID_SIZE } from '@/utils/nodeViewUtils';
type Props = NodeProps<CanvasNodeData> & { type Props = NodeProps<CanvasNodeData> & {
readOnly?: boolean; readOnly?: boolean;
@@ -187,7 +187,7 @@ const createEndpointMappingFn =
connectingHandle.value?.handleId === handleId; connectingHandle.value?.handleId === handleId;
const offsetValue = const offsetValue =
position === Position.Bottom position === Position.Bottom
? `${GRID_SIZE * 2 * (1 + index * 2) + CONFIGURATION_NODE_OFFSET}px` ? `${CONFIGURATION_NODE_RADIUS + GRID_SIZE * (3 * index)}px`
: isExperimentalNdvActive.value && endpoints.length === 1 : isExperimentalNdvActive.value && endpoints.length === 1
? `${(1 + index) * (GRID_SIZE * 1.5)}px` ? `${(1 + index) * (GRID_SIZE * 1.5)}px`
: `${(100 / (endpoints.length + 1)) * (index + 1)}%`; : `${(100 / (endpoints.length + 1)) * (index + 1)}%`;

View File

@@ -209,7 +209,7 @@ describe('CanvasNodeDefault', () => {
[ [
'1 required', '1 required',
[{ type: NodeConnectionTypes.AiLanguageModel, index: 0, required: true }], [{ type: NodeConnectionTypes.AiLanguageModel, index: 0, required: true }],
'272px', '224px',
], ],
[ [
'2 required, 1 optional', '2 required, 1 optional',
@@ -218,7 +218,7 @@ describe('CanvasNodeDefault', () => {
{ type: NodeConnectionTypes.AiDocument, index: 0, required: true }, { type: NodeConnectionTypes.AiDocument, index: 0, required: true },
{ type: NodeConnectionTypes.AiMemory, index: 0, required: true }, { type: NodeConnectionTypes.AiMemory, index: 0, required: true },
], ],
'272px', '224px',
], ],
[ [
'2 required, 2 optional', '2 required, 2 optional',
@@ -228,7 +228,7 @@ describe('CanvasNodeDefault', () => {
{ type: NodeConnectionTypes.AiDocument, index: 0, required: true }, { type: NodeConnectionTypes.AiDocument, index: 0, required: true },
{ type: NodeConnectionTypes.AiMemory, index: 0, required: true }, { type: NodeConnectionTypes.AiMemory, index: 0, required: true },
], ],
'272px', '224px',
], ],
[ [
'1 required, 4 optional', '1 required, 4 optional',
@@ -239,7 +239,7 @@ describe('CanvasNodeDefault', () => {
{ type: NodeConnectionTypes.AiMemory, index: 0 }, { type: NodeConnectionTypes.AiMemory, index: 0 },
{ type: NodeConnectionTypes.AiMemory, index: 0 }, { type: NodeConnectionTypes.AiMemory, index: 0 },
], ],
'336px', '272px',
], ],
])( ])(
'should adjust width css variable based on the number of non-main inputs (%s)', 'should adjust width css variable based on the number of non-main inputs (%s)',

View File

@@ -4,7 +4,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
<div <div
class="node configurable" class="node configurable"
data-test-id="canvas-configurable-node" data-test-id="canvas-configurable-node"
style="--canvas-node--width: 272px; --canvas-node--height: 96px; --node-icon-size: 40px;" style="--canvas-node--width: 224px; --canvas-node--height: 96px; --node-icon-size: 40px;"
> >
<!--v-if--> <!--v-if-->
<div <div
@@ -57,7 +57,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
<div <div
class="node configurable configuration" class="node configurable configuration"
data-test-id="canvas-configurable-node" data-test-id="canvas-configurable-node"
style="--canvas-node--width: 272px; --canvas-node--height: 80px; --node-icon-size: 30px;" style="--canvas-node--width: 240px; --canvas-node--height: 80px; --node-icon-size: 30px;"
> >
<!--v-if--> <!--v-if-->
<div <div

View File

@@ -483,21 +483,21 @@ describe('calculateNodeSize', () => {
const nonMainInputCount = 5; const nonMainInputCount = 5;
const mainInputCount = 3; const mainInputCount = 3;
const mainOutputCount = 2; const mainOutputCount = 2;
// width = max(4, 5) * 2 * 16 * 2 = 5 * 2 * 16 * 2 + offset = 336 // width = 80 + 0 + (max(4, 5) - 1) * 16 * 3 = 272
// height = DEFAULT_NODE_SIZE[1] + max(0, max(3,2,1) - 2) * 16 * 2 // height = DEFAULT_NODE_SIZE[1] + max(0, max(3,2,1) - 2) * 16 * 2
// maxVerticalHandles = 3 // maxVerticalHandles = 3
// height = 96 + (3 - 2) * 32 = 96 + 32 = 128 // height = 96 + (3 - 2) * 32 = 96 + 32 = 128
expect( expect(
calculateNodeSize(false, true, mainInputCount, mainOutputCount, nonMainInputCount), calculateNodeSize(false, true, mainInputCount, mainOutputCount, nonMainInputCount),
).toEqual({ width: 336, height: 128 }); ).toEqual({ width: 272, height: 128 });
}); });
it('should return configurable configuration node size when both isConfigurable and isConfiguration are true', () => { it('should return configurable configuration node size when both isConfigurable and isConfiguration are true', () => {
const nonMainInputCount = 2; const nonMainInputCount = 2;
// width = max(4, 2) * 2 * 16 * 2 = 4 * 2 * 16 * 2 + offset = 272 // width = 80 + 16 + (max(4, 2) - 1) * 16 * 3 = 240
// height = CONFIGURATION_NODE_SIZE[1] = 16 * 5 = 80 // height = CONFIGURATION_NODE_SIZE[1] = 16 * 5 = 80
expect(calculateNodeSize(true, true, 1, 1, nonMainInputCount)).toEqual({ expect(calculateNodeSize(true, true, 1, 1, nonMainInputCount)).toEqual({
width: 272, width: 240,
height: 80, height: 80,
}); });
}); });
@@ -524,12 +524,12 @@ describe('calculateNodeSize', () => {
it('should respect the minimum width for configurable nodes', () => { it('should respect the minimum width for configurable nodes', () => {
const nonMainInputCount = 2; // less than NODE_MIN_INPUT_ITEMS_COUNT const nonMainInputCount = 2; // less than NODE_MIN_INPUT_ITEMS_COUNT
// width = 4 * 2 * 16 * 2 + offset = 272 // width = 80 + 0 + (max(2, 4) - 1) * 16 * 3 = 224
// height = default path, mainInputCount = 1, mainOutputCount = 1 // height = default path, mainInputCount = 1, mainOutputCount = 1
// maxVerticalHandles = 1 // maxVerticalHandles = 1
// height = 96 + (1 - 2) * 32 = 96 + 0 = 96 // height = 96 + (1 - 2) * 32 = 96 + 0 = 96
expect(calculateNodeSize(false, true, 1, 1, nonMainInputCount)).toEqual({ expect(calculateNodeSize(false, true, 1, 1, nonMainInputCount)).toEqual({
width: 272, width: 224,
height: 96, height: 96,
}); });
}); });

View File

@@ -35,7 +35,11 @@ import {
export const GRID_SIZE = 16; export const GRID_SIZE = 16;
export const DEFAULT_NODE_SIZE: [number, number] = [GRID_SIZE * 6, GRID_SIZE * 6]; export const DEFAULT_NODE_SIZE: [number, number] = [GRID_SIZE * 6, GRID_SIZE * 6];
export const CONFIGURATION_NODE_SIZE: [number, number] = [GRID_SIZE * 5, GRID_SIZE * 5]; export const CONFIGURATION_NODE_RADIUS = (GRID_SIZE * 5) / 2;
export const CONFIGURATION_NODE_SIZE: [number, number] = [
CONFIGURATION_NODE_RADIUS * 2,
CONFIGURATION_NODE_RADIUS * 2,
]; // the node has circle shape
export const CONFIGURABLE_NODE_SIZE: [number, number] = [GRID_SIZE * 16, GRID_SIZE * 6]; export const CONFIGURABLE_NODE_SIZE: [number, number] = [GRID_SIZE * 16, GRID_SIZE * 6];
export const DEFAULT_START_POSITION_X = GRID_SIZE * 11; export const DEFAULT_START_POSITION_X = GRID_SIZE * 11;
export const DEFAULT_START_POSITION_Y = GRID_SIZE * 15; export const DEFAULT_START_POSITION_Y = GRID_SIZE * 15;
@@ -48,10 +52,6 @@ export const DEFAULT_VIEWPORT_BOUNDARIES: ViewportBoundaries = {
yMax: Infinity, yMax: Infinity,
}; };
// The top-center of the configuration node is not a multiple of GRID_SIZE,
// therefore we need to offset non-main inputs to align with the nodes top-center
export const CONFIGURATION_NODE_OFFSET = (CONFIGURATION_NODE_SIZE[0] / 2) % GRID_SIZE;
/** /**
* Utility functions for returning nodes found at the edges of a group * Utility functions for returning nodes found at the edges of a group
*/ */
@@ -619,10 +619,13 @@ export function calculateNodeSize(
const height = DEFAULT_NODE_SIZE[1] + Math.max(0, maxVerticalHandles - 2) * GRID_SIZE * 2; const height = DEFAULT_NODE_SIZE[1] + Math.max(0, maxVerticalHandles - 2) * GRID_SIZE * 2;
if (isConfigurable) { if (isConfigurable) {
const portCount = Math.max(NODE_MIN_INPUT_ITEMS_COUNT, nonMainInputCount);
return { return {
// Configuration node has extra width so that its centered port aligns to the grid
width: width:
Math.max(NODE_MIN_INPUT_ITEMS_COUNT, nonMainInputCount) * GRID_SIZE * 4 + CONFIGURATION_NODE_RADIUS * 2 +
CONFIGURATION_NODE_OFFSET * 2, GRID_SIZE * ((isConfiguration ? 1 : 0) + (portCount - 1) * 3),
height: isConfiguration ? CONFIGURATION_NODE_SIZE[1] : height, height: isConfiguration ? CONFIGURATION_NODE_SIZE[1] : height,
}; };
} }