From 3ea51c11cb264fe5be036d81c068633e933f9b26 Mon Sep 17 00:00:00 2001 From: Suguru Inoue Date: Fri, 20 Jun 2025 11:52:53 +0200 Subject: [PATCH] fix(editor): Make adjustments to status icon and connector port position in canvas (#16469) --- .../canvas/elements/nodes/CanvasNode.test.ts | 6 +- .../canvas/elements/nodes/CanvasNode.vue | 7 +- .../render-types/CanvasNodeDefault.test.ts | 189 +++++++++--------- .../nodes/render-types/CanvasNodeDefault.vue | 129 +++++------- .../CanvasNodeDefault.test.ts.snap | 30 +-- packages/frontend/editor-ui/src/constants.ts | 3 +- .../editor-ui/src/utils/canvasUtils.test.ts | 52 +++-- .../editor-ui/src/utils/canvasUtils.ts | 14 +- .../editor-ui/src/utils/nodeViewUtils.ts | 39 +++- 9 files changed, 234 insertions(+), 235 deletions(-) diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.test.ts b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.test.ts index b11cb96975..68614d1b31 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.test.ts +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.test.ts @@ -94,6 +94,7 @@ describe('CanvasNode', () => { inputs: [ { type: NodeConnectionTypes.Main, index: 0 }, { type: NodeConnectionTypes.AiAgent, index: 0, required: true }, + { type: NodeConnectionTypes.AiMemory, index: 0 }, { type: NodeConnectionTypes.AiTool, index: 0 }, ], outputs: [], @@ -109,8 +110,9 @@ describe('CanvasNode', () => { const inputHandles = getAllByTestId('canvas-node-input-handle'); - expect(inputHandles[1]).toHaveStyle('left: 20%'); - expect(inputHandles[2]).toHaveStyle('left: 80%'); + expect(inputHandles[1]).toHaveStyle('left: 40px'); + expect(inputHandles[2]).toHaveStyle('left: 160px'); + expect(inputHandles[3]).toHaveStyle('left: 200px'); }); }); diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue index 32d2dc75cb..69723dd082 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue @@ -34,6 +34,7 @@ import type { EventBus } from '@n8n/utils/event-bus'; import { createEventBus } from '@n8n/utils/event-bus'; import isEqual from 'lodash/isEqual'; import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue'; +import { GRID_SIZE } from '@/utils/nodeViewUtils'; type Props = NodeProps & { readOnly?: boolean; @@ -183,6 +184,10 @@ const createEndpointMappingFn = connectingHandle.value?.nodeId === props.id && connectingHandle.value?.handleType === handleType && connectingHandle.value?.handleId === handleId; + const offsetValue = + position === Position.Bottom + ? `${GRID_SIZE * (2 + index * 2)}px` + : `${(100 / (endpoints.length + 1)) * (index + 1)}%`; return { ...endpoint, @@ -191,7 +196,7 @@ const createEndpointMappingFn = isConnecting, position, offset: { - [offsetAxis]: `${(100 / (endpoints.length + 1)) * (index + 1)}%`, + [offsetAxis]: offsetValue, }, }; }; diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.test.ts b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.test.ts index a26a8cfe57..d845b9917d 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.test.ts +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.test.ts @@ -33,84 +33,41 @@ describe('CanvasNodeDefault', () => { expect(getByTestId('canvas-default-node')).toMatchSnapshot(); }); - describe('inputs', () => { - it('should adjust height css variable based on the number of inputs (1 input)', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - inputs: [{ type: NodeConnectionTypes.Main, index: 0 }], - }, - }), + describe('inputs and outputs', () => { + it.each([ + [1, 1, '100px'], + [3, 1, '100px'], + [4, 1, '140px'], + [1, 1, '100px'], + [1, 3, '100px'], + [1, 4, '140px'], + [4, 4, '140px'], + ])( + 'should adjust height css variable based on the number of inputs and outputs (%i inputs, %i outputs)', + (inputCount, outputCount, expected) => { + const { getByText } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + inputs: Array.from({ length: inputCount }).map(() => ({ + type: NodeConnectionTypes.Main, + index: 0, + })), + outputs: Array.from({ length: outputCount }).map(() => ({ + type: NodeConnectionTypes.Main, + index: 0, + })), + }, + }), + }, }, - }, - }); + }); - const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--canvas-node--main-input-count': '1' }); // height calculation based on the number of inputs - }); - - it('should adjust height css variable based on the number of inputs (multiple inputs)', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - inputs: [ - { type: NodeConnectionTypes.Main, index: 0 }, - { type: NodeConnectionTypes.Main, index: 0 }, - { type: NodeConnectionTypes.Main, index: 0 }, - ], - }, - }), - }, - }, - }); - - const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--canvas-node--main-input-count': '3' }); // height calculation based on the number of inputs - }); - }); - - describe('outputs', () => { - it('should adjust height css variable based on the number of outputs (1 output)', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - outputs: [{ type: NodeConnectionTypes.Main, index: 0 }], - }, - }), - }, - }, - }); - - const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--canvas-node--main-output-count': '1' }); // height calculation based on the number of outputs - }); - - it('should adjust height css variable based on the number of outputs (multiple outputs)', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - outputs: [ - { type: NodeConnectionTypes.Main, index: 0 }, - { type: NodeConnectionTypes.Main, index: 0 }, - { type: NodeConnectionTypes.Main, index: 0 }, - ], - }, - }), - }, - }, - }); - - const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--canvas-node--main-output-count': '3' }); // height calculation based on the number of outputs - }); + const nodeElement = getByText('Test Node').closest('.node'); + expect(nodeElement).toHaveStyle({ '--canvas-node--height': expected }); + }, + ); }); describe('selected', () => { @@ -244,33 +201,67 @@ describe('CanvasNodeDefault', () => { }); describe('inputs', () => { - it('should adjust width css variable based on the number of non-main inputs', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - inputs: [ - { type: NodeConnectionTypes.Main, index: 0 }, - { type: NodeConnectionTypes.AiTool, index: 0 }, - { type: NodeConnectionTypes.AiDocument, index: 0, required: true }, - { type: NodeConnectionTypes.AiMemory, index: 0, required: true }, - ], - render: { - type: CanvasNodeRenderType.Default, - options: { - configurable: true, + it.each([ + [ + '1 required', + [{ type: NodeConnectionTypes.AiLanguageModel, index: 0, required: true }], + '240px', + ], + [ + '2 required, 1 optional', + [ + { type: NodeConnectionTypes.AiTool, index: 0 }, + { type: NodeConnectionTypes.AiDocument, index: 0, required: true }, + { type: NodeConnectionTypes.AiMemory, index: 0, required: true }, + ], + '240px', + ], + [ + '2 required, 2 optional', + [ + { type: NodeConnectionTypes.AiTool, index: 0 }, + { type: NodeConnectionTypes.AiLanguageModel, index: 0 }, + { type: NodeConnectionTypes.AiDocument, index: 0, required: true }, + { type: NodeConnectionTypes.AiMemory, index: 0, required: true }, + ], + '240px', + ], + [ + '1 required, 4 optional', + [ + { type: NodeConnectionTypes.AiLanguageModel, index: 0, required: true }, + { type: NodeConnectionTypes.AiTool, index: 0 }, + { type: NodeConnectionTypes.AiDocument, index: 0 }, + { type: NodeConnectionTypes.AiMemory, index: 0 }, + { type: NodeConnectionTypes.AiMemory, index: 0 }, + ], + '280px', + ], + ])( + 'should adjust width css variable based on the number of non-main inputs (%s)', + (_, nonMainInputs, expected) => { + const { getByText } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + inputs: [{ type: NodeConnectionTypes.Main, index: 0 }, ...nonMainInputs], + render: { + type: CanvasNodeRenderType.Default, + options: { + configurable: true, + }, }, }, - }, - }), + }), + }, }, - }, - }); + }); - const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--configurable-node--input-count': '3' }); - }); + const nodeElement = getByText('Test Node').closest('.node'); + expect(nodeElement).toHaveStyle({ '--canvas-node--width': expected }); + }, + ); }); }); diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue index c0e4b24971..5fdcd874d4 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue @@ -3,10 +3,10 @@ import { computed, ref, useCssModule, watch } from 'vue'; import { useNodeConnections } from '@/composables/useNodeConnections'; import { useI18n } from '@n8n/i18n'; import { useCanvasNode } from '@/composables/useCanvasNode'; -import { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants'; import type { CanvasNodeDefaultRender } from '@/types'; import { useCanvas } from '@/composables/useCanvas'; import { useNodeSettingsInCanvas } from '@/components/canvas/composables/useNodeSettingsInCanvas'; +import { calculateNodeSize } from '@/utils/nodeViewUtils'; import ExperimentalCanvasNodeSettings from '../../../components/ExperimentalCanvasNodeSettings.vue'; const $style = useCssModule(); @@ -36,18 +36,12 @@ const { hasIssues, render, } = useCanvasNode(); -const { - mainOutputs, - mainOutputConnections, - mainInputs, - mainInputConnections, - nonMainInputs, - requiredNonMainInputs, -} = useNodeConnections({ - inputs, - outputs, - connections, -}); +const { mainOutputs, mainOutputConnections, mainInputs, mainInputConnections, nonMainInputs } = + useNodeConnections({ + inputs, + outputs, + connections, + }); const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']); @@ -71,29 +65,24 @@ const classes = computed(() => { }; }); -const styles = computed(() => { - const stylesObject: Record = {}; +const iconSize = computed(() => (renderOptions.value.configuration ? 30 : 40)); - if (renderOptions.value.configurable) { - let spacerCount = 0; - if (NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS && requiredNonMainInputs.value.length > 0) { - const requiredNonMainInputsCount = requiredNonMainInputs.value.length; - const optionalNonMainInputsCount = nonMainInputs.value.length - requiredNonMainInputsCount; - spacerCount = requiredNonMainInputsCount > 0 && optionalNonMainInputsCount > 0 ? 1 : 0; - } +const nodeSize = computed(() => + calculateNodeSize( + renderOptions.value.configuration ?? false, + renderOptions.value.configurable ?? false, + mainInputs.value.length, + mainOutputs.value.length, + nonMainInputs.value.length, + ), +); - stylesObject['--configurable-node--input-count'] = nonMainInputs.value.length + spacerCount; - } - - if (nodeSettingsZoom.value !== undefined) { - stylesObject['--zoom'] = nodeSettingsZoom.value; - } - - stylesObject['--canvas-node--main-input-count'] = mainInputs.value.length; - stylesObject['--canvas-node--main-output-count'] = mainOutputs.value.length; - - return stylesObject; -}); +const styles = computed(() => ({ + '--canvas-node--width': `${nodeSize.value.width}px`, + '--canvas-node--height': `${nodeSize.value.height}px`, + '--node-icon-size': `${iconSize.value}px`, + ...(nodeSettingsZoom.value === undefined ? {} : { '--zoom': nodeSettingsZoom.value }), +})); const dataTestId = computed(() => { let type = 'default'; @@ -117,8 +106,6 @@ const isStrikethroughVisible = computed(() => { return isDisabled.value && isSingleMainInputNode && isSingleMainOutputNode; }); -const iconSize = computed(() => (renderOptions.value.configuration ? 30 : 40)); - const iconSource = computed(() => renderOptions.value.icon); const showTooltip = ref(false); @@ -156,8 +143,13 @@ function onActivate(event: MouseEvent) { diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/__snapshots__/CanvasNodeDefault.test.ts.snap b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/__snapshots__/CanvasNodeDefault.test.ts.snap index 7607434296..22c7f3ffb4 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/__snapshots__/CanvasNodeDefault.test.ts.snap +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/__snapshots__/CanvasNodeDefault.test.ts.snap @@ -4,12 +4,12 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
configurable > should render configurable node corr
-
@@ -43,6 +42,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr Test Node Subtitle
+ `; @@ -51,12 +51,12 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
configuration > should render configurable configur
-
@@ -90,6 +89,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur Test Node Subtitle
+ `; @@ -98,12 +98,12 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
configuration > should render configuration node co
-
@@ -137,6 +136,7 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co Test Node Subtitle
+ `; @@ -145,12 +145,12 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
should render node correctly 1`] = `
-
@@ -184,6 +183,7 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = ` Test Node Subtitle
+ `; @@ -192,12 +192,12 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
trigger > should render trigger node correctly 1`]
-
@@ -231,6 +230,7 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`] Test Node Subtitle
+ `; diff --git a/packages/frontend/editor-ui/src/constants.ts b/packages/frontend/editor-ui/src/constants.ts index c2b66de70f..4b0dbd2917 100644 --- a/packages/frontend/editor-ui/src/constants.ts +++ b/packages/frontend/editor-ui/src/constants.ts @@ -115,8 +115,7 @@ export const EXPRESSIONS_DOCS_URL = `https://${DOCS_DOMAIN}/code-examples/expres export const N8N_PRICING_PAGE_URL = 'https://n8n.io/pricing'; export const N8N_MAIN_GITHUB_REPO_URL = 'https://github.com/n8n-io/n8n'; -export const NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS = false; -export const NODE_MIN_INPUT_ITEMS_COUNT = 4; +export const NODE_MIN_INPUT_ITEMS_COUNT = 5; // node types export const BAMBOO_HR_NODE_TYPE = 'n8n-nodes-base.bambooHr'; diff --git a/packages/frontend/editor-ui/src/utils/canvasUtils.test.ts b/packages/frontend/editor-ui/src/utils/canvasUtils.test.ts index 53bef9bdee..7cf5a27535 100644 --- a/packages/frontend/editor-ui/src/utils/canvasUtils.test.ts +++ b/packages/frontend/editor-ui/src/utils/canvasUtils.test.ts @@ -15,6 +15,7 @@ import { CanvasConnectionMode } from '@/types'; import type { INodeUi } from '@/Interface'; import type { Connection } from '@vue-flow/core'; import { createTestNode } from '@/__tests__/mocks'; +import { NODE_MIN_INPUT_ITEMS_COUNT } from '@/constants'; vi.mock('uuid', () => ({ v4: vi.fn(() => 'mock-uuid'), @@ -990,52 +991,56 @@ describe('insertSpacersBetweenEndpoints', () => { it('should insert spacers when there are less than min endpoints count', () => { const endpoints = [{ index: 0, required: true }]; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); - expect(result).toEqual([{ index: 0, required: true }, null, null, null]); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); + expect(result).toEqual([{ index: 0, required: true }, null, null, null, null]); }); it('should not insert spacers when there are at least min endpoints count', () => { - const endpoints = [{ index: 0, required: true }, { index: 1 }, { index: 2 }, { index: 3 }]; + const endpoints = [ + { index: 0, required: true }, + { index: 1 }, + { index: 2 }, + { index: 3 }, + { index: 4 }, + ]; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); expect(result).toEqual(endpoints); }); it('should handle zero required endpoints', () => { const endpoints = [{ index: 0, required: false }]; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); - expect(result).toEqual([null, null, null, { index: 0, required: false }]); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); + expect(result).toEqual([null, null, null, null, { index: 0, required: false }]); }); it('should handle no endpoints', () => { const endpoints: Array<{ index: number; required: boolean }> = []; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); - expect(result).toEqual([null, null, null, null]); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); + expect(result).toEqual([null, null, null, null, null]); }); - it('should handle required endpoints greater than min endpoints count', () => { - const endpoints = [ - { index: 0, required: true }, - { index: 1, required: true }, - { index: 2, required: true }, - { index: 3, required: true }, - { index: 4, required: true }, - ]; + it('should handle required endpoints greater than NODE_MIN_INPUT_ITEMS_COUNT', () => { + const endpoints = Array.from({ length: NODE_MIN_INPUT_ITEMS_COUNT + 1 }).map((_, index) => ({ + index, + required: true, + })); const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); expect(result).toEqual(endpoints); }); it('should insert spacers between required and optional endpoints', () => { const endpoints = [{ index: 0, required: true }, { index: 1, required: true }, { index: 2 }]; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); expect(result).toEqual([ { index: 0, required: true }, { index: 1, required: true }, null, + null, { index: 2 }, ]); }); @@ -1043,14 +1048,7 @@ describe('insertSpacersBetweenEndpoints', () => { it('should handle required endpoints count greater than endpoints length', () => { const endpoints = [{ index: 0, required: true }]; const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 4); - expect(result).toEqual([{ index: 0, required: true }, null, null, null]); - }); - - it('should handle min endpoints count less than required endpoints count', () => { - const endpoints = [{ index: 0, required: false }]; - const requiredEndpointsCount = endpoints.filter((endpoint) => endpoint.required).length; - const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount, 0); - expect(result).toEqual([{ index: 0, required: false }]); + const result = insertSpacersBetweenEndpoints(endpoints, requiredEndpointsCount); + expect(result).toEqual([{ index: 0, required: true }, null, null, null, null]); }); }); diff --git a/packages/frontend/editor-ui/src/utils/canvasUtils.ts b/packages/frontend/editor-ui/src/utils/canvasUtils.ts index 159250a004..16b517afec 100644 --- a/packages/frontend/editor-ui/src/utils/canvasUtils.ts +++ b/packages/frontend/editor-ui/src/utils/canvasUtils.ts @@ -10,6 +10,7 @@ import { CanvasConnectionMode } from '@/types'; import type { Connection } from '@vue-flow/core'; import { isValidCanvasConnectionMode, isValidNodeConnectionType } from '@/utils/typeGuards'; import { NodeConnectionTypes } from 'n8n-workflow'; +import { NODE_MIN_INPUT_ITEMS_COUNT } from '@/constants'; /** * Maps multiple legacy n8n connections to VueFlow connections @@ -246,18 +247,15 @@ export function checkOverlap(node1: BoundingBox, node2: BoundingBox) { /** * Inserts spacers between endpoints to visually separate them */ -export function insertSpacersBetweenEndpoints( - endpoints: T[], - requiredEndpointsCount = 0, - minEndpointsCount = 4, -) { +export function insertSpacersBetweenEndpoints(endpoints: T[], requiredEndpointsCount = 0) { const endpointsWithSpacers: Array = [...endpoints]; const optionalNonMainInputsCount = endpointsWithSpacers.length - requiredEndpointsCount; - const spacerCount = minEndpointsCount - requiredEndpointsCount - optionalNonMainInputsCount; + const spacerCount = + NODE_MIN_INPUT_ITEMS_COUNT - requiredEndpointsCount - optionalNonMainInputsCount; // Insert `null` in between required non-main inputs and non-required non-main inputs - // to separate them visually if there are less than 4 inputs in total - if (endpointsWithSpacers.length < minEndpointsCount) { + // to separate them visually if there are less than `minEndpointsCount` inputs in total + if (endpointsWithSpacers.length < NODE_MIN_INPUT_ITEMS_COUNT) { for (let i = 0; i < spacerCount; i++) { endpointsWithSpacers.splice(requiredEndpointsCount + i, 0, null); } diff --git a/packages/frontend/editor-ui/src/utils/nodeViewUtils.ts b/packages/frontend/editor-ui/src/utils/nodeViewUtils.ts index d8c4405804..27b659247b 100644 --- a/packages/frontend/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/frontend/editor-ui/src/utils/nodeViewUtils.ts @@ -2,6 +2,7 @@ import { AI_MCP_TOOL_NODE_TYPE, LIST_LIKE_NODE_OPERATIONS, MAIN_HEADER_TABS, + NODE_MIN_INPUT_ITEMS_COUNT, NODE_POSITION_CONFLICT_ALLOWLIST, SET_NODE_TYPE, SPLIT_IN_BATCHES_NODE_TYPE, @@ -33,14 +34,14 @@ import { export const GRID_SIZE = 20; -export const NODE_SIZE = 100; -export const DEFAULT_NODE_SIZE: [number, number] = [100, 100]; -export const CONFIGURATION_NODE_SIZE: [number, number] = [80, 80]; -export const CONFIGURABLE_NODE_SIZE: [number, number] = [256, 100]; -export const DEFAULT_START_POSITION_X = 180; -export const DEFAULT_START_POSITION_Y = 240; +export const NODE_SIZE = GRID_SIZE * 5; +export const DEFAULT_NODE_SIZE: [number, number] = [GRID_SIZE * 5, GRID_SIZE * 5]; +export const CONFIGURATION_NODE_SIZE: [number, number] = [GRID_SIZE * 4, GRID_SIZE * 4]; +export const CONFIGURABLE_NODE_SIZE: [number, number] = [GRID_SIZE * 12, GRID_SIZE * 5]; +export const DEFAULT_START_POSITION_X = GRID_SIZE * 9; +export const DEFAULT_START_POSITION_Y = GRID_SIZE * 12; export const HEADER_HEIGHT = 65; -export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300; +export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = GRID_SIZE * 15; export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE; export const DEFAULT_VIEWPORT_BOUNDARIES: ViewportBoundaries = { xMin: -Infinity, @@ -600,3 +601,27 @@ export function updateViewportToContainNodes( zoom, }; } + +export function calculateNodeSize( + isConfiguration: boolean, + isConfigurable: boolean, + mainInputCount: number, + mainOutputCount: number, + nonMainInputCount: number, +): { width: number; height: number } { + const maxVerticalHandles = Math.max(mainInputCount, mainOutputCount, 1); + const height = 100 + Math.max(0, maxVerticalHandles - 3) * GRID_SIZE * 2; + + if (isConfigurable) { + return { + width: (Math.max(NODE_MIN_INPUT_ITEMS_COUNT - 1, nonMainInputCount) * 2 + 4) * GRID_SIZE, + height: isConfiguration ? 75 : height, + }; + } + + if (isConfiguration) { + return { width: GRID_SIZE * 4, height: GRID_SIZE * 4 }; + } + + return { width: 100, height }; +}