mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
feat(editor): Improve canvas node insertion position (#14289)
This commit is contained in:
@@ -58,6 +58,7 @@ import type {
|
||||
CanvasConnectionPort,
|
||||
CanvasNode,
|
||||
CanvasNodeMoveEvent,
|
||||
ViewportBoundaries,
|
||||
} from '@/types';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
import {
|
||||
@@ -72,7 +73,9 @@ import {
|
||||
CONFIGURABLE_NODE_SIZE,
|
||||
CONFIGURATION_NODE_SIZE,
|
||||
DEFAULT_NODE_SIZE,
|
||||
DEFAULT_VIEWPORT_BOUNDARIES,
|
||||
generateOffsets,
|
||||
getNodesGroupSize,
|
||||
PUSH_NODES_OFFSET,
|
||||
} from '@/utils/nodeViewUtils';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
@@ -116,6 +119,7 @@ type AddNodesBaseOptions = {
|
||||
trackHistory?: boolean;
|
||||
keepPristine?: boolean;
|
||||
telemetry?: boolean;
|
||||
viewport?: ViewportBoundaries;
|
||||
};
|
||||
|
||||
type AddNodesOptions = AddNodesBaseOptions & {
|
||||
@@ -526,7 +530,10 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
);
|
||||
}
|
||||
|
||||
async function addNodes(nodes: AddedNodesAndConnections['nodes'], options: AddNodesOptions = {}) {
|
||||
async function addNodes(
|
||||
nodes: AddedNodesAndConnections['nodes'],
|
||||
{ viewport, ...options }: AddNodesOptions = {},
|
||||
) {
|
||||
let insertPosition = options.position;
|
||||
let lastAddedNode: INodeUi | undefined;
|
||||
const addedNodes: INodeUi[] = [];
|
||||
@@ -546,7 +553,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
for (const nodeAddData of nodesWithTypeVersion) {
|
||||
for (const [index, nodeAddData] of nodesWithTypeVersion.entries()) {
|
||||
const { isAutoAdd, openDetail: openNDV, ...node } = nodeAddData;
|
||||
const position = node.position ?? insertPosition;
|
||||
const nodeTypeDescription = requireNodeTypeDescription(node.type, node.typeVersion);
|
||||
@@ -560,6 +567,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
nodeTypeDescription,
|
||||
{
|
||||
...options,
|
||||
...(index === 0 ? { viewport } : {}),
|
||||
openNDV,
|
||||
isAutoAdd,
|
||||
},
|
||||
@@ -634,7 +642,9 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
): INodeUi {
|
||||
checkMaxNodesOfTypeReached(nodeTypeDescription);
|
||||
|
||||
const nodeData = resolveNodeData(node, nodeTypeDescription);
|
||||
const nodeData = resolveNodeData(node, nodeTypeDescription, {
|
||||
viewport: options.viewport,
|
||||
});
|
||||
if (!nodeData) {
|
||||
throw new Error(i18n.baseText('nodeViewV2.showError.failedToCreateNode'));
|
||||
}
|
||||
@@ -797,12 +807,15 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
function resolveNodeData(
|
||||
node: AddNodeDataWithTypeVersion,
|
||||
nodeTypeDescription: INodeTypeDescription,
|
||||
options: { viewport?: ViewportBoundaries } = {},
|
||||
) {
|
||||
const id = node.id ?? nodeHelpers.assignNodeId(node as INodeUi);
|
||||
const name = node.name ?? (nodeTypeDescription.defaults.name as string);
|
||||
const type = nodeTypeDescription.name;
|
||||
const typeVersion = node.typeVersion;
|
||||
const position = resolveNodePosition(node as INodeUi, nodeTypeDescription);
|
||||
const position = resolveNodePosition(node as INodeUi, nodeTypeDescription, {
|
||||
viewport: options.viewport,
|
||||
});
|
||||
const disabled = node.disabled ?? false;
|
||||
const parameters = node.parameters ?? {};
|
||||
|
||||
@@ -883,14 +896,8 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
function resolveNodePosition(
|
||||
node: Omit<INodeUi, 'position'> & { position?: INodeUi['position'] },
|
||||
nodeTypeDescription: INodeTypeDescription,
|
||||
options: { viewport?: ViewportBoundaries } = {},
|
||||
) {
|
||||
let position: XYPosition | undefined = node.position;
|
||||
let pushOffsets: XYPosition = [40, 40];
|
||||
|
||||
if (position) {
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position, pushOffsets);
|
||||
}
|
||||
|
||||
// Available when
|
||||
// - clicking the plus button of a node handle
|
||||
// - dragging an edge / connection of a node handle
|
||||
@@ -907,6 +914,17 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
|
||||
const nodeSize =
|
||||
connectionType === NodeConnectionTypes.Main ? DEFAULT_NODE_SIZE : CONFIGURATION_NODE_SIZE;
|
||||
let pushOffsets: XYPosition = [nodeSize[0] / 2, nodeSize[1] / 2];
|
||||
|
||||
let position: XYPosition | undefined = node.position;
|
||||
if (position) {
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position, {
|
||||
offset: pushOffsets,
|
||||
size: nodeSize,
|
||||
viewport: options.viewport,
|
||||
normalize: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (lastInteractedWithNode) {
|
||||
const lastInteractedWithNodeTypeDescription = nodeTypesStore.getNodeType(
|
||||
@@ -1056,7 +1074,11 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
}
|
||||
}
|
||||
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position, pushOffsets);
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position, {
|
||||
offset: pushOffsets,
|
||||
size: nodeSize,
|
||||
viewport: options.viewport,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveNodeName(node: INodeUi) {
|
||||
@@ -1503,7 +1525,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
|
||||
async function addImportedNodesToWorkflow(
|
||||
data: IWorkflowDataUpdate,
|
||||
{ trackBulk = true, trackHistory = false } = {},
|
||||
{ trackBulk = true, trackHistory = false, viewport = DEFAULT_VIEWPORT_BOUNDARIES } = {},
|
||||
): Promise<IWorkflowDataUpdate> {
|
||||
// Because nodes with the same name maybe already exist, it could
|
||||
// be needed that they have to be renamed. Also could it be possible
|
||||
@@ -1650,7 +1672,11 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
await addNodes(Object.values(tempWorkflow.nodes), { trackBulk: false, trackHistory });
|
||||
await addNodes(Object.values(tempWorkflow.nodes), {
|
||||
trackBulk: false,
|
||||
trackHistory,
|
||||
viewport,
|
||||
});
|
||||
await addConnections(
|
||||
mapLegacyConnectionsToCanvasConnections(
|
||||
tempWorkflow.connectionsBySourceNode,
|
||||
@@ -1674,8 +1700,17 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
async function importWorkflowData(
|
||||
workflowData: IWorkflowDataUpdate,
|
||||
source: string,
|
||||
importTags = true,
|
||||
{ trackBulk = true, trackHistory = true } = {},
|
||||
{
|
||||
importTags = true,
|
||||
trackBulk = true,
|
||||
trackHistory = true,
|
||||
viewport,
|
||||
}: {
|
||||
importTags?: boolean;
|
||||
trackBulk?: boolean;
|
||||
trackHistory?: boolean;
|
||||
viewport?: ViewportBoundaries;
|
||||
} = {},
|
||||
): Promise<IWorkflowDataUpdate> {
|
||||
uiStore.resetLastInteractedWith();
|
||||
|
||||
@@ -1767,10 +1802,19 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
// the user
|
||||
workflowHelpers.updateNodePositions(
|
||||
workflowData,
|
||||
NodeViewUtils.getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value),
|
||||
NodeViewUtils.getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value, {
|
||||
...(workflowData.nodes && workflowData.nodes.length > 1
|
||||
? { size: getNodesGroupSize(workflowData.nodes) }
|
||||
: {}),
|
||||
viewport,
|
||||
}),
|
||||
);
|
||||
|
||||
await addImportedNodesToWorkflow(workflowData, { trackBulk, trackHistory });
|
||||
await addImportedNodesToWorkflow(workflowData, {
|
||||
trackBulk,
|
||||
trackHistory,
|
||||
viewport,
|
||||
});
|
||||
|
||||
if (importTags && settingsStore.areTagsEnabled && Array.isArray(workflowData.tags)) {
|
||||
await importWorkflowTags(workflowData);
|
||||
@@ -1920,9 +1964,12 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
return filteredConnections;
|
||||
}
|
||||
|
||||
async function duplicateNodes(ids: string[]) {
|
||||
async function duplicateNodes(ids: string[], options: { viewport?: ViewportBoundaries } = {}) {
|
||||
const workflowData = deepCopy(getNodesToSave(workflowsStore.getNodesByIds(ids)));
|
||||
const result = await importWorkflowData(workflowData, 'duplicate', false);
|
||||
const result = await importWorkflowData(workflowData, 'duplicate', {
|
||||
viewport: options.viewport,
|
||||
importTags: false,
|
||||
});
|
||||
|
||||
return result.nodes?.map((node) => node.id).filter(isPresent) ?? [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user