mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor: Document and rename canvasUtils (no-changelog) (#13423)
This commit is contained in:
parent
2ef6f111d0
commit
dd43854a8e
|
@ -6,7 +6,7 @@ import { computed, onMounted, ref, useCssModule } from 'vue';
|
|||
import { getEdgeRenderData } from './utils';
|
||||
import { useCanvas } from '@/composables/useCanvas';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
|
||||
const props = defineProps<ConnectionLineProps>();
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import { useCanvas } from '@/composables/useCanvas';
|
|||
import {
|
||||
createCanvasConnectionHandleString,
|
||||
insertSpacersBetweenEndpoints,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
} from '@/utils/canvasUtils';
|
||||
import type { EventBus } from 'n8n-design-system';
|
||||
import { createEventBus } from 'n8n-design-system';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
|
|
@ -15,10 +15,7 @@ import {
|
|||
} from '@/__tests__/mocks';
|
||||
import { MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE, STICKY_NODE_TYPE, STORES } from '@/constants';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import {
|
||||
createCanvasConnectionHandleString,
|
||||
createCanvasConnectionId,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
import { createCanvasConnectionHandleString, createCanvasConnectionId } from '@/utils/canvasUtils';
|
||||
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
|
||||
import { MarkerType } from '@vue-flow/core';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
mapLegacyConnectionsToCanvasConnections,
|
||||
mapLegacyEndpointsToCanvasConnectionPort,
|
||||
parseCanvasConnectionHandleString,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
} from '@/utils/canvasUtils';
|
||||
import type {
|
||||
ExecutionStatus,
|
||||
ExecutionSummary,
|
||||
|
|
|
@ -48,7 +48,7 @@ import {
|
|||
} from '@/constants';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
import { useClipboard } from '@/composables/useClipboard';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
import { nextTick } from 'vue';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ import {
|
|||
mapLegacyConnectionsToCanvasConnections,
|
||||
mapLegacyConnectionToCanvasConnection,
|
||||
parseCanvasConnectionHandleString,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
} from '@/utils/canvasUtils';
|
||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
CONFIGURABLE_NODE_SIZE,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { NodeConnectionType } from 'n8n-workflow';
|
|||
import { useNodeConnections } from '@/composables/useNodeConnections';
|
||||
import type { CanvasNodeData } from '@/types';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
|
||||
describe('useNodeConnections', () => {
|
||||
const defaultConnections = {
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { MaybeRef } from 'vue';
|
|||
import { computed, unref } from 'vue';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
|
||||
export function useNodeConnections({
|
||||
inputs,
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
REGULAR_NODE_CREATOR_VIEW,
|
||||
} from '@/constants';
|
||||
import type { INodeCreateElement } from '@/Interface';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
|
||||
|
@ -56,7 +56,7 @@ vi.mock('@/stores/workflows.store', () => {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('@/utils/canvasUtilsV2', () => {
|
||||
vi.mock('@/utils/canvasUtils', () => {
|
||||
return {
|
||||
parseCanvasConnectionHandleString: vi.fn(),
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|||
import {
|
||||
createCanvasConnectionHandleString,
|
||||
parseCanvasConnectionHandleString,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
} from '@/utils/canvasUtils';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
import { isVueFlowConnection } from '@/utils/typeGuards';
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {
|
|||
} from '@/Interface';
|
||||
import { useSettingsStore } from './settings.store';
|
||||
import * as templatesApi from '@/api/templates';
|
||||
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
||||
import { getNodesWithNormalizedPosition } from '@/utils/nodeViewUtils';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useUsersStore } from './users.store';
|
||||
import { useWorkflowsStore } from './workflows.store';
|
||||
|
@ -399,7 +399,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
|||
): Promise<IWorkflowTemplate | undefined> => {
|
||||
const template = await getWorkflowTemplate(templateId);
|
||||
if (template?.workflow?.nodes) {
|
||||
template.workflow.nodes = getFixedNodesList(template.workflow.nodes) as INodeUi[];
|
||||
template.workflow.nodes = getNodesWithNormalizedPosition(
|
||||
template.workflow.nodes,
|
||||
) as INodeUi[];
|
||||
template.workflow.nodes?.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
delete node.credentials;
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
mapLegacyConnectionsToCanvasConnections,
|
||||
mapLegacyEndpointsToCanvasConnectionPort,
|
||||
parseCanvasConnectionHandleString,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
} from '@/utils/canvasUtils';
|
||||
import type { IConnection, IConnections, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type { CanvasConnection } from '@/types';
|
|
@ -6,6 +6,9 @@ import type { Connection } from '@vue-flow/core';
|
|||
import { isValidCanvasConnectionMode, isValidNodeConnectionType } from '@/utils/typeGuards';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Maps multiple legacy n8n connections to VueFlow connections
|
||||
*/
|
||||
export function mapLegacyConnectionsToCanvasConnections(
|
||||
legacyConnections: IConnections,
|
||||
nodes: INodeUi[],
|
||||
|
@ -75,6 +78,9 @@ export function mapLegacyConnectionsToCanvasConnections(
|
|||
return mappedConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a single legacy n8n connection to a VueFlow connection
|
||||
*/
|
||||
export function mapLegacyConnectionToCanvasConnection(
|
||||
sourceNode: INodeUi,
|
||||
targetNode: INodeUi,
|
||||
|
@ -101,6 +107,12 @@ export function mapLegacyConnectionToCanvasConnection(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a canvas connection handle string into its parts:
|
||||
* - mode
|
||||
* - type
|
||||
* - index
|
||||
*/
|
||||
export function parseCanvasConnectionHandleString(handle: string | null | undefined) {
|
||||
const [mode, type, index] = (handle ?? '').split('/');
|
||||
|
||||
|
@ -118,6 +130,9 @@ export function parseCanvasConnectionHandleString(handle: string | null | undefi
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a canvas connection handle string from its parts
|
||||
*/
|
||||
export function createCanvasConnectionHandleString({
|
||||
mode,
|
||||
type = NodeConnectionType.Main,
|
||||
|
@ -130,10 +145,16 @@ export function createCanvasConnectionHandleString({
|
|||
return `${mode}/${type}/${index}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a canvas connection ID from a connection
|
||||
*/
|
||||
export function createCanvasConnectionId(connection: Connection) {
|
||||
return `[${connection.source}/${connection.sourceHandle}][${connection.target}/${connection.targetHandle}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a VueFlow connection to a legacy n8n connection
|
||||
*/
|
||||
export function mapCanvasConnectionToLegacyConnection(
|
||||
sourceNode: INodeUi,
|
||||
targetNode: INodeUi,
|
||||
|
@ -165,6 +186,9 @@ export function mapCanvasConnectionToLegacyConnection(
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps legacy n8n node inputs to VueFlow connection handles
|
||||
*/
|
||||
export function mapLegacyEndpointsToCanvasConnectionPort(
|
||||
endpoints: INodeTypeDescription['inputs'],
|
||||
endpointNames: string[] = [],
|
||||
|
@ -196,6 +220,9 @@ export function mapLegacyEndpointsToCanvasConnectionPort(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two bounding boxes overlap
|
||||
*/
|
||||
export function checkOverlap(node1: BoundingBox, node2: BoundingBox) {
|
||||
return !(
|
||||
// node1 is completely to the left of node2
|
||||
|
@ -211,6 +238,9 @@ export function checkOverlap(node1: BoundingBox, node2: BoundingBox) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts spacers between endpoints to visually separate them
|
||||
*/
|
||||
export function insertSpacersBetweenEndpoints<T>(
|
||||
endpoints: T[],
|
||||
requiredEndpointsCount = 0,
|
|
@ -34,6 +34,9 @@ export const HEADER_HEIGHT = 65;
|
|||
export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300;
|
||||
export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE;
|
||||
|
||||
/**
|
||||
* Returns the leftmost and topmost node from the given list of nodes
|
||||
*/
|
||||
export const getLeftmostTopNode = <T extends { position: XYPosition }>(nodes: T[]): T => {
|
||||
return nodes.reduce((leftmostTop, node) => {
|
||||
if (node.position[0] > leftmostTop.position[0] || node.position[1] > leftmostTop.position[1]) {
|
||||
|
@ -44,6 +47,9 @@ export const getLeftmostTopNode = <T extends { position: XYPosition }>(nodes: T[
|
|||
}, nodes[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given position is available for a new node
|
||||
*/
|
||||
const canUsePosition = (position1: XYPosition, position2: XYPosition) => {
|
||||
if (Math.abs(position1[0] - position2[0]) <= 100) {
|
||||
if (Math.abs(position1[1] - position2[1]) <= 50) {
|
||||
|
@ -54,6 +60,9 @@ const canUsePosition = (position1: XYPosition, position2: XYPosition) => {
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the closest number divisible by the given number
|
||||
*/
|
||||
const closestNumberDivisibleBy = (inputNumber: number, divisibleBy: number): number => {
|
||||
const quotient = Math.ceil(inputNumber / divisibleBy);
|
||||
|
||||
|
@ -73,6 +82,9 @@ const closestNumberDivisibleBy = (inputNumber: number, divisibleBy: number): num
|
|||
return inputNumber2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the new position for a node based on the given position and the nodes in the workflow
|
||||
*/
|
||||
export const getNewNodePosition = (
|
||||
nodes: INodeUi[],
|
||||
newPosition: XYPosition,
|
||||
|
@ -113,6 +125,9 @@ export const getNewNodePosition = (
|
|||
return targetPosition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the position of a mouse or touch event
|
||||
*/
|
||||
export const getMousePosition = (e: MouseEvent | TouchEvent): XYPosition => {
|
||||
// @ts-ignore
|
||||
const x = e.pageX !== undefined ? e.pageX : e.touches?.[0]?.pageX ? e.touches[0].pageX : 0;
|
||||
|
@ -122,6 +137,9 @@ export const getMousePosition = (e: MouseEvent | TouchEvent): XYPosition => {
|
|||
return [x, y];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the relative position of a point on the canvas
|
||||
*/
|
||||
export const getRelativePosition = (
|
||||
x: number,
|
||||
y: number,
|
||||
|
@ -131,12 +149,9 @@ export const getRelativePosition = (
|
|||
return [(x - offset[0]) / scale, (y - offset[1]) / scale];
|
||||
};
|
||||
|
||||
export const getMidCanvasPosition = (scale: number, offset: XYPosition): XYPosition => {
|
||||
const { editorWidth, editorHeight } = getContentDimensions();
|
||||
|
||||
return getRelativePosition(editorWidth / 2, (editorHeight - HEADER_HEIGHT) / 2, scale, offset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the width and height of the node view content
|
||||
*/
|
||||
const getContentDimensions = (): { editorWidth: number; editorHeight: number } => {
|
||||
let contentWidth = window.innerWidth;
|
||||
let contentHeight = window.innerHeight;
|
||||
|
@ -153,7 +168,21 @@ const getContentDimensions = (): { editorWidth: number; editorHeight: number } =
|
|||
};
|
||||
};
|
||||
|
||||
export const getFixedNodesList = <T extends { position: XYPosition }>(workflowNodes: T[]): T[] => {
|
||||
/**
|
||||
* Returns the position of the canvas center
|
||||
*/
|
||||
export const getMidCanvasPosition = (scale: number, offset: XYPosition): XYPosition => {
|
||||
const { editorWidth, editorHeight } = getContentDimensions();
|
||||
|
||||
return getRelativePosition(editorWidth / 2, (editorHeight - HEADER_HEIGHT) / 2, scale, offset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize node positions based on the leftmost top node
|
||||
*/
|
||||
export const getNodesWithNormalizedPosition = <T extends { position: XYPosition }>(
|
||||
workflowNodes: T[],
|
||||
): T[] => {
|
||||
const nodes = [...workflowNodes];
|
||||
|
||||
if (nodes.length) {
|
||||
|
@ -229,6 +258,9 @@ export function isElementIntersection(
|
|||
return isWithinVerticalBounds && isWithinHorizontalBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node hints based on the node type and execution data
|
||||
*/
|
||||
export function getGenericHints({
|
||||
workflowNode,
|
||||
node,
|
||||
|
|
|
@ -9,7 +9,7 @@ import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, VIEWS } from '@/constants';
|
|||
import type { useRootStore } from '@/stores/root.store';
|
||||
import type { PosthogStore } from '@/stores/posthog.store';
|
||||
import type { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
||||
import { getNodesWithNormalizedPosition } from '@/utils/nodeViewUtils';
|
||||
import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms';
|
||||
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
|
||||
import { replaceAllTemplateNodeCredentials } from '@/utils/templates/templateTransforms';
|
||||
|
@ -43,7 +43,7 @@ export async function createWorkflowFromTemplate(opts: {
|
|||
template.workflow.nodes,
|
||||
credentialOverrides,
|
||||
);
|
||||
const nodes = getFixedNodesList(nodesWithCreds) as INodeUi[];
|
||||
const nodes = getNodesWithNormalizedPosition(nodesWithCreds) as INodeUi[];
|
||||
const connections = template.workflow.connections;
|
||||
|
||||
const workflowToCreate: IWorkflowData = {
|
||||
|
|
|
@ -95,7 +95,7 @@ import { sourceControlEventBus } from '@/event-bus/source-control';
|
|||
import { useTagsStore } from '@/stores/tags.store';
|
||||
import { usePushConnectionStore } from '@/stores/pushConnection.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { getFixedNodesList, getNodeViewTab } from '@/utils/nodeViewUtils';
|
||||
import { getNodesWithNormalizedPosition, getNodeViewTab } from '@/utils/nodeViewUtils';
|
||||
import CanvasStopCurrentExecutionButton from '@/components/canvas/elements/buttons/CanvasStopCurrentExecutionButton.vue';
|
||||
import CanvasStopWaitingForWebhookButton from '@/components/canvas/elements/buttons/CanvasStopWaitingForWebhookButton.vue';
|
||||
import CanvasClearExecutionDataButton from '@/components/canvas/elements/buttons/CanvasClearExecutionDataButton.vue';
|
||||
|
@ -108,7 +108,7 @@ import { useClipboard } from '@/composables/useClipboard';
|
|||
import { useBeforeUnload } from '@/composables/useBeforeUnload';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtils';
|
||||
import { isValidNodeConnectionType } from '@/utils/typeGuards';
|
||||
import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
|
||||
|
||||
|
@ -910,7 +910,7 @@ async function importWorkflowExact({ workflow: workflowData }: { workflow: IWork
|
|||
|
||||
initializeWorkspace({
|
||||
...workflowData,
|
||||
nodes: getFixedNodesList<INodeUi>(workflowData.nodes),
|
||||
nodes: getNodesWithNormalizedPosition<INodeUi>(workflowData.nodes),
|
||||
} as IWorkflowDb);
|
||||
|
||||
fitView();
|
||||
|
|
Loading…
Reference in a new issue