## Summary In an effort to do as little processing as possible in each `Node` component, this PR passes current workflow object to it as a property instead of calling the slow `getCurrentWorkflow` store getter a few times in each node. This should substantially improve loading times for large workflows. As a benchmark, I was using a workflow from [this Linear ticket](https://linear.app/n8n/issue/ADO-1501/deliveryhero-enterprise-instance-very-slow-loading-workflows) and this fix brought down opening time by **20 seconds**. Together with fixes from #7901, this workflow was opening in less than **10 seconds** on my laptop. [Latest e2e run](https://github.com/n8n-io/n8n/actions/runs/7062162739) #### How to test the change: 1. Open a large workflow 2. Observe loading time ## Issues fixed ADO-1523 https://community.n8n.io/t/ui-very-slow-with-more-than-100-nodes/8236/14 ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
654 lines
19 KiB
TypeScript
654 lines
19 KiB
TypeScript
import { defineComponent } from 'vue';
|
|
import type { PropType } from 'vue';
|
|
import { mapStores } from 'pinia';
|
|
|
|
import type { INodeUi } from '@/Interface';
|
|
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
|
import {
|
|
NO_OP_NODE_TYPE,
|
|
NODE_CONNECTION_TYPE_ALLOW_MULTIPLE,
|
|
NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
|
NODE_MIN_INPUT_ITEMS_COUNT,
|
|
} from '@/constants';
|
|
|
|
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
|
import type {
|
|
ConnectionTypes,
|
|
INodeInputConfiguration,
|
|
INodeTypeDescription,
|
|
INodeOutputConfiguration,
|
|
Workflow,
|
|
} from 'n8n-workflow';
|
|
import { useUIStore } from '@/stores/ui.store';
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
|
import type { Endpoint, EndpointOptions } from '@jsplumb/core';
|
|
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
|
import { useHistoryStore } from '@/stores/history.store';
|
|
import { useCanvasStore } from '@/stores/canvas.store';
|
|
import type { EndpointSpec } from '@jsplumb/common';
|
|
|
|
const createAddInputEndpointSpec = (
|
|
connectionName: NodeConnectionType,
|
|
color: string,
|
|
): EndpointSpec => {
|
|
const multiple = NODE_CONNECTION_TYPE_ALLOW_MULTIPLE.includes(connectionName);
|
|
|
|
return {
|
|
type: 'N8nAddInput',
|
|
options: {
|
|
width: 24,
|
|
height: 72,
|
|
color,
|
|
multiple,
|
|
},
|
|
};
|
|
};
|
|
|
|
const createDiamondOutputEndpointSpec = (): EndpointSpec => ({
|
|
type: 'Rectangle',
|
|
options: {
|
|
height: 10,
|
|
width: 10,
|
|
cssClass: 'diamond-output-endpoint',
|
|
},
|
|
});
|
|
|
|
export const nodeBase = defineComponent({
|
|
mixins: [deviceSupportHelpers],
|
|
mounted() {
|
|
// Initialize the node
|
|
if (this.data !== null) {
|
|
try {
|
|
this.__addNode(this.data);
|
|
} catch (error) {
|
|
// This breaks when new nodes are loaded into store but workflow tab is not currently active
|
|
// Shouldn't affect anything
|
|
}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
inputs: [] as Array<ConnectionTypes | INodeInputConfiguration>,
|
|
outputs: [] as Array<ConnectionTypes | INodeOutputConfiguration>,
|
|
};
|
|
},
|
|
computed: {
|
|
...mapStores(useNodeTypesStore, useUIStore, useCanvasStore, useWorkflowsStore, useHistoryStore),
|
|
data(): INodeUi | null {
|
|
return this.workflowsStore.getNodeByName(this.name);
|
|
},
|
|
nodeId(): string {
|
|
return this.data?.id || '';
|
|
},
|
|
},
|
|
props: {
|
|
name: {
|
|
type: String,
|
|
},
|
|
instance: {
|
|
type: Object as PropType<BrowserJsPlumbInstance>,
|
|
},
|
|
isReadOnly: {
|
|
type: Boolean,
|
|
},
|
|
isActive: {
|
|
type: Boolean,
|
|
},
|
|
hideActions: {
|
|
type: Boolean,
|
|
},
|
|
disableSelecting: {
|
|
type: Boolean,
|
|
},
|
|
showCustomTooltip: {
|
|
type: Boolean,
|
|
},
|
|
workflow: {
|
|
type: Object as () => Workflow,
|
|
required: true,
|
|
},
|
|
},
|
|
methods: {
|
|
__addEndpointTestingData(endpoint: Endpoint, type: string, inputIndex: number) {
|
|
if (window?.Cypress && 'canvas' in endpoint.endpoint) {
|
|
const canvas = endpoint.endpoint.canvas;
|
|
this.instance.setAttribute(canvas, 'data-endpoint-name', this.data.name);
|
|
this.instance.setAttribute(canvas, 'data-input-index', inputIndex.toString());
|
|
this.instance.setAttribute(canvas, 'data-endpoint-type', type);
|
|
}
|
|
},
|
|
__addInputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
|
// Add Inputs
|
|
const rootTypeIndexData: {
|
|
[key: string]: number;
|
|
} = {};
|
|
const typeIndexData: {
|
|
[key: string]: number;
|
|
} = {};
|
|
|
|
const inputs: Array<ConnectionTypes | INodeInputConfiguration> =
|
|
NodeHelpers.getNodeInputs(this.workflow, this.data!, nodeTypeData) || [];
|
|
this.inputs = inputs;
|
|
|
|
const sortedInputs = [...inputs];
|
|
sortedInputs.sort((a, b) => {
|
|
if (typeof a === 'string') {
|
|
return 1;
|
|
} else if (typeof b === 'string') {
|
|
return -1;
|
|
}
|
|
|
|
if (a.required && !b.required) {
|
|
return -1;
|
|
} else if (!a.required && b.required) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
sortedInputs.forEach((value, i) => {
|
|
let inputConfiguration: INodeInputConfiguration;
|
|
if (typeof value === 'string') {
|
|
inputConfiguration = {
|
|
type: value,
|
|
};
|
|
} else {
|
|
inputConfiguration = value;
|
|
}
|
|
|
|
const inputName: ConnectionTypes = inputConfiguration.type;
|
|
|
|
const rootCategoryInputName =
|
|
inputName === NodeConnectionType.Main ? NodeConnectionType.Main : 'other';
|
|
|
|
// Increment the index for inputs with current name
|
|
if (rootTypeIndexData.hasOwnProperty(rootCategoryInputName)) {
|
|
rootTypeIndexData[rootCategoryInputName]++;
|
|
} else {
|
|
rootTypeIndexData[rootCategoryInputName] = 0;
|
|
}
|
|
|
|
if (typeIndexData.hasOwnProperty(inputName)) {
|
|
typeIndexData[inputName]++;
|
|
} else {
|
|
typeIndexData[inputName] = 0;
|
|
}
|
|
|
|
const rootTypeIndex = rootTypeIndexData[rootCategoryInputName];
|
|
const typeIndex = typeIndexData[inputName];
|
|
|
|
const inputsOfSameRootType = inputs.filter((inputData) => {
|
|
const thisInputName: string = typeof inputData === 'string' ? inputData : inputData.type;
|
|
return inputName === NodeConnectionType.Main
|
|
? thisInputName === NodeConnectionType.Main
|
|
: thisInputName !== NodeConnectionType.Main;
|
|
});
|
|
|
|
const nonMainInputs = inputsOfSameRootType.filter((inputData) => {
|
|
return inputData !== NodeConnectionType.Main;
|
|
});
|
|
const requiredNonMainInputs = nonMainInputs.filter((inputData) => {
|
|
return typeof inputData !== 'string' && inputData.required;
|
|
});
|
|
const optionalNonMainInputs = nonMainInputs.filter((inputData) => {
|
|
return typeof inputData !== 'string' && !inputData.required;
|
|
});
|
|
const spacerIndexes = this.getSpacerIndexes(
|
|
requiredNonMainInputs.length,
|
|
optionalNonMainInputs.length,
|
|
);
|
|
|
|
// Get the position of the anchor depending on how many it has
|
|
const anchorPosition = NodeViewUtils.getAnchorPosition(
|
|
inputName,
|
|
'input',
|
|
inputsOfSameRootType.length,
|
|
spacerIndexes,
|
|
)[rootTypeIndex];
|
|
|
|
const scope = NodeViewUtils.getEndpointScope(inputName as NodeConnectionType);
|
|
|
|
const newEndpointData: EndpointOptions = {
|
|
uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, inputName, typeIndex),
|
|
anchor: anchorPosition,
|
|
// We potentially want to change that in the future to allow people to dynamically
|
|
// activate and deactivate connected nodes
|
|
maxConnections: inputConfiguration.maxConnections ?? -1,
|
|
endpoint: 'Rectangle',
|
|
paintStyle: NodeViewUtils.getInputEndpointStyle(
|
|
nodeTypeData,
|
|
'--color-foreground-xdark',
|
|
inputName,
|
|
),
|
|
hoverPaintStyle: NodeViewUtils.getInputEndpointStyle(
|
|
nodeTypeData,
|
|
'--color-primary',
|
|
inputName,
|
|
),
|
|
scope: NodeViewUtils.getScope(scope),
|
|
source: inputName !== NodeConnectionType.Main,
|
|
target: !this.isReadOnly && inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
|
parameters: {
|
|
connection: 'target',
|
|
nodeId: this.nodeId,
|
|
type: inputName,
|
|
index: typeIndex,
|
|
},
|
|
enabled: !this.isReadOnly, // enabled in default case to allow dragging
|
|
cssClass: 'rect-input-endpoint',
|
|
dragAllowedWhenFull: true,
|
|
hoverClass: 'rect-input-endpoint-hover',
|
|
...this.__getInputConnectionStyle(inputName, nodeTypeData),
|
|
};
|
|
|
|
const endpoint = this.instance?.addEndpoint(
|
|
this.$refs[this.data.name] as Element,
|
|
newEndpointData,
|
|
) as Endpoint;
|
|
this.__addEndpointTestingData(endpoint, 'input', typeIndex);
|
|
if (inputConfiguration.displayName || nodeTypeData.inputNames?.[i]) {
|
|
// Apply input names if they got set
|
|
endpoint.addOverlay(
|
|
NodeViewUtils.getInputNameOverlay(
|
|
inputConfiguration.displayName || nodeTypeData.inputNames[i],
|
|
inputName,
|
|
inputConfiguration.required,
|
|
),
|
|
);
|
|
}
|
|
if (!Array.isArray(endpoint)) {
|
|
endpoint.__meta = {
|
|
nodeName: node.name,
|
|
nodeId: this.nodeId,
|
|
index: typeIndex,
|
|
totalEndpoints: inputsOfSameRootType.length,
|
|
nodeType: node.type,
|
|
};
|
|
}
|
|
|
|
// TODO: Activate again if it makes sense. Currently makes problems when removing
|
|
// connection on which the input has a name. It does not get hidden because
|
|
// the endpoint to which it connects when letting it go over the node is
|
|
// different to the regular one (have different ids). So that seems to make
|
|
// problems when hiding the input-name.
|
|
|
|
// if (index === 0 && inputName === NodeConnectionType.Main) {
|
|
// // Make the first main-input the default one to connect to when connection gets dropped on node
|
|
// this.instance.makeTarget(this.nodeId, newEndpointData);
|
|
// }
|
|
});
|
|
if (sortedInputs.length === 0) {
|
|
this.instance.manage(this.$refs[this.data.name] as Element);
|
|
}
|
|
},
|
|
getSpacerIndexes(
|
|
leftGroupItemsCount: number,
|
|
rightGroupItemsCount: number,
|
|
insertSpacerBetweenGroups = NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
|
minItemsCount = NODE_MIN_INPUT_ITEMS_COUNT,
|
|
): number[] {
|
|
const spacerIndexes = [];
|
|
|
|
if (leftGroupItemsCount > 0 && rightGroupItemsCount > 0) {
|
|
if (insertSpacerBetweenGroups) {
|
|
spacerIndexes.push(leftGroupItemsCount);
|
|
} else if (leftGroupItemsCount + rightGroupItemsCount < minItemsCount) {
|
|
for (
|
|
let spacerIndex = leftGroupItemsCount;
|
|
spacerIndex < minItemsCount - rightGroupItemsCount;
|
|
spacerIndex++
|
|
) {
|
|
spacerIndexes.push(spacerIndex);
|
|
}
|
|
}
|
|
} else {
|
|
if (
|
|
leftGroupItemsCount > 0 &&
|
|
leftGroupItemsCount < minItemsCount &&
|
|
rightGroupItemsCount === 0
|
|
) {
|
|
for (
|
|
let spacerIndex = 0;
|
|
spacerIndex < minItemsCount - leftGroupItemsCount;
|
|
spacerIndex++
|
|
) {
|
|
spacerIndexes.push(spacerIndex + leftGroupItemsCount);
|
|
}
|
|
} else if (
|
|
leftGroupItemsCount === 0 &&
|
|
rightGroupItemsCount > 0 &&
|
|
rightGroupItemsCount < minItemsCount
|
|
) {
|
|
for (
|
|
let spacerIndex = 0;
|
|
spacerIndex < minItemsCount - rightGroupItemsCount;
|
|
spacerIndex++
|
|
) {
|
|
spacerIndexes.push(spacerIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
return spacerIndexes;
|
|
},
|
|
__addOutputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
|
const rootTypeIndexData: {
|
|
[key: string]: number;
|
|
} = {};
|
|
const typeIndexData: {
|
|
[key: string]: number;
|
|
} = {};
|
|
|
|
this.outputs = NodeHelpers.getNodeOutputs(this.workflow, this.data, nodeTypeData) || [];
|
|
|
|
// TODO: There are still a lot of references of "main" in NodesView and
|
|
// other locations. So assume there will be more problems
|
|
let maxLabelLength = 0;
|
|
const outputConfigurations: INodeOutputConfiguration[] = [];
|
|
this.outputs.forEach((value, i) => {
|
|
let outputConfiguration: INodeOutputConfiguration;
|
|
if (typeof value === 'string') {
|
|
outputConfiguration = {
|
|
type: value,
|
|
};
|
|
} else {
|
|
outputConfiguration = value;
|
|
}
|
|
if (nodeTypeData.outputNames?.[i]) {
|
|
outputConfiguration.displayName = nodeTypeData.outputNames[i];
|
|
}
|
|
|
|
if (outputConfiguration.displayName) {
|
|
maxLabelLength =
|
|
outputConfiguration.displayName.length > maxLabelLength
|
|
? outputConfiguration.displayName.length
|
|
: maxLabelLength;
|
|
}
|
|
|
|
outputConfigurations.push(outputConfiguration);
|
|
});
|
|
|
|
const endpointLabelLength = maxLabelLength < 4 ? 'short' : 'medium';
|
|
|
|
this.outputs.forEach((value, i) => {
|
|
const outputConfiguration = outputConfigurations[i];
|
|
|
|
const outputName: ConnectionTypes = outputConfiguration.type;
|
|
|
|
const rootCategoryOutputName =
|
|
outputName === NodeConnectionType.Main ? NodeConnectionType.Main : 'other';
|
|
|
|
// Increment the index for outputs with current name
|
|
if (rootTypeIndexData.hasOwnProperty(rootCategoryOutputName)) {
|
|
rootTypeIndexData[rootCategoryOutputName]++;
|
|
} else {
|
|
rootTypeIndexData[rootCategoryOutputName] = 0;
|
|
}
|
|
|
|
if (typeIndexData.hasOwnProperty(outputName)) {
|
|
typeIndexData[outputName]++;
|
|
} else {
|
|
typeIndexData[outputName] = 0;
|
|
}
|
|
|
|
const rootTypeIndex = rootTypeIndexData[rootCategoryOutputName];
|
|
const typeIndex = typeIndexData[outputName];
|
|
|
|
const outputsOfSameRootType = this.outputs.filter((outputData) => {
|
|
const thisOutputName: string =
|
|
typeof outputData === 'string' ? outputData : outputData.type;
|
|
return outputName === NodeConnectionType.Main
|
|
? thisOutputName === NodeConnectionType.Main
|
|
: thisOutputName !== NodeConnectionType.Main;
|
|
});
|
|
|
|
// Get the position of the anchor depending on how many it has
|
|
const anchorPosition = NodeViewUtils.getAnchorPosition(
|
|
outputName,
|
|
'output',
|
|
outputsOfSameRootType.length,
|
|
)[rootTypeIndex];
|
|
|
|
const scope = NodeViewUtils.getEndpointScope(outputName as NodeConnectionType);
|
|
|
|
const newEndpointData: EndpointOptions = {
|
|
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, outputName, typeIndex),
|
|
anchor: anchorPosition,
|
|
maxConnections: -1,
|
|
endpoint: {
|
|
type: 'Dot',
|
|
options: {
|
|
radius: nodeTypeData && outputsOfSameRootType.length > 2 ? 7 : 9,
|
|
},
|
|
},
|
|
hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-primary'),
|
|
scope,
|
|
source: true,
|
|
target: outputName !== NodeConnectionType.Main,
|
|
enabled: !this.isReadOnly,
|
|
parameters: {
|
|
connection: 'source',
|
|
nodeId: this.nodeId,
|
|
type: outputName,
|
|
index: typeIndex,
|
|
},
|
|
hoverClass: 'dot-output-endpoint-hover',
|
|
connectionsDirected: true,
|
|
dragAllowedWhenFull: false,
|
|
...this.__getOutputConnectionStyle(outputName, outputConfiguration, nodeTypeData),
|
|
};
|
|
|
|
const endpoint = this.instance.addEndpoint(
|
|
this.$refs[this.data.name] as Element,
|
|
newEndpointData,
|
|
);
|
|
|
|
this.__addEndpointTestingData(endpoint, 'output', typeIndex);
|
|
if (outputConfiguration.displayName) {
|
|
// Apply output names if they got set
|
|
const overlaySpec = NodeViewUtils.getOutputNameOverlay(
|
|
outputConfiguration.displayName,
|
|
outputName,
|
|
outputConfiguration?.category,
|
|
);
|
|
endpoint.addOverlay(overlaySpec);
|
|
}
|
|
|
|
if (!Array.isArray(endpoint)) {
|
|
endpoint.__meta = {
|
|
nodeName: node.name,
|
|
nodeId: this.nodeId,
|
|
index: typeIndex,
|
|
totalEndpoints: outputsOfSameRootType.length,
|
|
endpointLabelLength,
|
|
};
|
|
}
|
|
|
|
if (!this.isReadOnly && outputName === NodeConnectionType.Main) {
|
|
const plusEndpointData: EndpointOptions = {
|
|
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, outputName, typeIndex),
|
|
anchor: anchorPosition,
|
|
maxConnections: -1,
|
|
endpoint: {
|
|
type: 'N8nPlus',
|
|
options: {
|
|
dimensions: 24,
|
|
connectedEndpoint: endpoint,
|
|
showOutputLabel: this.outputs.length === 1,
|
|
size: this.outputs.length >= 3 ? 'small' : 'medium',
|
|
endpointLabelLength,
|
|
hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'),
|
|
},
|
|
},
|
|
source: true,
|
|
target: false,
|
|
enabled: !this.isReadOnly,
|
|
paintStyle: {
|
|
outlineStroke: 'none',
|
|
},
|
|
hoverPaintStyle: {
|
|
outlineStroke: 'none',
|
|
},
|
|
parameters: {
|
|
connection: 'source',
|
|
nodeId: this.nodeId,
|
|
type: outputName,
|
|
index: typeIndex,
|
|
category: outputConfiguration?.category,
|
|
},
|
|
cssClass: 'plus-draggable-endpoint',
|
|
dragAllowedWhenFull: false,
|
|
};
|
|
|
|
if (outputConfiguration?.category) {
|
|
plusEndpointData.cssClass = `${plusEndpointData.cssClass} ${outputConfiguration?.category}`;
|
|
}
|
|
|
|
const plusEndpoint = this.instance.addEndpoint(
|
|
this.$refs[this.data.name] as Element,
|
|
plusEndpointData,
|
|
);
|
|
this.__addEndpointTestingData(plusEndpoint, 'plus', typeIndex);
|
|
|
|
if (!Array.isArray(plusEndpoint)) {
|
|
plusEndpoint.__meta = {
|
|
nodeName: node.name,
|
|
nodeId: this.nodeId,
|
|
index: typeIndex,
|
|
nodeType: node.type,
|
|
totalEndpoints: outputsOfSameRootType.length,
|
|
};
|
|
}
|
|
}
|
|
});
|
|
},
|
|
__addNode(node: INodeUi) {
|
|
const nodeTypeData = (this.nodeTypesStore.getNodeType(node.type, node.typeVersion) ??
|
|
this.nodeTypesStore.getNodeType(NO_OP_NODE_TYPE)) as INodeTypeDescription;
|
|
|
|
this.__addInputEndpoints(node, nodeTypeData);
|
|
this.__addOutputEndpoints(node, nodeTypeData);
|
|
},
|
|
__getEndpointColor(connectionType: ConnectionTypes) {
|
|
return `--node-type-${connectionType}-color`;
|
|
},
|
|
__getInputConnectionStyle(
|
|
connectionType: ConnectionTypes,
|
|
nodeTypeData: INodeTypeDescription,
|
|
): EndpointOptions {
|
|
if (connectionType === NodeConnectionType.Main) {
|
|
return {
|
|
paintStyle: NodeViewUtils.getInputEndpointStyle(
|
|
nodeTypeData,
|
|
this.__getEndpointColor(NodeConnectionType.Main),
|
|
connectionType,
|
|
),
|
|
};
|
|
}
|
|
|
|
if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) {
|
|
return {};
|
|
}
|
|
|
|
const createSupplementalConnectionType = (
|
|
connectionName: ConnectionTypes,
|
|
): EndpointOptions => ({
|
|
endpoint: createAddInputEndpointSpec(
|
|
connectionName as NodeConnectionType,
|
|
this.__getEndpointColor(connectionName),
|
|
),
|
|
});
|
|
|
|
return createSupplementalConnectionType(connectionType);
|
|
},
|
|
__getOutputConnectionStyle(
|
|
connectionType: ConnectionTypes,
|
|
outputConfiguration: INodeOutputConfiguration,
|
|
nodeTypeData: INodeTypeDescription,
|
|
): EndpointOptions {
|
|
const type = 'output';
|
|
|
|
const createSupplementalConnectionType = (
|
|
connectionName: ConnectionTypes,
|
|
): EndpointOptions => ({
|
|
endpoint: createDiamondOutputEndpointSpec(),
|
|
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
|
nodeTypeData,
|
|
this.__getEndpointColor(connectionName),
|
|
),
|
|
hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle(
|
|
nodeTypeData,
|
|
this.__getEndpointColor(connectionName),
|
|
),
|
|
});
|
|
|
|
if (connectionType === NodeConnectionType.Main) {
|
|
if (outputConfiguration.category === 'error') {
|
|
return {
|
|
paintStyle: {
|
|
...NodeViewUtils.getOutputEndpointStyle(
|
|
nodeTypeData,
|
|
this.__getEndpointColor(NodeConnectionType.Main),
|
|
),
|
|
fill: 'var(--node-error-output-color)',
|
|
},
|
|
cssClass: `dot-${type}-endpoint`,
|
|
};
|
|
}
|
|
return {
|
|
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
|
nodeTypeData,
|
|
this.__getEndpointColor(NodeConnectionType.Main),
|
|
),
|
|
cssClass: `dot-${type}-endpoint`,
|
|
};
|
|
}
|
|
|
|
if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) {
|
|
return {};
|
|
}
|
|
|
|
return createSupplementalConnectionType(connectionType);
|
|
},
|
|
touchEnd(e: MouseEvent) {
|
|
if (this.isTouchDevice) {
|
|
if (this.uiStore.isActionActive('dragActive')) {
|
|
this.uiStore.removeActiveAction('dragActive');
|
|
}
|
|
}
|
|
},
|
|
mouseLeftClick(e: MouseEvent) {
|
|
// @ts-ignore
|
|
const path = e.path || (e.composedPath && e.composedPath());
|
|
for (let index = 0; index < path.length; index++) {
|
|
if (
|
|
path[index].className &&
|
|
typeof path[index].className === 'string' &&
|
|
path[index].className.includes('no-select-on-click')
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!this.isTouchDevice) {
|
|
if (this.uiStore.isActionActive('dragActive')) {
|
|
this.uiStore.removeActiveAction('dragActive');
|
|
} else {
|
|
if (!this.isCtrlKeyPressed(e)) {
|
|
this.$emit('deselectAllNodes');
|
|
}
|
|
|
|
if (this.uiStore.isNodeSelected(this.data.name)) {
|
|
this.$emit('deselectNode', this.name);
|
|
} else {
|
|
this.$emit('nodeSelected', this.name);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
});
|