mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(editor): Overhaul node insert position computation in new canvas (no-changelog) (#10637)
This commit is contained in:
parent
e5aba60aff
commit
32ce65c1af
|
@ -1805,6 +1805,7 @@ export type ToggleNodeCreatorOptions = {
|
|||
createNodeActive: boolean;
|
||||
source?: NodeCreatorOpenSource;
|
||||
nodeCreatorView?: NodeFilterType;
|
||||
hasAddedNodes?: boolean;
|
||||
};
|
||||
|
||||
export type AppliedThemeOption = 'light' | 'dark';
|
||||
|
|
|
@ -49,18 +49,18 @@ export const mockNode = ({
|
|||
}) => mock<INodeUi>({ id, name, type, position, disabled, issues, typeVersion, parameters });
|
||||
|
||||
export const mockNodeTypeDescription = ({
|
||||
name,
|
||||
name = SET_NODE_TYPE,
|
||||
version = 1,
|
||||
credentials = [],
|
||||
inputs = [NodeConnectionType.Main],
|
||||
outputs = [NodeConnectionType.Main],
|
||||
}: {
|
||||
name: INodeTypeDescription['name'];
|
||||
name?: INodeTypeDescription['name'];
|
||||
version?: INodeTypeDescription['version'];
|
||||
credentials?: INodeTypeDescription['credentials'];
|
||||
inputs?: INodeTypeDescription['inputs'];
|
||||
outputs?: INodeTypeDescription['outputs'];
|
||||
}) =>
|
||||
} = {}) =>
|
||||
mock<INodeTypeDescription>({
|
||||
name,
|
||||
displayName: name,
|
||||
|
|
|
@ -23,7 +23,7 @@ const LazyNodeCreator = defineAsyncComponent(
|
|||
);
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
createNodeActive: false,
|
||||
createNodeActive: false, // Determines if the node creator is open
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -88,13 +88,15 @@ function addStickyNote() {
|
|||
emit('addNodes', getAddedNodesAndConnections([{ type: STICKY_NODE_TYPE, position }]));
|
||||
}
|
||||
|
||||
function closeNodeCreator() {
|
||||
emit('toggleNodeCreator', { createNodeActive: false });
|
||||
function closeNodeCreator(hasAddedNodes = false) {
|
||||
if (props.createNodeActive) {
|
||||
emit('toggleNodeCreator', { createNodeActive: false, hasAddedNodes });
|
||||
}
|
||||
}
|
||||
|
||||
function nodeTypeSelected(nodeTypes: string[]) {
|
||||
emit('addNodes', getAddedNodesAndConnections(nodeTypes.map((type) => ({ type }))));
|
||||
closeNodeCreator();
|
||||
closeNodeCreator(true);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -101,8 +101,8 @@ watch(
|
|||
);
|
||||
|
||||
// Close node creator when the last view stacks is closed
|
||||
watch(viewStacksLength, (viewStacksLength) => {
|
||||
if (viewStacksLength === 0) {
|
||||
watch(viewStacksLength, (value) => {
|
||||
if (value === 0) {
|
||||
emit('closeNodeCreator');
|
||||
setShowScrim(false);
|
||||
}
|
||||
|
|
|
@ -57,7 +57,11 @@ const emit = defineEmits<{
|
|||
'create:connection:start': [handle: ConnectStartEvent];
|
||||
'create:connection': [connection: Connection];
|
||||
'create:connection:end': [connection: Connection, event?: MouseEvent];
|
||||
'create:connection:cancelled': [handle: ConnectStartEvent, event?: MouseEvent];
|
||||
'create:connection:cancelled': [
|
||||
handle: ConnectStartEvent,
|
||||
position: XYPosition,
|
||||
event?: MouseEvent,
|
||||
];
|
||||
'click:connection:add': [connection: Connection];
|
||||
'click:pane': [position: XYPosition];
|
||||
'run:workflow': [];
|
||||
|
@ -227,7 +231,7 @@ function onConnectEnd(event?: MouseEvent) {
|
|||
if (connectedHandle.value) {
|
||||
emit('create:connection:end', connectedHandle.value, event);
|
||||
} else if (connectingHandle.value) {
|
||||
emit('create:connection:cancelled', connectingHandle.value, event);
|
||||
emit('create:connection:cancelled', connectingHandle.value, getProjectedPosition(event), event);
|
||||
}
|
||||
|
||||
connectedHandle.value = undefined;
|
||||
|
@ -291,14 +295,19 @@ function emitWithLastSelectedNode(emitFn: (id: string) => void) {
|
|||
const defaultZoom = 1;
|
||||
const zoom = ref(defaultZoom);
|
||||
|
||||
function onClickPane(event: MouseEvent) {
|
||||
function getProjectedPosition(event?: MouseEvent) {
|
||||
const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 };
|
||||
const position = project({
|
||||
x: event.offsetX - bounds.left,
|
||||
y: event.offsetY - bounds.top,
|
||||
});
|
||||
const offsetX = event?.clientX ?? 0;
|
||||
const offsetY = event?.clientY ?? 0;
|
||||
|
||||
emit('click:pane', position);
|
||||
return project({
|
||||
x: offsetX - bounds.left,
|
||||
y: offsetY - bounds.top,
|
||||
});
|
||||
}
|
||||
|
||||
function onClickPane(event: MouseEvent) {
|
||||
emit('click:pane', getProjectedPosition(event));
|
||||
}
|
||||
|
||||
async function onFitView() {
|
||||
|
|
|
@ -145,8 +145,8 @@ function openContextMenu(event: MouseEvent) {
|
|||
*/
|
||||
|
||||
&.configuration {
|
||||
--canvas-node--width: 76px;
|
||||
--canvas-node--height: 76px;
|
||||
--canvas-node--width: 80px;
|
||||
--canvas-node--height: 80px;
|
||||
|
||||
background: var(--canvas-node--background, var(--node-type-supplemental-background));
|
||||
border: var(--canvas-node-border-width) solid
|
||||
|
|
|
@ -9,7 +9,7 @@ exports[`useCanvasOperations > copyNodes > should copy nodes 1`] = `
|
|||
"parameters": {},
|
||||
"id": "1",
|
||||
"name": "Node 1",
|
||||
"type": "type",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
40,
|
||||
40
|
||||
|
@ -20,7 +20,7 @@ exports[`useCanvasOperations > copyNodes > should copy nodes 1`] = `
|
|||
"parameters": {},
|
||||
"id": "2",
|
||||
"name": "Node 2",
|
||||
"type": "type",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
40,
|
||||
40
|
||||
|
@ -44,7 +44,7 @@ exports[`useCanvasOperations > cutNodes > should copy and delete nodes 1`] = `
|
|||
"parameters": {},
|
||||
"id": "1",
|
||||
"name": "Node 1",
|
||||
"type": "type",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
40,
|
||||
40
|
||||
|
@ -55,7 +55,7 @@ exports[`useCanvasOperations > cutNodes > should copy and delete nodes 1`] = `
|
|||
"parameters": {},
|
||||
"id": "2",
|
||||
"name": "Node 2",
|
||||
"type": "type",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [
|
||||
40,
|
||||
40
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@ import { useDataSchema } from '@/composables/useDataSchema';
|
|||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { usePinnedData, type PinDataSource } from '@/composables/usePinnedData';
|
||||
import { type PinDataSource, usePinnedData } from '@/composables/usePinnedData';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
|
@ -65,6 +65,13 @@ import {
|
|||
parseCanvasConnectionHandleString,
|
||||
} from '@/utils/canvasUtilsV2';
|
||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
CONFIGURABLE_NODE_SIZE,
|
||||
CONFIGURATION_NODE_SIZE,
|
||||
DEFAULT_NODE_SIZE,
|
||||
GRID_SIZE,
|
||||
PUSH_NODES_OFFSET,
|
||||
} from '@/utils/nodeViewUtils';
|
||||
import { isValidNodeConnectionType } from '@/utils/typeGuards';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
import type {
|
||||
|
@ -358,6 +365,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
|
||||
function setNodeSelected(id?: string) {
|
||||
if (!id) {
|
||||
uiStore.lastInteractedWithNodeId = null;
|
||||
uiStore.lastSelectedNode = '';
|
||||
return;
|
||||
}
|
||||
|
@ -367,6 +375,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
return;
|
||||
}
|
||||
|
||||
uiStore.lastInteractedWithNodeId = id;
|
||||
uiStore.lastSelectedNode = node.name;
|
||||
}
|
||||
|
||||
|
@ -834,73 +843,91 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
}
|
||||
}
|
||||
|
||||
function resolveNodePosition(node: INodeUi, nodeTypeDescription: INodeTypeDescription) {
|
||||
if (node.position) {
|
||||
return NodeViewUtils.getNewNodePosition(
|
||||
canvasStore.getNodesWithPlaceholderNode(),
|
||||
node.position,
|
||||
);
|
||||
}
|
||||
function resolveNodePosition(
|
||||
node: Omit<INodeUi, 'position'> & { position?: INodeUi['position'] },
|
||||
nodeTypeDescription: INodeTypeDescription,
|
||||
) {
|
||||
let position: XYPosition | undefined = node.position;
|
||||
let pushOffsets: XYPosition = [40, 40];
|
||||
|
||||
// Available when
|
||||
// - clicking the plus button of a node handle
|
||||
// - dragging an edge / connection of a node handle
|
||||
// - selecting a node, adding a node via the node creator
|
||||
const lastInteractedWithNode = uiStore.lastInteractedWithNode;
|
||||
// Available when clicking the plus button of a node edge / connection
|
||||
const lastInteractedWithNodeConnection = uiStore.lastInteractedWithNodeConnection;
|
||||
// Available when dragging an edge / connection from a node
|
||||
const lastInteractedWithNodeHandle = uiStore.lastInteractedWithNodeHandle;
|
||||
|
||||
const { type: connectionType, index: connectionIndex } = parseCanvasConnectionHandleString(
|
||||
lastInteractedWithNodeHandle ?? lastInteractedWithNodeConnection?.sourceHandle ?? '',
|
||||
);
|
||||
|
||||
const nodeSize =
|
||||
connectionType === NodeConnectionType.Main ? DEFAULT_NODE_SIZE : CONFIGURATION_NODE_SIZE;
|
||||
|
||||
if (lastInteractedWithNode) {
|
||||
const lastSelectedNodeTypeDescription = nodeTypesStore.getNodeType(
|
||||
const lastInteractedWithNodeTypeDescription = nodeTypesStore.getNodeType(
|
||||
lastInteractedWithNode.type,
|
||||
lastInteractedWithNode.typeVersion,
|
||||
);
|
||||
const lastInteractedWithNodeObject = editableWorkflowObject.value.getNode(
|
||||
lastInteractedWithNode.name,
|
||||
);
|
||||
|
||||
if (lastInteractedWithNodeConnection) {
|
||||
shiftDownstreamNodesPosition(lastInteractedWithNode.name, NodeViewUtils.PUSH_NODES_OFFSET, {
|
||||
trackHistory: true,
|
||||
});
|
||||
}
|
||||
|
||||
// This position is set in `onMouseUp` when pulling connections
|
||||
const newNodeInsertPosition = canvasStore.newNodeInsertPosition;
|
||||
const newNodeInsertPosition = uiStore.lastCancelledConnectionPosition;
|
||||
if (newNodeInsertPosition) {
|
||||
canvasStore.newNodeInsertPosition = null;
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, [
|
||||
newNodeInsertPosition[0] + NodeViewUtils.GRID_SIZE,
|
||||
newNodeInsertPosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||
]);
|
||||
} else {
|
||||
let yOffset = 0;
|
||||
// When pulling / cancelling a connection.
|
||||
// The new node should be placed at the same position as the mouse up event,
|
||||
// designated by the `newNodeInsertPosition` value.
|
||||
|
||||
// Compute the y offset for the new node based on the number of main outputs of the source node
|
||||
if (uiStore.lastInteractedWithNodeConnection) {
|
||||
const sourceNodeType = nodeTypesStore.getNodeType(
|
||||
lastInteractedWithNode.type,
|
||||
lastInteractedWithNode.typeVersion,
|
||||
const xOffset = connectionType === NodeConnectionType.Main ? 0 : -nodeSize[0] / 2;
|
||||
const yOffset = connectionType === NodeConnectionType.Main ? -nodeSize[1] / 2 : 0;
|
||||
|
||||
position = [newNodeInsertPosition[0] + xOffset, newNodeInsertPosition[1] + yOffset];
|
||||
|
||||
uiStore.lastCancelledConnectionPosition = null;
|
||||
} else if (lastInteractedWithNodeTypeDescription) {
|
||||
// When
|
||||
// - clicking the plus button of a node handle
|
||||
// - clicking the plus button of a node edge / connection
|
||||
// - selecting a node, adding a node via the node creator
|
||||
|
||||
let yOffset = 0;
|
||||
if (lastInteractedWithNodeConnection) {
|
||||
// When clicking the plus button of a node edge / connection
|
||||
// Compute the y offset for the new node based on the number of main outputs of the source node
|
||||
// and shift the downstream nodes accordingly
|
||||
|
||||
shiftDownstreamNodesPosition(lastInteractedWithNode.name, PUSH_NODES_OFFSET, {
|
||||
trackHistory: true,
|
||||
});
|
||||
|
||||
const yOffsetValuesByOutputCount = [
|
||||
[-nodeSize[1], nodeSize[1]],
|
||||
[-nodeSize[1] - 2 * GRID_SIZE, 0, nodeSize[1] - 2 * GRID_SIZE],
|
||||
[
|
||||
-2 * nodeSize[1] - 2 * GRID_SIZE,
|
||||
-nodeSize[1],
|
||||
nodeSize[1],
|
||||
2 * nodeSize[1] - 2 * GRID_SIZE,
|
||||
],
|
||||
];
|
||||
|
||||
const lastInteractedWithNodeOutputs = NodeHelpers.getNodeOutputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNode,
|
||||
lastInteractedWithNodeTypeDescription,
|
||||
);
|
||||
const lastInteractedWithNodeOutputTypes = NodeHelpers.getConnectionTypes(
|
||||
lastInteractedWithNodeOutputs,
|
||||
);
|
||||
const lastInteractedWithNodeMainOutputs = lastInteractedWithNodeOutputTypes.filter(
|
||||
(output) => output === NodeConnectionType.Main,
|
||||
);
|
||||
|
||||
if (sourceNodeType) {
|
||||
const offsets = [
|
||||
[-100, 100],
|
||||
[-140, 0, 140],
|
||||
[-240, -100, 100, 240],
|
||||
];
|
||||
|
||||
const sourceNodeOutputs = NodeHelpers.getNodeOutputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNode,
|
||||
sourceNodeType,
|
||||
);
|
||||
const sourceNodeOutputTypes = NodeHelpers.getConnectionTypes(sourceNodeOutputs);
|
||||
const sourceNodeOutputMainOutputs = sourceNodeOutputTypes.filter(
|
||||
(output) => output === NodeConnectionType.Main,
|
||||
);
|
||||
|
||||
if (sourceNodeOutputMainOutputs.length > 1) {
|
||||
const { index: sourceOutputIndex } = parseCanvasConnectionHandleString(
|
||||
uiStore.lastInteractedWithNodeConnection.sourceHandle,
|
||||
);
|
||||
const offset = offsets[sourceNodeOutputMainOutputs.length - 2];
|
||||
yOffset = offset[sourceOutputIndex];
|
||||
}
|
||||
if (lastInteractedWithNodeMainOutputs.length > 1) {
|
||||
const yOffsetValues =
|
||||
yOffsetValuesByOutputCount[lastInteractedWithNodeMainOutputs.length - 2];
|
||||
yOffset = yOffsetValues[connectionIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -913,80 +940,96 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
// with only "main" outputs.
|
||||
outputs = NodeHelpers.getNodeOutputs(
|
||||
editableWorkflowObject.value,
|
||||
node,
|
||||
node as INode,
|
||||
nodeTypeDescription,
|
||||
);
|
||||
} catch (e) {}
|
||||
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
|
||||
const lastInteractedWithNodeObject = editableWorkflowObject.value.getNode(
|
||||
lastInteractedWithNode.name,
|
||||
);
|
||||
|
||||
// If node has only scoped outputs, position it below the last selected node
|
||||
if (lastSelectedNodeTypeDescription) {
|
||||
pushOffsets = [100, 0];
|
||||
|
||||
if (
|
||||
outputTypes.length > 0 &&
|
||||
outputTypes.every((outputName) => outputName !== NodeConnectionType.Main) &&
|
||||
lastInteractedWithNodeObject
|
||||
) {
|
||||
// When the added node has only non-main outputs (configuration nodes)
|
||||
// We want to place the new node directly below the last interacted with node.
|
||||
|
||||
const lastInteractedWithNodeInputs = NodeHelpers.getNodeInputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNodeObject,
|
||||
lastInteractedWithNodeTypeDescription,
|
||||
);
|
||||
const lastInteractedWithNodeInputTypes = NodeHelpers.getConnectionTypes(
|
||||
lastInteractedWithNodeInputs,
|
||||
);
|
||||
const lastInteractedWithNodeScopedInputTypes = (
|
||||
lastInteractedWithNodeInputTypes || []
|
||||
).filter((input) => input !== NodeConnectionType.Main);
|
||||
const scopedConnectionIndex = lastInteractedWithNodeScopedInputTypes.findIndex(
|
||||
(inputType) => outputs[0] === inputType,
|
||||
);
|
||||
|
||||
const lastInteractedWithNodeWidthDivisions = Math.max(
|
||||
lastInteractedWithNodeScopedInputTypes.length + 1,
|
||||
1,
|
||||
);
|
||||
|
||||
position = [
|
||||
lastInteractedWithNode.position[0] +
|
||||
(CONFIGURABLE_NODE_SIZE[0] / lastInteractedWithNodeWidthDivisions) *
|
||||
(scopedConnectionIndex + 1) -
|
||||
nodeSize[0] / 2,
|
||||
lastInteractedWithNode.position[1] + PUSH_NODES_OFFSET,
|
||||
];
|
||||
} else {
|
||||
// When the node has only main outputs, mixed outputs, or no outputs at all
|
||||
// We want to place the new node directly to the right of the last interacted with node.
|
||||
|
||||
const lastInteractedWithNodeInputs = NodeHelpers.getNodeInputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNode,
|
||||
lastInteractedWithNodeTypeDescription,
|
||||
);
|
||||
const lastInteractedWithNodeInputTypes = NodeHelpers.getConnectionTypes(
|
||||
lastInteractedWithNodeInputs,
|
||||
);
|
||||
|
||||
let pushOffset = PUSH_NODES_OFFSET;
|
||||
if (
|
||||
lastInteractedWithNodeObject &&
|
||||
outputTypes.length > 0 &&
|
||||
outputTypes.every((outputName) => outputName !== NodeConnectionType.Main)
|
||||
!!lastInteractedWithNodeInputTypes.find((input) => input !== NodeConnectionType.Main)
|
||||
) {
|
||||
const lastSelectedInputs = NodeHelpers.getNodeInputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNodeObject,
|
||||
lastSelectedNodeTypeDescription,
|
||||
);
|
||||
const lastSelectedInputTypes = NodeHelpers.getConnectionTypes(lastSelectedInputs);
|
||||
|
||||
const scopedConnectionIndex = (lastSelectedInputTypes || [])
|
||||
.filter((input) => input !== NodeConnectionType.Main)
|
||||
.findIndex((inputType) => outputs[0] === inputType);
|
||||
|
||||
return NodeViewUtils.getNewNodePosition(
|
||||
workflowsStore.allNodes,
|
||||
[
|
||||
lastInteractedWithNode.position[0] +
|
||||
(NodeViewUtils.NODE_SIZE /
|
||||
(Math.max(lastSelectedNodeTypeDescription?.inputs?.length ?? 1), 1)) *
|
||||
scopedConnectionIndex,
|
||||
lastInteractedWithNode.position[1] + NodeViewUtils.PUSH_NODES_OFFSET,
|
||||
],
|
||||
[100, 0],
|
||||
);
|
||||
} else {
|
||||
// Has only main outputs or no outputs at all
|
||||
const inputs = NodeHelpers.getNodeInputs(
|
||||
editableWorkflowObject.value,
|
||||
lastInteractedWithNode,
|
||||
lastSelectedNodeTypeDescription,
|
||||
);
|
||||
const inputsTypes = NodeHelpers.getConnectionTypes(inputs);
|
||||
|
||||
let pushOffset = NodeViewUtils.PUSH_NODES_OFFSET;
|
||||
if (!!inputsTypes.find((input) => input !== NodeConnectionType.Main)) {
|
||||
// If the node has scoped inputs, push it down a bit more
|
||||
pushOffset += 150;
|
||||
}
|
||||
|
||||
// If a node is active then add the new node directly after the current one
|
||||
return NodeViewUtils.getNewNodePosition(
|
||||
workflowsStore.allNodes,
|
||||
[
|
||||
lastInteractedWithNode.position[0] + pushOffset,
|
||||
lastInteractedWithNode.position[1] + yOffset,
|
||||
],
|
||||
[100, 0],
|
||||
);
|
||||
// If the node has scoped inputs, push it down a bit more
|
||||
pushOffset += 140;
|
||||
}
|
||||
|
||||
// If a node is active then add the new node directly after the current one
|
||||
position = [
|
||||
lastInteractedWithNode.position[0] + pushOffset,
|
||||
lastInteractedWithNode.position[1] + yOffset,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If added node is a trigger and it's the first one added to the canvas
|
||||
// we place it at canvasAddButtonPosition to replace the canvas add button
|
||||
const position = (
|
||||
nodeTypesStore.isTriggerNode(node.type) && triggerNodes.value.length === 0
|
||||
? [0, 0]
|
||||
: // If no node is active find a free spot
|
||||
lastClickPosition.value
|
||||
) as XYPosition;
|
||||
if (!position) {
|
||||
if (nodeTypesStore.isTriggerNode(node.type) && triggerNodes.value.length === 0) {
|
||||
// When added node is a trigger, and it's the first one added to the canvas
|
||||
// we place it at root to replace the canvas add button
|
||||
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position);
|
||||
position = [0, 0];
|
||||
} else {
|
||||
// When no position is set, we place the node at the last clicked position
|
||||
|
||||
position = lastClickPosition.value;
|
||||
}
|
||||
}
|
||||
|
||||
return NodeViewUtils.getNewNodePosition(workflowsStore.allNodes, position, pushOffsets);
|
||||
}
|
||||
|
||||
function resolveNodeName(node: INodeUi) {
|
||||
|
@ -1219,7 +1262,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
|
||||
function resetWorkspace() {
|
||||
// Reset node creator
|
||||
nodeCreatorStore.openNodeCreator({ createNodeActive: false });
|
||||
nodeCreatorStore.setNodeCreatorState({ createNodeActive: false });
|
||||
nodeCreatorStore.setShowScrim(false);
|
||||
|
||||
// Make sure that if there is a waiting test-webhook, it gets removed
|
||||
|
@ -1380,7 +1423,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
|
||||
// Create a workflow with the new nodes and connections that we can use
|
||||
// the rename method
|
||||
const tempWorkflow: Workflow = workflowHelpers.getWorkflow(createNodes, newConnections);
|
||||
const tempWorkflow: Workflow = workflowsStore.getWorkflow(createNodes, newConnections);
|
||||
|
||||
// Rename all the nodes of which the name changed
|
||||
for (oldName in nodeNameTable) {
|
||||
|
@ -1708,6 +1751,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
requireNodeTypeDescription,
|
||||
addNodes,
|
||||
addNode,
|
||||
resolveNodePosition,
|
||||
revertAddNode,
|
||||
updateNodesPosition,
|
||||
updateNodePosition,
|
||||
|
@ -1726,6 +1770,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
copyNodes,
|
||||
cutNodes,
|
||||
duplicateNodes,
|
||||
getNodesToSave,
|
||||
revertDeleteNode,
|
||||
addConnections,
|
||||
createConnection,
|
||||
|
|
|
@ -428,6 +428,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
// #endregion
|
||||
|
||||
return {
|
||||
state,
|
||||
getCredentialOwnerName,
|
||||
getCredentialsByType,
|
||||
getCredentialById,
|
||||
|
|
|
@ -90,7 +90,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
|
||||
setTimeout(() => {
|
||||
if (creatorView) {
|
||||
openNodeCreator({
|
||||
setNodeCreatorState({
|
||||
createNodeActive: true,
|
||||
nodeCreatorView: creatorView,
|
||||
});
|
||||
|
@ -110,7 +110,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
});
|
||||
}
|
||||
|
||||
function openNodeCreator({
|
||||
function setNodeCreatorState({
|
||||
source,
|
||||
createNodeActive,
|
||||
nodeCreatorView,
|
||||
|
@ -200,7 +200,6 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
uiStore.lastSelectedNode = sourceNode.name;
|
||||
uiStore.lastSelectedNodeEndpointUuid = connection.sourceHandle ?? null;
|
||||
uiStore.lastSelectedNodeOutputIndex = index;
|
||||
// canvasStore.newNodeInsertPosition = null;
|
||||
|
||||
if (isVueFlowConnection(connection)) {
|
||||
uiStore.lastInteractedWithNodeConnection = connection;
|
||||
|
@ -208,7 +207,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
uiStore.lastInteractedWithNodeHandle = connection.sourceHandle ?? null;
|
||||
uiStore.lastInteractedWithNodeId = sourceNode.id;
|
||||
|
||||
openNodeCreator({
|
||||
setNodeCreatorState({
|
||||
source: eventSource,
|
||||
createNodeActive: true,
|
||||
nodeCreatorView,
|
||||
|
@ -231,7 +230,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
ndvStore.activeNodeName = null;
|
||||
setSelectedView(TRIGGER_NODE_CREATOR_VIEW);
|
||||
setShowScrim(true);
|
||||
openNodeCreator({
|
||||
setNodeCreatorState({
|
||||
source,
|
||||
createNodeActive: true,
|
||||
nodeCreatorView: TRIGGER_NODE_CREATOR_VIEW,
|
||||
|
@ -276,7 +275,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
|||
setOpenSource,
|
||||
setActions,
|
||||
setMergeNodes,
|
||||
openNodeCreator,
|
||||
setNodeCreatorState,
|
||||
openSelectiveNodeCreator,
|
||||
openNodeCreatorForConnectingNode,
|
||||
openNodeCreatorForTriggerNodes,
|
||||
|
|
|
@ -200,6 +200,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
const lastInteractedWithNodeConnection = ref<Connection | null>(null);
|
||||
const lastInteractedWithNodeHandle = ref<string | null>(null);
|
||||
const lastInteractedWithNodeId = ref<string | null>(null);
|
||||
const lastCancelledConnectionPosition = ref<XYPosition | null>(null);
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
@ -624,6 +625,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
lastInteractedWithNodeConnection.value = null;
|
||||
lastInteractedWithNodeHandle.value = null;
|
||||
lastInteractedWithNodeId.value = null;
|
||||
lastCancelledConnectionPosition.value = null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -652,6 +654,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
lastInteractedWithNodeHandle,
|
||||
lastInteractedWithNodeId,
|
||||
lastInteractedWithNode,
|
||||
lastCancelledConnectionPosition,
|
||||
nodeViewOffsetPosition,
|
||||
nodeViewMoveInProgress,
|
||||
nodeViewInitialized,
|
||||
|
|
|
@ -103,9 +103,8 @@ export function mapLegacyConnectionToCanvasConnection(
|
|||
export function parseCanvasConnectionHandleString(handle: string | null | undefined) {
|
||||
const [mode, type, index] = (handle ?? '').split('/');
|
||||
|
||||
const resolvedType = isValidNodeConnectionType(type) ? type : NodeConnectionType.Main;
|
||||
const resolvedMode = isValidCanvasConnectionMode(mode) ? mode : CanvasConnectionMode.Output;
|
||||
|
||||
const resolvedType = isValidNodeConnectionType(type) ? type : NodeConnectionType.Main;
|
||||
let resolvedIndex = parseInt(index, 10);
|
||||
if (isNaN(resolvedIndex)) {
|
||||
resolvedIndex = 0;
|
||||
|
|
|
@ -38,6 +38,9 @@ const MIN_X_TO_SHOW_OUTPUT_LABEL = 90;
|
|||
const MIN_Y_TO_SHOW_OUTPUT_LABEL = 100;
|
||||
|
||||
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 PLACEHOLDER_TRIGGER_NODE_SIZE = 100;
|
||||
export const DEFAULT_START_POSITION_X = 180;
|
||||
export const DEFAULT_START_POSITION_Y = 240;
|
||||
|
|
|
@ -34,7 +34,11 @@ import type {
|
|||
ToggleNodeCreatorOptions,
|
||||
XYPosition,
|
||||
} from '@/Interface';
|
||||
import type { Connection, ViewportTransform } from '@vue-flow/core';
|
||||
import type {
|
||||
Connection,
|
||||
ViewportTransform,
|
||||
XYPosition as VueFlowXYPosition,
|
||||
} from '@vue-flow/core';
|
||||
import type {
|
||||
CanvasConnectionCreateData,
|
||||
CanvasEventBusEvents,
|
||||
|
@ -738,12 +742,20 @@ function onRevertCreateConnection({ connection }: { connection: [IConnection, IC
|
|||
revertCreateConnection(connection);
|
||||
}
|
||||
|
||||
function onCreateConnectionCancelled(event: ConnectStartEvent, mouseEvent?: MouseEvent) {
|
||||
function onCreateConnectionCancelled(
|
||||
event: ConnectStartEvent,
|
||||
position: VueFlowXYPosition,
|
||||
mouseEvent?: MouseEvent,
|
||||
) {
|
||||
const preventDefault = (mouseEvent?.target as HTMLElement).classList?.contains('clickable');
|
||||
if (preventDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
uiStore.lastInteractedWithNodeId = event.nodeId;
|
||||
uiStore.lastInteractedWithNodeHandle = event.handleId;
|
||||
uiStore.lastCancelledConnectionPosition = [position.x, position.y];
|
||||
|
||||
setTimeout(() => {
|
||||
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
||||
connection: {
|
||||
|
@ -874,11 +886,15 @@ async function onOpenNodeCreatorForTriggerNodes(source: NodeCreatorOpenSource) {
|
|||
}
|
||||
|
||||
function onOpenNodeCreatorFromCanvas(source: NodeCreatorOpenSource) {
|
||||
onOpenNodeCreator({ createNodeActive: true, source });
|
||||
onToggleNodeCreator({ createNodeActive: true, source });
|
||||
}
|
||||
|
||||
function onOpenNodeCreator(options: ToggleNodeCreatorOptions) {
|
||||
nodeCreatorStore.openNodeCreator(options);
|
||||
function onToggleNodeCreator(options: ToggleNodeCreatorOptions) {
|
||||
nodeCreatorStore.setNodeCreatorState(options);
|
||||
|
||||
if (!options.createNodeActive && !options.hasAddedNodes) {
|
||||
uiStore.resetLastInteractedWith();
|
||||
}
|
||||
}
|
||||
|
||||
function onCreateSticky() {
|
||||
|
@ -1378,7 +1394,6 @@ function selectNodes(ids: string[]) {
|
|||
|
||||
function onClickPane(position: CanvasNode['position']) {
|
||||
lastClickPosition.value = [position.x, position.y];
|
||||
canvasStore.newNodeInsertPosition = [position.x, position.y];
|
||||
uiStore.isCreateNodeActive = false;
|
||||
}
|
||||
|
||||
|
@ -1563,7 +1578,7 @@ onDeactivated(() => {
|
|||
v-if="!isCanvasReadOnly"
|
||||
:create-node-active="uiStore.isCreateNodeActive"
|
||||
:node-view-scale="viewportTransform.zoom"
|
||||
@toggle-node-creator="onOpenNodeCreator"
|
||||
@toggle-node-creator="onToggleNodeCreator"
|
||||
@add-nodes="onAddNodesAndConnections"
|
||||
/>
|
||||
</Suspense>
|
||||
|
|
Loading…
Reference in a new issue