mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor): Improve canvas node insertion position (#14289)
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
import type { RouteLocation } from 'vue-router';
|
||||
import type { ViewportBoundaries } from '@/types';
|
||||
|
||||
/*
|
||||
* Canvas constants and functions
|
||||
@@ -26,18 +27,25 @@ import type { RouteLocation } from 'vue-router';
|
||||
export const GRID_SIZE = 20;
|
||||
|
||||
export const NODE_SIZE = 100;
|
||||
export const DEFAULT_NODE_SIZE = [100, 100];
|
||||
export const CONFIGURATION_NODE_SIZE = [80, 80];
|
||||
export const CONFIGURABLE_NODE_SIZE = [256, 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 HEADER_HEIGHT = 65;
|
||||
export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300;
|
||||
export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE;
|
||||
export const DEFAULT_VIEWPORT_BOUNDARIES: ViewportBoundaries = {
|
||||
xMin: -Infinity,
|
||||
yMin: -Infinity,
|
||||
xMax: Infinity,
|
||||
yMax: Infinity,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the leftmost and topmost node from the given list of nodes
|
||||
* Utility functions for returning nodes found at the edges of a group
|
||||
*/
|
||||
|
||||
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]) {
|
||||
@@ -48,12 +56,68 @@ export const getLeftmostTopNode = <T extends { position: XYPosition }>(nodes: T[
|
||||
}, nodes[0]);
|
||||
};
|
||||
|
||||
export const getLeftMostNode = <T extends { position: XYPosition }>(nodes: T[]): T => {
|
||||
return nodes.reduce((leftmost, node) => {
|
||||
if (node.position[0] < leftmost.position[0]) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return leftmost;
|
||||
}, nodes[0]);
|
||||
};
|
||||
|
||||
export const getTopMostNode = <T extends { position: XYPosition }>(nodes: T[]): T => {
|
||||
return nodes.reduce((topmost, node) => {
|
||||
if (node.position[1] < topmost.position[1]) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return topmost;
|
||||
}, nodes[0]);
|
||||
};
|
||||
|
||||
export const getRightMostNode = <T extends { position: XYPosition }>(nodes: T[]): T => {
|
||||
return nodes.reduce((rightmost, node) => {
|
||||
if (node.position[0] > rightmost.position[0]) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return rightmost;
|
||||
}, nodes[0]);
|
||||
};
|
||||
|
||||
export const getBottomMostNode = <T extends { position: XYPosition }>(nodes: T[]): T => {
|
||||
return nodes.reduce((bottommost, node) => {
|
||||
if (node.position[1] > bottommost.position[1]) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return bottommost;
|
||||
}, nodes[0]);
|
||||
};
|
||||
|
||||
export const getNodesGroupSize = (nodes: INodeUi[]): [number, number] => {
|
||||
const leftMostNode = getLeftMostNode(nodes);
|
||||
const topMostNode = getTopMostNode(nodes);
|
||||
const rightMostNode = getRightMostNode(nodes);
|
||||
const bottomMostNode = getBottomMostNode(nodes);
|
||||
|
||||
const width = Math.abs(rightMostNode.position[0] - leftMostNode.position[0]) + NODE_SIZE;
|
||||
const height = Math.abs(bottomMostNode.position[1] - topMostNode.position[1]) + NODE_SIZE;
|
||||
|
||||
return [width, height];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const canUsePosition = (
|
||||
position1: XYPosition,
|
||||
position2: XYPosition,
|
||||
size: [number, number] = DEFAULT_NODE_SIZE,
|
||||
) => {
|
||||
if (Math.abs(position1[0] - position2[0]) <= size[0]) {
|
||||
if (Math.abs(position1[1] - position2[1]) <= size[1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -88,52 +152,77 @@ const closestNumberDivisibleBy = (inputNumber: number, divisibleBy: number): num
|
||||
*/
|
||||
export const getNewNodePosition = (
|
||||
nodes: INodeUi[],
|
||||
newPosition: XYPosition,
|
||||
movePosition?: XYPosition,
|
||||
initialPosition: XYPosition,
|
||||
{
|
||||
offset = [DEFAULT_NODE_SIZE[0] / 2, DEFAULT_NODE_SIZE[1] / 2],
|
||||
size = DEFAULT_NODE_SIZE,
|
||||
viewport = DEFAULT_VIEWPORT_BOUNDARIES,
|
||||
normalize = true,
|
||||
}: {
|
||||
offset?: XYPosition;
|
||||
size?: [number, number];
|
||||
viewport?: ViewportBoundaries;
|
||||
normalize?: boolean;
|
||||
} = {},
|
||||
): XYPosition => {
|
||||
const targetPosition: XYPosition = [...newPosition];
|
||||
const resolvedOffset = [...offset];
|
||||
resolvedOffset[0] = closestNumberDivisibleBy(resolvedOffset[0], GRID_SIZE);
|
||||
resolvedOffset[1] = closestNumberDivisibleBy(resolvedOffset[1], GRID_SIZE);
|
||||
|
||||
targetPosition[0] = closestNumberDivisibleBy(targetPosition[0], GRID_SIZE);
|
||||
targetPosition[1] = closestNumberDivisibleBy(targetPosition[1], GRID_SIZE);
|
||||
const resolvedPosition: XYPosition = [...initialPosition];
|
||||
resolvedPosition[0] = closestNumberDivisibleBy(resolvedPosition[0], GRID_SIZE);
|
||||
resolvedPosition[1] = closestNumberDivisibleBy(resolvedPosition[1], GRID_SIZE);
|
||||
|
||||
if (!movePosition) {
|
||||
movePosition = [40, 40];
|
||||
if (normalize) {
|
||||
let conflictFound = false;
|
||||
let i, node;
|
||||
do {
|
||||
conflictFound = false;
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
node = nodes[i];
|
||||
|
||||
if (!node || NODE_POSITION_CONFLICT_ALLOWLIST.includes(node.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!canUsePosition(node.position, resolvedPosition, size)) {
|
||||
conflictFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (conflictFound) {
|
||||
resolvedPosition[0] += resolvedOffset[0];
|
||||
resolvedPosition[1] += resolvedOffset[1];
|
||||
}
|
||||
} while (conflictFound);
|
||||
|
||||
if (resolvedPosition[0] < viewport.xMin + resolvedOffset[0]) {
|
||||
resolvedPosition[0] = viewport.xMin + resolvedOffset[0];
|
||||
}
|
||||
|
||||
if (resolvedPosition[1] < viewport.yMin + resolvedOffset[1]) {
|
||||
resolvedPosition[1] = viewport.yMin + resolvedOffset[1];
|
||||
}
|
||||
|
||||
if (resolvedPosition[0] > viewport.xMax - resolvedOffset[0]) {
|
||||
resolvedPosition[0] = viewport.xMax - size[0] - resolvedOffset[0];
|
||||
}
|
||||
|
||||
if (resolvedPosition[1] > viewport.yMax - resolvedOffset[1]) {
|
||||
resolvedPosition[1] = viewport.yMax - size[1] - resolvedOffset[1];
|
||||
}
|
||||
}
|
||||
|
||||
let conflictFound = false;
|
||||
let i, node;
|
||||
do {
|
||||
conflictFound = false;
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
node = nodes[i];
|
||||
|
||||
if (NODE_POSITION_CONFLICT_ALLOWLIST.includes(node.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!canUsePosition(node.position, targetPosition)) {
|
||||
conflictFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (conflictFound) {
|
||||
targetPosition[0] += movePosition[0];
|
||||
targetPosition[1] += movePosition[1];
|
||||
}
|
||||
} while (conflictFound);
|
||||
|
||||
return targetPosition;
|
||||
return resolvedPosition;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
// @ts-ignore
|
||||
const y = e.pageY !== undefined ? e.pageY : e.touches?.[0]?.pageY ? e.touches[0].pageY : 0;
|
||||
export const getMousePosition = (event: MouseEvent | TouchEvent): XYPosition => {
|
||||
const x = (event && 'clientX' in event ? event.clientX : event?.touches?.[0]?.clientX) ?? 0;
|
||||
const y = (event && 'clientY' in event ? event.clientY : event?.touches?.[0]?.clientY) ?? 0;
|
||||
|
||||
return [x, y];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user