mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-13 05:47:31 -08:00
fix(editor): Fix for missing node connections in dev environment (#4707)
* 🐛 Fixing connections not showing up in dev environment * 🐛 Fixing a bug when opening execution page directly
This commit is contained in:
parent
ee6ac5d341
commit
b18ae18a6b
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import { getMidCanvasPosition } from '@/utils';
|
import { getMidCanvasPosition } from '@/utils/nodeViewUtils';
|
||||||
import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants";
|
import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants";
|
||||||
import { mapStores } from "pinia";
|
import { mapStores } from "pinia";
|
||||||
import { useUIStore } from "@/stores/ui";
|
import { useUIStore } from "@/stores/ui";
|
||||||
|
|
|
@ -58,7 +58,8 @@
|
||||||
import Vue, { PropType } from 'vue';
|
import Vue, { PropType } from 'vue';
|
||||||
import { INodeTypeDescription } from 'n8n-workflow';
|
import { INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
import { isCommunityPackageName, getNewNodePosition, NODE_SIZE } from '@/utils';
|
import { isCommunityPackageName } from '@/utils';
|
||||||
|
import { getNewNodePosition, NODE_SIZE } from '@/utils/nodeViewUtils';
|
||||||
import { COMMUNITY_NODES_INSTALLATION_DOCS_URL } from '@/constants';
|
import { COMMUNITY_NODES_INSTALLATION_DOCS_URL } from '@/constants';
|
||||||
|
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import NodeIcon from '@/components/NodeIcon.vue';
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { INodeUi, XYPosition } from '@/Interface';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
||||||
import { getMousePosition, getRelativePosition, HEADER_HEIGHT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_EXPANDED } from '@/utils';
|
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows';
|
import { useWorkflowsStore } from '@/stores/workflows';
|
||||||
|
import { getMousePosition, getRelativePosition, HEADER_HEIGHT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_EXPANDED } from '@/utils/nodeViewUtils';
|
||||||
|
|
||||||
export const mouseSelect = mixins(
|
export const mouseSelect = mixins(
|
||||||
deviceSupportHelpers,
|
deviceSupportHelpers,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
||||||
import { getMousePosition } from '@/utils';
|
import { getMousePosition } from '@/utils/nodeViewUtils';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,6 @@ import mixins from 'vue-typed-mixins';
|
||||||
import { IJsPlumbInstance, IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
import { IJsPlumbInstance, IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
||||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
||||||
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||||
import {
|
|
||||||
ANCHOR_POSITIONS,
|
|
||||||
GRID_SIZE,
|
|
||||||
getInputEndpointUUID,
|
|
||||||
getOutputEndpointUUID,
|
|
||||||
getInputEndpointStyle,
|
|
||||||
getOutputEndpointStyle,
|
|
||||||
getInputNameOverlay,
|
|
||||||
getOutputNameOverlay,
|
|
||||||
getStyleTokenValue,
|
|
||||||
} from '@/utils';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
@ -22,6 +11,8 @@ import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
import { useWorkflowsStore } from "@/stores/workflows";
|
import { useWorkflowsStore } from "@/stores/workflows";
|
||||||
import { useNodeTypesStore } from "@/stores/nodeTypes";
|
import { useNodeTypesStore } from "@/stores/nodeTypes";
|
||||||
|
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||||
|
import { getStyleTokenValue } from "@/utils";
|
||||||
|
|
||||||
export const nodeBase = mixins(
|
export const nodeBase = mixins(
|
||||||
deviceSupportHelpers,
|
deviceSupportHelpers,
|
||||||
|
@ -91,15 +82,15 @@ export const nodeBase = mixins(
|
||||||
index = indexData[inputName];
|
index = indexData[inputName];
|
||||||
|
|
||||||
// Get the position of the anchor depending on how many it has
|
// Get the position of the anchor depending on how many it has
|
||||||
const anchorPosition = ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
const anchorPosition = NodeViewUtils.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
||||||
|
|
||||||
const newEndpointData: IEndpointOptions = {
|
const newEndpointData: IEndpointOptions = {
|
||||||
uuid: getInputEndpointUUID(this.nodeId, index),
|
uuid:NodeViewUtils. getInputEndpointUUID(this.nodeId, index),
|
||||||
anchor: anchorPosition,
|
anchor: anchorPosition,
|
||||||
maxConnections: -1,
|
maxConnections: -1,
|
||||||
endpoint: 'Rectangle',
|
endpoint: 'Rectangle',
|
||||||
endpointStyle: getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
endpointStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||||
endpointHoverStyle: getInputEndpointStyle(nodeTypeData, '--color-primary'),
|
endpointHoverStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||||
isSource: false,
|
isSource: false,
|
||||||
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -119,7 +110,7 @@ export const nodeBase = mixins(
|
||||||
if (nodeTypeData.inputNames) {
|
if (nodeTypeData.inputNames) {
|
||||||
// Apply input names if they got set
|
// Apply input names if they got set
|
||||||
newEndpointData.overlays = [
|
newEndpointData.overlays = [
|
||||||
getInputNameOverlay(nodeTypeData.inputNames[index]),
|
NodeViewUtils.getInputNameOverlay(nodeTypeData.inputNames[index]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,15 +152,15 @@ export const nodeBase = mixins(
|
||||||
index = indexData[inputName];
|
index = indexData[inputName];
|
||||||
|
|
||||||
// Get the position of the anchor depending on how many it has
|
// Get the position of the anchor depending on how many it has
|
||||||
const anchorPosition = ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
const anchorPosition = NodeViewUtils.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
||||||
|
|
||||||
const newEndpointData: IEndpointOptions = {
|
const newEndpointData: IEndpointOptions = {
|
||||||
uuid: getOutputEndpointUUID(this.nodeId, index),
|
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||||
anchor: anchorPosition,
|
anchor: anchorPosition,
|
||||||
maxConnections: -1,
|
maxConnections: -1,
|
||||||
endpoint: 'Dot',
|
endpoint: 'Dot',
|
||||||
endpointStyle: getOutputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
endpointStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||||
endpointHoverStyle: getOutputEndpointStyle(nodeTypeData, '--color-primary'),
|
endpointHoverStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||||
isSource: true,
|
isSource: true,
|
||||||
isTarget: false,
|
isTarget: false,
|
||||||
enabled: !this.isReadOnly,
|
enabled: !this.isReadOnly,
|
||||||
|
@ -186,7 +177,7 @@ export const nodeBase = mixins(
|
||||||
if (nodeTypeData.outputNames) {
|
if (nodeTypeData.outputNames) {
|
||||||
// Apply output names if they got set
|
// Apply output names if they got set
|
||||||
newEndpointData.overlays = [
|
newEndpointData.overlays = [
|
||||||
getOutputNameOverlay(nodeTypeData.outputNames[index]),
|
NodeViewUtils.getOutputNameOverlay(nodeTypeData.outputNames[index]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +193,7 @@ export const nodeBase = mixins(
|
||||||
|
|
||||||
if (!this.isReadOnly) {
|
if (!this.isReadOnly) {
|
||||||
const plusEndpointData: IEndpointOptions = {
|
const plusEndpointData: IEndpointOptions = {
|
||||||
uuid: getOutputEndpointUUID(this.nodeId, index),
|
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||||
anchor: anchorPosition,
|
anchor: anchorPosition,
|
||||||
maxConnections: -1,
|
maxConnections: -1,
|
||||||
endpoint: 'N8nPlus',
|
endpoint: 'N8nPlus',
|
||||||
|
@ -250,7 +241,7 @@ export const nodeBase = mixins(
|
||||||
// https://jsplumb.github.io/jsplumb/home.html
|
// https://jsplumb.github.io/jsplumb/home.html
|
||||||
// Make nodes draggable
|
// Make nodes draggable
|
||||||
this.instance.draggable(this.nodeId, {
|
this.instance.draggable(this.nodeId, {
|
||||||
grid: [GRID_SIZE, GRID_SIZE],
|
grid: [NodeViewUtils.GRID_SIZE, NodeViewUtils.GRID_SIZE],
|
||||||
start: (params: { e: MouseEvent }) => {
|
start: (params: { e: MouseEvent }) => {
|
||||||
if (this.isReadOnly === true) {
|
if (this.isReadOnly === true) {
|
||||||
// Do not allow to move nodes in readOnly mode
|
// Do not allow to move nodes in readOnly mode
|
||||||
|
|
|
@ -8,11 +8,6 @@ import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
import { INodeUi, XYPosition } from '@/Interface';
|
import { INodeUi, XYPosition } from '@/Interface';
|
||||||
import {
|
import {
|
||||||
DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
|
|
||||||
PLACEHOLDER_TRIGGER_NODE_SIZE,
|
|
||||||
getMidCanvasPosition,
|
|
||||||
getNewNodePosition,
|
|
||||||
getZoomToFit,
|
|
||||||
scaleBigger,
|
scaleBigger,
|
||||||
scaleReset,
|
scaleReset,
|
||||||
scaleSmaller,
|
scaleSmaller,
|
||||||
|
@ -20,6 +15,7 @@ import {
|
||||||
import { START_NODE_TYPE } from '@/constants';
|
import { START_NODE_TYPE } from '@/constants';
|
||||||
import '@/plugins/N8nCustomConnectorType';
|
import '@/plugins/N8nCustomConnectorType';
|
||||||
import '@/plugins/PlusEndpointType';
|
import '@/plugins/PlusEndpointType';
|
||||||
|
import { DEFAULT_PLACEHOLDER_TRIGGER_BUTTON, getMidCanvasPosition, getNewNodePosition, getZoomToFit, PLACEHOLDER_TRIGGER_NODE_SIZE } from '@/utils/nodeViewUtils';
|
||||||
|
|
||||||
export const useCanvasStore = defineStore('canvas', () => {
|
export const useCanvasStore = defineStore('canvas', () => {
|
||||||
const workflowStore = useWorkflowsStore();
|
const workflowStore = useWorkflowsStore();
|
||||||
|
|
|
@ -282,7 +282,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
}
|
}
|
||||||
this.workflow.name = data.newName;
|
this.workflow.name = data.newName;
|
||||||
|
|
||||||
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID && this.workflowsById[this.workflow.id]) {
|
||||||
this.workflowsById[this.workflow.id].name = data.newName;
|
this.workflowsById[this.workflow.id].name = data.newName;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,275 +1,15 @@
|
||||||
import { isNumber } from "@/utils/typesUtils";
|
import { MAIN_HEADER_TABS, VIEWS } from "@/constants";
|
||||||
import { getStyleTokenValue } from '@/utils/htmlUtils';
|
import { IZoomConfig } from "@/Interface";
|
||||||
import { NODE_OUTPUT_DEFAULT_KEY, STICKY_NODE_TYPE, QUICKSTART_NOTE_NAME, MAIN_HEADER_TABS, VIEWS } from "@/constants";
|
import { Route } from "vue-router";
|
||||||
import { EndpointStyle, IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
|
|
||||||
import { AnchorArraySpec, Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
|
||||||
import {
|
|
||||||
IConnection,
|
|
||||||
INode,
|
|
||||||
ITaskData,
|
|
||||||
INodeExecutionData,
|
|
||||||
NodeInputConnections,
|
|
||||||
INodeTypeDescription,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Constants and utility functions mainly used by canvas store
|
Constants and utility functions mainly used by canvas store
|
||||||
and components used to display workflow in node view.
|
and components used to display workflow in node view.
|
||||||
|
These are general-purpose functions that are exported
|
||||||
|
with this module and should be used by importing from
|
||||||
|
'@/utils'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const OVERLAY_DROP_NODE_ID = 'drop-add-node';
|
|
||||||
export const OVERLAY_MIDPOINT_ARROW_ID = 'midpoint-arrow';
|
|
||||||
export const OVERLAY_ENDPOINT_ARROW_ID = 'endpoint-arrow';
|
|
||||||
export const OVERLAY_RUN_ITEMS_ID = 'run-items-label';
|
|
||||||
export const OVERLAY_CONNECTION_ACTIONS_ID = 'connection-actions';
|
|
||||||
export const JSPLUMB_FLOWCHART_STUB = 26;
|
|
||||||
export const OVERLAY_INPUT_NAME_LABEL = 'input-name-label';
|
|
||||||
export const OVERLAY_INPUT_NAME_LABEL_POSITION = [-3, .5];
|
|
||||||
export const OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED = [-4.5, .5];
|
|
||||||
export const OVERLAY_OUTPUT_NAME_LABEL = 'output-name-label';
|
|
||||||
export const GRID_SIZE = 20;
|
|
||||||
|
|
||||||
const MIN_X_TO_SHOW_OUTPUT_LABEL = 90;
|
|
||||||
const MIN_Y_TO_SHOW_OUTPUT_LABEL = 100;
|
|
||||||
|
|
||||||
export const NODE_SIZE = 100;
|
|
||||||
export const PLACEHOLDER_TRIGGER_NODE_SIZE = 100;
|
|
||||||
export const DEFAULT_START_POSITION_X = 180;
|
|
||||||
export const DEFAULT_START_POSITION_Y = 240;
|
|
||||||
export const HEADER_HEIGHT = 65;
|
|
||||||
export const SIDEBAR_WIDTH = 65;
|
|
||||||
export const INNER_SIDEBAR_WIDTH = 310;
|
|
||||||
export const SIDEBAR_WIDTH_EXPANDED = 200;
|
|
||||||
export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300;
|
|
||||||
export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE;
|
|
||||||
const LOOPBACK_MINIMUM = 140;
|
|
||||||
export const INPUT_UUID_KEY = '-input';
|
|
||||||
export const OUTPUT_UUID_KEY = '-output';
|
|
||||||
export const PLACEHOLDER_BUTTON = 'PlaceholderTriggerButton';
|
|
||||||
|
|
||||||
export const DEFAULT_PLACEHOLDER_TRIGGER_BUTTON = {
|
|
||||||
name: 'Choose a Trigger...',
|
|
||||||
type: PLACEHOLDER_BUTTON,
|
|
||||||
typeVersion: 1,
|
|
||||||
position: [],
|
|
||||||
parameters: {
|
|
||||||
height: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
|
||||||
width: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WELCOME_STICKY_NODE = {
|
|
||||||
name: QUICKSTART_NOTE_NAME,
|
|
||||||
type: STICKY_NODE_TYPE,
|
|
||||||
typeVersion: 1,
|
|
||||||
position: [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
] as XYPosition,
|
|
||||||
parameters: {
|
|
||||||
height: 300,
|
|
||||||
width: 380,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONNECTOR_FLOWCHART_TYPE = ['N8nCustom', {
|
|
||||||
cornerRadius: 12,
|
|
||||||
stub: JSPLUMB_FLOWCHART_STUB + 10,
|
|
||||||
targetGap: 4,
|
|
||||||
alwaysRespectStubs: false,
|
|
||||||
loopbackVerticalLength: NODE_SIZE + GRID_SIZE, // height of vertical segment when looping
|
|
||||||
loopbackMinimum: LOOPBACK_MINIMUM, // minimum length before flowchart loops around
|
|
||||||
getEndpointOffset(endpoint: Endpoint) {
|
|
||||||
const indexOffset = 10; // stub offset between different endpoints of same node
|
|
||||||
const index = endpoint && endpoint.__meta ? endpoint.__meta.index : 0;
|
|
||||||
const totalEndpoints = endpoint && endpoint.__meta ? endpoint.__meta.totalEndpoints : 0;
|
|
||||||
|
|
||||||
const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL);
|
|
||||||
const labelOffset = outputOverlay && outputOverlay.label && outputOverlay.label.length > 1 ? 10 : 0;
|
|
||||||
const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus
|
|
||||||
|
|
||||||
return index * indexOffset + labelOffset + outputsOffset;
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
|
|
||||||
export const CONNECTOR_PAINT_STYLE_DEFAULT: PaintStyle = {
|
|
||||||
stroke: getStyleTokenValue('--color-foreground-dark'),
|
|
||||||
strokeWidth: 2,
|
|
||||||
outlineWidth: 12,
|
|
||||||
outlineStroke: 'transparent',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONNECTOR_PAINT_STYLE_PULL: PaintStyle = {
|
|
||||||
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
|
||||||
stroke: getStyleTokenValue('--color-foreground-xdark'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONNECTOR_PAINT_STYLE_PRIMARY = {
|
|
||||||
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
|
||||||
stroke: getStyleTokenValue('--color-primary'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONNECTOR_PAINT_STYLE_SUCCESS = {
|
|
||||||
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
|
||||||
stroke: getStyleTokenValue('--color-success-light'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [
|
|
||||||
[
|
|
||||||
'Arrow',
|
|
||||||
{
|
|
||||||
id: OVERLAY_ENDPOINT_ARROW_ID,
|
|
||||||
location: 1,
|
|
||||||
width: 12,
|
|
||||||
foldback: 1,
|
|
||||||
length: 10,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Arrow',
|
|
||||||
{
|
|
||||||
id: OVERLAY_MIDPOINT_ARROW_ID,
|
|
||||||
location: 0.5,
|
|
||||||
width: 12,
|
|
||||||
foldback: 1,
|
|
||||||
length: 10,
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ANCHOR_POSITIONS: {
|
|
||||||
[key: string]: {
|
|
||||||
[key: number]: AnchorArraySpec[];
|
|
||||||
}
|
|
||||||
} = {
|
|
||||||
input: {
|
|
||||||
1: [
|
|
||||||
[0.01, 0.5, -1, 0],
|
|
||||||
],
|
|
||||||
2: [
|
|
||||||
[0.01, 0.3, -1, 0],
|
|
||||||
[0.01, 0.7, -1, 0],
|
|
||||||
],
|
|
||||||
3: [
|
|
||||||
[0.01, 0.25, -1, 0],
|
|
||||||
[0.01, 0.5, -1, 0],
|
|
||||||
[0.01, 0.75, -1, 0],
|
|
||||||
],
|
|
||||||
4: [
|
|
||||||
[0.01, 0.2, -1, 0],
|
|
||||||
[0.01, 0.4, -1, 0],
|
|
||||||
[0.01, 0.6, -1, 0],
|
|
||||||
[0.01, 0.8, -1, 0],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
1: [
|
|
||||||
[.99, 0.5, 1, 0],
|
|
||||||
],
|
|
||||||
2: [
|
|
||||||
[.99, 0.3, 1, 0],
|
|
||||||
[.99, 0.7, 1, 0],
|
|
||||||
],
|
|
||||||
3: [
|
|
||||||
[.99, 0.25, 1, 0],
|
|
||||||
[.99, 0.5, 1, 0],
|
|
||||||
[.99, 0.75, 1, 0],
|
|
||||||
],
|
|
||||||
4: [
|
|
||||||
[.99, 0.2, 1, 0],
|
|
||||||
[.99, 0.4, 1, 0],
|
|
||||||
[.99, 0.6, 1, 0],
|
|
||||||
[.99, 0.8, 1, 0],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const getInputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string): EndpointStyle => ({
|
|
||||||
width: 8,
|
|
||||||
height: nodeTypeData && nodeTypeData.outputs.length > 2 ? 18 : 20,
|
|
||||||
fill: getStyleTokenValue(color),
|
|
||||||
stroke: getStyleTokenValue(color),
|
|
||||||
lineWidth: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getInputNameOverlay = (label: string): OverlaySpec => ([
|
|
||||||
'Label',
|
|
||||||
{
|
|
||||||
id: OVERLAY_INPUT_NAME_LABEL,
|
|
||||||
location: OVERLAY_INPUT_NAME_LABEL_POSITION,
|
|
||||||
label,
|
|
||||||
cssClass: 'node-input-endpoint-label',
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const getOutputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string) => ({
|
|
||||||
radius: nodeTypeData && nodeTypeData.outputs.length > 2 ? 7 : 9,
|
|
||||||
fill: getStyleTokenValue(color),
|
|
||||||
outlineStroke: 'none',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getOutputNameOverlay = (label: string): OverlaySpec => ([
|
|
||||||
'Label',
|
|
||||||
{
|
|
||||||
id: OVERLAY_OUTPUT_NAME_LABEL,
|
|
||||||
location: [1.9, 0.5],
|
|
||||||
label,
|
|
||||||
cssClass: 'node-output-endpoint-label',
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const addOverlays = (connection: Connection, overlays: OverlaySpec[]) => {
|
|
||||||
overlays.forEach((overlay: OverlaySpec) => {
|
|
||||||
connection.addOverlay(overlay);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getLeftmostTopNode = (nodes: INodeUi[]): INodeUi => {
|
|
||||||
return nodes.reduce((leftmostTop, node) => {
|
|
||||||
if (node.position[0] > leftmostTop.position[0] || node.position[1] > leftmostTop.position[1]) {
|
|
||||||
return leftmostTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWorkflowCorners = (nodes: INodeUi[]): IBounds => {
|
|
||||||
return nodes.reduce((accu: IBounds, node: INodeUi) => {
|
|
||||||
const hasCustomDimensions = [STICKY_NODE_TYPE, PLACEHOLDER_BUTTON].includes(node.type);
|
|
||||||
const xOffset = hasCustomDimensions && isNumber(node.parameters.width) ? node.parameters.width : NODE_SIZE;
|
|
||||||
const yOffset = hasCustomDimensions && isNumber(node.parameters.height) ? node.parameters.height : NODE_SIZE;
|
|
||||||
|
|
||||||
const x = node.position[0];
|
|
||||||
const y = node.position[1];
|
|
||||||
|
|
||||||
if (x < accu.minX) {
|
|
||||||
accu.minX = x;
|
|
||||||
}
|
|
||||||
if (y < accu.minY) {
|
|
||||||
accu.minY = y;
|
|
||||||
}
|
|
||||||
if ((x + xOffset) > accu.maxX) {
|
|
||||||
accu.maxX = x + xOffset;
|
|
||||||
}
|
|
||||||
if ((y + yOffset) > accu.maxY) {
|
|
||||||
accu.maxY = y + yOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return accu;
|
|
||||||
}, {
|
|
||||||
minX: nodes[0].position[0],
|
|
||||||
minY: nodes[0].position[1],
|
|
||||||
maxX: nodes[0].position[0],
|
|
||||||
maxY: nodes[0].position[1],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const scaleSmaller = ({scale, offset: [xOffset, yOffset]}: IZoomConfig): IZoomConfig => {
|
export const scaleSmaller = ({scale, offset: [xOffset, yOffset]}: IZoomConfig): IZoomConfig => {
|
||||||
scale /= 1.25;
|
scale /= 1.25;
|
||||||
xOffset /= 1.25;
|
xOffset /= 1.25;
|
||||||
|
@ -313,133 +53,8 @@ export const scaleReset = (config: IZoomConfig): IZoomConfig => {
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
|
||||||
try {
|
|
||||||
return item.getOverlay(overlayId); // handle when _jsPlumb element is deleted
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
export const closestNumberDivisibleBy = (inputNumber: number, divisibleBy: number): number => {
|
||||||
const overlay = getOverlay(item, overlayId);
|
|
||||||
if (overlay) {
|
|
||||||
overlay.setVisible(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hideOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
|
||||||
const overlay = getOverlay(item, overlayId);
|
|
||||||
if (overlay) {
|
|
||||||
overlay.setVisible(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showOrHideMidpointArrow = (connection: Connection) => {
|
|
||||||
if (!connection || !connection.endpoints || connection.endpoints.length !== 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasItemsLabel = !!getOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
|
||||||
|
|
||||||
const sourceEndpoint = connection.endpoints[0];
|
|
||||||
const targetEndpoint = connection.endpoints[1];
|
|
||||||
|
|
||||||
const sourcePosition = sourceEndpoint.anchor.lastReturnValue[0];
|
|
||||||
const targetPosition = targetEndpoint.anchor.lastReturnValue ? targetEndpoint.anchor.lastReturnValue[0] : sourcePosition + 1; // lastReturnValue is null when moving connections from node to another
|
|
||||||
|
|
||||||
const minimum = hasItemsLabel ? 150 : 0;
|
|
||||||
const isBackwards = sourcePosition >= targetPosition;
|
|
||||||
const isTooLong = Math.abs(sourcePosition - targetPosition) >= minimum;
|
|
||||||
|
|
||||||
const arrow = getOverlay(connection, OVERLAY_MIDPOINT_ARROW_ID);
|
|
||||||
if (arrow) {
|
|
||||||
arrow.setVisible(isBackwards && isTooLong);
|
|
||||||
arrow.setLocation(hasItemsLabel ? .6: .5);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getConnectorLengths = (connection: Connection): [number, number] => {
|
|
||||||
if (!connection.connector) {
|
|
||||||
return [0, 0];
|
|
||||||
}
|
|
||||||
const bounds = connection.connector.bounds;
|
|
||||||
const diffX = Math.abs(bounds.maxX - bounds.minX);
|
|
||||||
const diffY = Math.abs(bounds.maxY - bounds.minY);
|
|
||||||
|
|
||||||
return [diffX, diffY];
|
|
||||||
};
|
|
||||||
|
|
||||||
const isLoopingBackwards = (connection: Connection) => {
|
|
||||||
const sourceEndpoint = connection.endpoints[0];
|
|
||||||
const targetEndpoint = connection.endpoints[1];
|
|
||||||
|
|
||||||
const sourcePosition = sourceEndpoint.anchor.lastReturnValue[0];
|
|
||||||
const targetPosition = targetEndpoint.anchor.lastReturnValue[0];
|
|
||||||
|
|
||||||
return targetPosition - sourcePosition < (-1 * LOOPBACK_MINIMUM);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showOrHideItemsLabel = (connection: Connection) => {
|
|
||||||
if (!connection || !connection.connector) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlay = getOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
|
||||||
if (!overlay) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionsOverlay = getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
|
||||||
if (actionsOverlay && actionsOverlay.visible) {
|
|
||||||
overlay.setVisible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [diffX, diffY] = getConnectorLengths(connection);
|
|
||||||
|
|
||||||
if (diffX < MIN_X_TO_SHOW_OUTPUT_LABEL && diffY < MIN_Y_TO_SHOW_OUTPUT_LABEL) {
|
|
||||||
overlay.setVisible(false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
overlay.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerElement = overlay.canvas && overlay.canvas.querySelector('span');
|
|
||||||
if (innerElement) {
|
|
||||||
if (diffY === 0 || isLoopingBackwards(connection)) {
|
|
||||||
innerElement.classList.add('floating');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
innerElement.classList.remove('floating');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getIcon = (name: string): string => {
|
|
||||||
if (name === 'trash') {
|
|
||||||
return `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trash" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-trash fa-w-14 Icon__medium_ctPPJ"><path data-v-66d5c7e2="" fill="currentColor" d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z" class=""></path></svg>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'plus') {
|
|
||||||
return `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plus" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-plus fa-w-14 Icon__medium_ctPPJ"><path data-v-301ed208="" fill="currentColor" d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z" class=""></path></svg>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const canUsePosition = (position1: XYPosition, position2: XYPosition) => {
|
|
||||||
if (Math.abs(position1[0] - position2[0]) <= 100) {
|
|
||||||
if (Math.abs(position1[1] - position2[1]) <= 50) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
function closestNumberDivisibleBy(inputNumber: number, divisibleBy: number) {
|
|
||||||
const quotient = Math.ceil(inputNumber / divisibleBy);
|
const quotient = Math.ceil(inputNumber / divisibleBy);
|
||||||
|
|
||||||
// 1st possible closest number
|
// 1st possible closest number
|
||||||
|
@ -455,334 +70,6 @@ function closestNumberDivisibleBy(inputNumber: number, divisibleBy: number) {
|
||||||
|
|
||||||
// else inputNumber2 is the required closest number
|
// else inputNumber2 is the required closest number
|
||||||
return inputNumber2;
|
return inputNumber2;
|
||||||
}
|
|
||||||
|
|
||||||
export const getNewNodePosition = (nodes: INodeUi[], newPosition: XYPosition, movePosition?: XYPosition): XYPosition => {
|
|
||||||
const targetPosition: XYPosition = [...newPosition];
|
|
||||||
|
|
||||||
targetPosition[0] = closestNumberDivisibleBy(targetPosition[0], GRID_SIZE);
|
|
||||||
targetPosition[1] = closestNumberDivisibleBy(targetPosition[1], GRID_SIZE);
|
|
||||||
|
|
||||||
if (!movePosition) {
|
|
||||||
movePosition = [40, 40];
|
|
||||||
}
|
|
||||||
|
|
||||||
let conflictFound = false;
|
|
||||||
let i, node;
|
|
||||||
do {
|
|
||||||
conflictFound = false;
|
|
||||||
for (i = 0; i < nodes.length; i++) {
|
|
||||||
node = nodes[i];
|
|
||||||
if (!canUsePosition(node.position, targetPosition)) {
|
|
||||||
conflictFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conflictFound === true) {
|
|
||||||
targetPosition[0] += movePosition[0];
|
|
||||||
targetPosition[1] += movePosition[1];
|
|
||||||
}
|
|
||||||
} while (conflictFound === true);
|
|
||||||
|
|
||||||
return targetPosition;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMousePosition = (e: MouseEvent | TouchEvent): XYPosition => {
|
|
||||||
// @ts-ignore
|
|
||||||
const x = e.pageX !== undefined ? e.pageX : (e.touches && e.touches[0] && e.touches[0].pageX ? e.touches[0].pageX : 0);
|
|
||||||
// @ts-ignore
|
|
||||||
const y = e.pageY !== undefined ? e.pageY : (e.touches && e.touches[0] && e.touches[0].pageY ? e.touches[0].pageY : 0);
|
|
||||||
|
|
||||||
return [x, y];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRelativePosition = (x: number, y: number, scale: number, offset: XYPosition): XYPosition => {
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getBackgroundStyles = (scale: number, offsetPosition: XYPosition, executionPreview: boolean) => {
|
|
||||||
const squareSize = GRID_SIZE * scale;
|
|
||||||
const dotSize = 1 * scale;
|
|
||||||
const dotPosition = (GRID_SIZE / 2) * scale;
|
|
||||||
|
|
||||||
if (executionPreview) {
|
|
||||||
return {
|
|
||||||
'background-image': 'linear-gradient(135deg, #f9f9fb 25%, #ffffff 25%, #ffffff 50%, #f9f9fb 50%, #f9f9fb 75%, #ffffff 75%, #ffffff 100%)',
|
|
||||||
'background-size': `${squareSize}px ${squareSize}px`,
|
|
||||||
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles: object = {
|
|
||||||
'background-size': `${squareSize}px ${squareSize}px`,
|
|
||||||
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
|
|
||||||
};
|
|
||||||
if (squareSize > 10.5) {
|
|
||||||
const dotColor = getStyleTokenValue('--color-canvas-dot');
|
|
||||||
return {
|
|
||||||
...styles,
|
|
||||||
'background-image': `radial-gradient(circle at ${dotPosition}px ${dotPosition}px, ${dotColor} ${dotSize}px, transparent 0)`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hideConnectionActions = (connection: Connection | null) => {
|
|
||||||
if (connection && connection.connector) {
|
|
||||||
hideOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
|
||||||
showOrHideItemsLabel(connection);
|
|
||||||
showOrHideMidpointArrow(connection);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showConnectionActions = (connection: Connection | null) => {
|
|
||||||
if (connection && connection.connector) {
|
|
||||||
showOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
|
||||||
hideOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
|
||||||
if (!getOverlay(connection, OVERLAY_RUN_ITEMS_ID)) {
|
|
||||||
hideOverlay(connection, OVERLAY_MIDPOINT_ARROW_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputConnections) => {
|
|
||||||
const outputMap: {[sourceOutputIndex: string]: {[targetNodeName: string]: {[targetInputIndex: string]: {total: number, iterations: number}}}} = {};
|
|
||||||
|
|
||||||
data.forEach((run: ITaskData) => {
|
|
||||||
if (!run.data || !run.data.main) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
run.data.main.forEach((output: INodeExecutionData[] | null, i: number) => {
|
|
||||||
const sourceOutputIndex = i;
|
|
||||||
|
|
||||||
if (!outputMap[sourceOutputIndex]) {
|
|
||||||
outputMap[sourceOutputIndex] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY]) {
|
|
||||||
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY] = {};
|
|
||||||
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0] = {
|
|
||||||
total: 0,
|
|
||||||
iterations: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultOutput = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
|
||||||
defaultOutput.total += output ? output.length : 0;
|
|
||||||
defaultOutput.iterations += output ? 1 : 0;
|
|
||||||
|
|
||||||
if (!nodeConnections[sourceOutputIndex]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeConnections[sourceOutputIndex]
|
|
||||||
.map((connection: IConnection) => {
|
|
||||||
const targetNodeName = connection.node;
|
|
||||||
const targetInputIndex = connection.index;
|
|
||||||
|
|
||||||
if (!outputMap[sourceOutputIndex][targetNodeName]) {
|
|
||||||
outputMap[sourceOutputIndex][targetNodeName] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outputMap[sourceOutputIndex][targetNodeName][targetInputIndex]) {
|
|
||||||
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex] = {
|
|
||||||
total: 0,
|
|
||||||
iterations: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].total += output ? output.length : 0;
|
|
||||||
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].iterations += output ? 1 : 0;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return outputMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetConnection = (connection: Connection) => {
|
|
||||||
connection.removeOverlay(OVERLAY_RUN_ITEMS_ID);
|
|
||||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_DEFAULT);
|
|
||||||
showOrHideMidpointArrow(connection);
|
|
||||||
if (connection.canvas) {
|
|
||||||
connection.canvas.classList.remove('success');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRunItemsLabel = (output: {total: number, iterations: number}): string => {
|
|
||||||
let label = `${output.total}`;
|
|
||||||
label = output.total > 1 ? `${label} items` : `${label} item`;
|
|
||||||
label = output.iterations > 1 ? `${label} total` : label;
|
|
||||||
return label;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addConnectionOutputSuccess = (connection: Connection, output: {total: number, iterations: number}) => {
|
|
||||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_SUCCESS);
|
|
||||||
if (connection.canvas) {
|
|
||||||
connection.canvas.classList.add('success');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getOverlay(connection, OVERLAY_RUN_ITEMS_ID)) {
|
|
||||||
connection.removeOverlay(OVERLAY_RUN_ITEMS_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.addOverlay([
|
|
||||||
'Label',
|
|
||||||
{
|
|
||||||
id: OVERLAY_RUN_ITEMS_ID,
|
|
||||||
label: `<span>${getRunItemsLabel(output)}</span>`,
|
|
||||||
cssClass: 'connection-run-items-label',
|
|
||||||
location: .5,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
showOrHideItemsLabel(connection);
|
|
||||||
showOrHideMidpointArrow(connection);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const getContentDimensions = (): { editorWidth: number, editorHeight: number } => {
|
|
||||||
let contentWidth = window.innerWidth;
|
|
||||||
let contentHeight = window.innerHeight;
|
|
||||||
const nodeViewRoot = document.getElementById('node-view-root');
|
|
||||||
|
|
||||||
if (nodeViewRoot) {
|
|
||||||
const contentBounds = nodeViewRoot.getBoundingClientRect();
|
|
||||||
contentWidth = contentBounds.width;
|
|
||||||
contentHeight = contentBounds.height;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
editorWidth: contentWidth,
|
|
||||||
editorHeight: contentHeight,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getZoomToFit = (nodes: INodeUi[], addFooterPadding = true): {offset: XYPosition, zoomLevel: number} => {
|
|
||||||
const { minX, minY, maxX, maxY } = getWorkflowCorners(nodes);
|
|
||||||
const { editorWidth, editorHeight } = getContentDimensions();
|
|
||||||
const footerHeight = addFooterPadding ? 200 : 100;
|
|
||||||
|
|
||||||
const PADDING = NODE_SIZE * 4;
|
|
||||||
|
|
||||||
const diffX = maxX - minX + PADDING;
|
|
||||||
const scaleX = editorWidth / diffX;
|
|
||||||
|
|
||||||
const diffY = maxY - minY + PADDING;
|
|
||||||
const scaleY = editorHeight / diffY;
|
|
||||||
|
|
||||||
const zoomLevel = Math.min(scaleX, scaleY, 1);
|
|
||||||
|
|
||||||
let xOffset = (minX * -1) * zoomLevel; // find top right corner
|
|
||||||
xOffset += (editorWidth - (maxX - minX) * zoomLevel) / 2; // add padding to center workflow
|
|
||||||
|
|
||||||
let yOffset = (minY * -1) * zoomLevel; // find top right corner
|
|
||||||
yOffset += (editorHeight - (maxY - minY + footerHeight) * zoomLevel) / 2; // add padding to center workflow
|
|
||||||
|
|
||||||
return {
|
|
||||||
zoomLevel,
|
|
||||||
offset: [closestNumberDivisibleBy(xOffset, GRID_SIZE), closestNumberDivisibleBy(yOffset, GRID_SIZE)],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showDropConnectionState = (connection: Connection, targetEndpoint?: Endpoint) => {
|
|
||||||
if (connection && connection.connector) {
|
|
||||||
if (targetEndpoint) {
|
|
||||||
connection.connector.setTargetEndpoint(targetEndpoint);
|
|
||||||
}
|
|
||||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_PRIMARY);
|
|
||||||
hideOverlay(connection, OVERLAY_DROP_NODE_ID);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showPullConnectionState = (connection: Connection) => {
|
|
||||||
if (connection && connection.connector) {
|
|
||||||
connection.connector.resetTargetEndpoint();
|
|
||||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_PULL);
|
|
||||||
showOverlay(connection, OVERLAY_DROP_NODE_ID);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetConnectionAfterPull = (connection: Connection) => {
|
|
||||||
if (connection && connection.connector) {
|
|
||||||
connection.connector.resetTargetEndpoint();
|
|
||||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_DEFAULT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetInputLabelPosition = (targetEndpoint: Endpoint) => {
|
|
||||||
const inputNameOverlay = getOverlay(targetEndpoint, OVERLAY_INPUT_NAME_LABEL);
|
|
||||||
if (inputNameOverlay) {
|
|
||||||
inputNameOverlay.setLocation(OVERLAY_INPUT_NAME_LABEL_POSITION);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const moveBackInputLabelPosition = (targetEndpoint: Endpoint) => {
|
|
||||||
const inputNameOverlay = getOverlay(targetEndpoint, OVERLAY_INPUT_NAME_LABEL);
|
|
||||||
if (inputNameOverlay) {
|
|
||||||
inputNameOverlay.setLocation(OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addConnectionActionsOverlay = (connection: Connection, onDelete: Function, onAdd: Function) => {
|
|
||||||
if (getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID)) {
|
|
||||||
return; // avoid free floating actions when moving connection from one node to another
|
|
||||||
}
|
|
||||||
connection.addOverlay([
|
|
||||||
'Label',
|
|
||||||
{
|
|
||||||
id: OVERLAY_CONNECTION_ACTIONS_ID,
|
|
||||||
label: `<div class="add">${getIcon('plus')}</div> <div class="delete">${getIcon('trash')}</div>`,
|
|
||||||
cssClass: OVERLAY_CONNECTION_ACTIONS_ID,
|
|
||||||
visible: false,
|
|
||||||
events: {
|
|
||||||
mousedown: (overlay: Overlay, event: MouseEvent) => {
|
|
||||||
const element = event.target as HTMLElement;
|
|
||||||
if (element.classList.contains('delete') || (element.parentElement && element.parentElement.classList.contains('delete'))) {
|
|
||||||
onDelete();
|
|
||||||
}
|
|
||||||
else if (element.classList.contains('add') || (element.parentElement && element.parentElement.classList.contains('add'))) {
|
|
||||||
onAdd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOutputEndpointUUID = (nodeId: string, outputIndex: number) => {
|
|
||||||
return `${nodeId}${OUTPUT_UUID_KEY}${outputIndex}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInputEndpointUUID = (nodeId: string, inputIndex: number) => {
|
|
||||||
return `${nodeId}${INPUT_UUID_KEY}${inputIndex}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFixedNodesList = (workflowNodes: INode[]) => {
|
|
||||||
const nodes = [...workflowNodes];
|
|
||||||
|
|
||||||
const leftmostTop = getLeftmostTopNode(nodes);
|
|
||||||
|
|
||||||
const diffX = DEFAULT_START_POSITION_X - leftmostTop.position[0];
|
|
||||||
const diffY = DEFAULT_START_POSITION_Y - leftmostTop.position[1];
|
|
||||||
|
|
||||||
nodes.map((node) => {
|
|
||||||
node.position[0] += diffX + (NODE_SIZE * 2);
|
|
||||||
node.position[1] += diffY;
|
|
||||||
});
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodeViewTab = (route: Route): string|null => {
|
export const getNodeViewTab = (route: Route): string|null => {
|
||||||
|
|
725
packages/editor-ui/src/utils/nodeViewUtils.ts
Normal file
725
packages/editor-ui/src/utils/nodeViewUtils.ts
Normal file
|
@ -0,0 +1,725 @@
|
||||||
|
import { closestNumberDivisibleBy, getStyleTokenValue, isNumber } from "@/utils";
|
||||||
|
import { NODE_OUTPUT_DEFAULT_KEY, STICKY_NODE_TYPE, QUICKSTART_NOTE_NAME } from "@/constants";
|
||||||
|
import { EndpointStyle, IBounds, INodeUi, XYPosition } from "@/Interface";
|
||||||
|
import { AnchorArraySpec, Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
||||||
|
import {
|
||||||
|
IConnection,
|
||||||
|
INode,
|
||||||
|
ITaskData,
|
||||||
|
INodeExecutionData,
|
||||||
|
NodeInputConnections,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Canvas constants and functions.
|
||||||
|
These utils are not exported with main `utils`package because they need to be used
|
||||||
|
on-demand (when jsplumb instance is ready) by components (mainly the NodeView).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const OVERLAY_DROP_NODE_ID = 'drop-add-node';
|
||||||
|
export const OVERLAY_MIDPOINT_ARROW_ID = 'midpoint-arrow';
|
||||||
|
export const OVERLAY_ENDPOINT_ARROW_ID = 'endpoint-arrow';
|
||||||
|
export const OVERLAY_RUN_ITEMS_ID = 'run-items-label';
|
||||||
|
export const OVERLAY_CONNECTION_ACTIONS_ID = 'connection-actions';
|
||||||
|
export const JSPLUMB_FLOWCHART_STUB = 26;
|
||||||
|
export const OVERLAY_INPUT_NAME_LABEL = 'input-name-label';
|
||||||
|
export const OVERLAY_INPUT_NAME_LABEL_POSITION = [-3, .5];
|
||||||
|
export const OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED = [-4.5, .5];
|
||||||
|
export const OVERLAY_OUTPUT_NAME_LABEL = 'output-name-label';
|
||||||
|
export const GRID_SIZE = 20;
|
||||||
|
|
||||||
|
const MIN_X_TO_SHOW_OUTPUT_LABEL = 90;
|
||||||
|
const MIN_Y_TO_SHOW_OUTPUT_LABEL = 100;
|
||||||
|
|
||||||
|
export const NODE_SIZE = 100;
|
||||||
|
export const PLACEHOLDER_TRIGGER_NODE_SIZE = 100;
|
||||||
|
export const DEFAULT_START_POSITION_X = 180;
|
||||||
|
export const DEFAULT_START_POSITION_Y = 240;
|
||||||
|
export const HEADER_HEIGHT = 65;
|
||||||
|
export const SIDEBAR_WIDTH = 65;
|
||||||
|
export const INNER_SIDEBAR_WIDTH = 310;
|
||||||
|
export const SIDEBAR_WIDTH_EXPANDED = 200;
|
||||||
|
export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300;
|
||||||
|
export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE;
|
||||||
|
const LOOPBACK_MINIMUM = 140;
|
||||||
|
export const INPUT_UUID_KEY = '-input';
|
||||||
|
export const OUTPUT_UUID_KEY = '-output';
|
||||||
|
export const PLACEHOLDER_BUTTON = 'PlaceholderTriggerButton';
|
||||||
|
|
||||||
|
export const DEFAULT_PLACEHOLDER_TRIGGER_BUTTON = {
|
||||||
|
name: 'Choose a Trigger...',
|
||||||
|
type: PLACEHOLDER_BUTTON,
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [],
|
||||||
|
parameters: {
|
||||||
|
height: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
||||||
|
width: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WELCOME_STICKY_NODE = {
|
||||||
|
name: QUICKSTART_NOTE_NAME,
|
||||||
|
type: STICKY_NODE_TYPE,
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
] as XYPosition,
|
||||||
|
parameters: {
|
||||||
|
height: 300,
|
||||||
|
width: 380,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTOR_FLOWCHART_TYPE = ['N8nCustom', {
|
||||||
|
cornerRadius: 12,
|
||||||
|
stub: JSPLUMB_FLOWCHART_STUB + 10,
|
||||||
|
targetGap: 4,
|
||||||
|
alwaysRespectStubs: false,
|
||||||
|
loopbackVerticalLength: NODE_SIZE + GRID_SIZE, // height of vertical segment when looping
|
||||||
|
loopbackMinimum: LOOPBACK_MINIMUM, // minimum length before flowchart loops around
|
||||||
|
getEndpointOffset(endpoint: Endpoint) {
|
||||||
|
const indexOffset = 10; // stub offset between different endpoints of same node
|
||||||
|
const index = endpoint && endpoint.__meta ? endpoint.__meta.index : 0;
|
||||||
|
const totalEndpoints = endpoint && endpoint.__meta ? endpoint.__meta.totalEndpoints : 0;
|
||||||
|
|
||||||
|
const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL);
|
||||||
|
const labelOffset = outputOverlay && outputOverlay.label && outputOverlay.label.length > 1 ? 10 : 0;
|
||||||
|
const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus
|
||||||
|
|
||||||
|
return index * indexOffset + labelOffset + outputsOffset;
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
export const CONNECTOR_PAINT_STYLE_DEFAULT: PaintStyle = {
|
||||||
|
stroke: getStyleTokenValue('--color-foreground-dark'),
|
||||||
|
strokeWidth: 2,
|
||||||
|
outlineWidth: 12,
|
||||||
|
outlineStroke: 'transparent',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTOR_PAINT_STYLE_PULL: PaintStyle = {
|
||||||
|
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
||||||
|
stroke: getStyleTokenValue('--color-foreground-xdark'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTOR_PAINT_STYLE_PRIMARY = {
|
||||||
|
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
||||||
|
stroke: getStyleTokenValue('--color-primary'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTOR_PAINT_STYLE_SUCCESS = {
|
||||||
|
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
||||||
|
stroke: getStyleTokenValue('--color-success-light'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [
|
||||||
|
[
|
||||||
|
'Arrow',
|
||||||
|
{
|
||||||
|
id: OVERLAY_ENDPOINT_ARROW_ID,
|
||||||
|
location: 1,
|
||||||
|
width: 12,
|
||||||
|
foldback: 1,
|
||||||
|
length: 10,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Arrow',
|
||||||
|
{
|
||||||
|
id: OVERLAY_MIDPOINT_ARROW_ID,
|
||||||
|
location: 0.5,
|
||||||
|
width: 12,
|
||||||
|
foldback: 1,
|
||||||
|
length: 10,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ANCHOR_POSITIONS: {
|
||||||
|
[key: string]: {
|
||||||
|
[key: number]: AnchorArraySpec[];
|
||||||
|
}
|
||||||
|
} = {
|
||||||
|
input: {
|
||||||
|
1: [
|
||||||
|
[0.01, 0.5, -1, 0],
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
[0.01, 0.3, -1, 0],
|
||||||
|
[0.01, 0.7, -1, 0],
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
[0.01, 0.25, -1, 0],
|
||||||
|
[0.01, 0.5, -1, 0],
|
||||||
|
[0.01, 0.75, -1, 0],
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
[0.01, 0.2, -1, 0],
|
||||||
|
[0.01, 0.4, -1, 0],
|
||||||
|
[0.01, 0.6, -1, 0],
|
||||||
|
[0.01, 0.8, -1, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
1: [
|
||||||
|
[.99, 0.5, 1, 0],
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
[.99, 0.3, 1, 0],
|
||||||
|
[.99, 0.7, 1, 0],
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
[.99, 0.25, 1, 0],
|
||||||
|
[.99, 0.5, 1, 0],
|
||||||
|
[.99, 0.75, 1, 0],
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
[.99, 0.2, 1, 0],
|
||||||
|
[.99, 0.4, 1, 0],
|
||||||
|
[.99, 0.6, 1, 0],
|
||||||
|
[.99, 0.8, 1, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getInputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string): EndpointStyle => ({
|
||||||
|
width: 8,
|
||||||
|
height: nodeTypeData && nodeTypeData.outputs.length > 2 ? 18 : 20,
|
||||||
|
fill: getStyleTokenValue(color),
|
||||||
|
stroke: getStyleTokenValue(color),
|
||||||
|
lineWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getInputNameOverlay = (label: string): OverlaySpec => ([
|
||||||
|
'Label',
|
||||||
|
{
|
||||||
|
id: OVERLAY_INPUT_NAME_LABEL,
|
||||||
|
location: OVERLAY_INPUT_NAME_LABEL_POSITION,
|
||||||
|
label,
|
||||||
|
cssClass: 'node-input-endpoint-label',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const getOutputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string) => ({
|
||||||
|
radius: nodeTypeData && nodeTypeData.outputs.length > 2 ? 7 : 9,
|
||||||
|
fill: getStyleTokenValue(color),
|
||||||
|
outlineStroke: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getOutputNameOverlay = (label: string): OverlaySpec => ([
|
||||||
|
'Label',
|
||||||
|
{
|
||||||
|
id: OVERLAY_OUTPUT_NAME_LABEL,
|
||||||
|
location: [1.9, 0.5],
|
||||||
|
label,
|
||||||
|
cssClass: 'node-output-endpoint-label',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const addOverlays = (connection: Connection, overlays: OverlaySpec[]) => {
|
||||||
|
overlays.forEach((overlay: OverlaySpec) => {
|
||||||
|
connection.addOverlay(overlay);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLeftmostTopNode = (nodes: INodeUi[]): INodeUi => {
|
||||||
|
return nodes.reduce((leftmostTop, node) => {
|
||||||
|
if (node.position[0] > leftmostTop.position[0] || node.position[1] > leftmostTop.position[1]) {
|
||||||
|
return leftmostTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWorkflowCorners = (nodes: INodeUi[]): IBounds => {
|
||||||
|
return nodes.reduce((accu: IBounds, node: INodeUi) => {
|
||||||
|
const hasCustomDimensions = [STICKY_NODE_TYPE, PLACEHOLDER_BUTTON].includes(node.type);
|
||||||
|
const xOffset = hasCustomDimensions && isNumber(node.parameters.width) ? node.parameters.width : NODE_SIZE;
|
||||||
|
const yOffset = hasCustomDimensions && isNumber(node.parameters.height) ? node.parameters.height : NODE_SIZE;
|
||||||
|
|
||||||
|
const x = node.position[0];
|
||||||
|
const y = node.position[1];
|
||||||
|
|
||||||
|
if (x < accu.minX) {
|
||||||
|
accu.minX = x;
|
||||||
|
}
|
||||||
|
if (y < accu.minY) {
|
||||||
|
accu.minY = y;
|
||||||
|
}
|
||||||
|
if ((x + xOffset) > accu.maxX) {
|
||||||
|
accu.maxX = x + xOffset;
|
||||||
|
}
|
||||||
|
if ((y + yOffset) > accu.maxY) {
|
||||||
|
accu.maxY = y + yOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accu;
|
||||||
|
}, {
|
||||||
|
minX: nodes[0].position[0],
|
||||||
|
minY: nodes[0].position[1],
|
||||||
|
maxX: nodes[0].position[0],
|
||||||
|
maxY: nodes[0].position[1],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
||||||
|
try {
|
||||||
|
return item.getOverlay(overlayId); // handle when _jsPlumb element is deleted
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
||||||
|
const overlay = getOverlay(item, overlayId);
|
||||||
|
if (overlay) {
|
||||||
|
overlay.setVisible(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hideOverlay = (item: Connection | Endpoint, overlayId: string) => {
|
||||||
|
const overlay = getOverlay(item, overlayId);
|
||||||
|
if (overlay) {
|
||||||
|
overlay.setVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showOrHideMidpointArrow = (connection: Connection) => {
|
||||||
|
if (!connection || !connection.endpoints || connection.endpoints.length !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasItemsLabel = !!getOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
||||||
|
|
||||||
|
const sourceEndpoint = connection.endpoints[0];
|
||||||
|
const targetEndpoint = connection.endpoints[1];
|
||||||
|
|
||||||
|
const sourcePosition = sourceEndpoint.anchor.lastReturnValue[0];
|
||||||
|
const targetPosition = targetEndpoint.anchor.lastReturnValue ? targetEndpoint.anchor.lastReturnValue[0] : sourcePosition + 1; // lastReturnValue is null when moving connections from node to another
|
||||||
|
|
||||||
|
const minimum = hasItemsLabel ? 150 : 0;
|
||||||
|
const isBackwards = sourcePosition >= targetPosition;
|
||||||
|
const isTooLong = Math.abs(sourcePosition - targetPosition) >= minimum;
|
||||||
|
|
||||||
|
const arrow = getOverlay(connection, OVERLAY_MIDPOINT_ARROW_ID);
|
||||||
|
if (arrow) {
|
||||||
|
arrow.setVisible(isBackwards && isTooLong);
|
||||||
|
arrow.setLocation(hasItemsLabel ? .6: .5);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConnectorLengths = (connection: Connection): [number, number] => {
|
||||||
|
if (!connection.connector) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
const bounds = connection.connector.bounds;
|
||||||
|
const diffX = Math.abs(bounds.maxX - bounds.minX);
|
||||||
|
const diffY = Math.abs(bounds.maxY - bounds.minY);
|
||||||
|
|
||||||
|
return [diffX, diffY];
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoopingBackwards = (connection: Connection) => {
|
||||||
|
const sourceEndpoint = connection.endpoints[0];
|
||||||
|
const targetEndpoint = connection.endpoints[1];
|
||||||
|
|
||||||
|
const sourcePosition = sourceEndpoint.anchor.lastReturnValue[0];
|
||||||
|
const targetPosition = targetEndpoint.anchor.lastReturnValue[0];
|
||||||
|
|
||||||
|
return targetPosition - sourcePosition < (-1 * LOOPBACK_MINIMUM);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showOrHideItemsLabel = (connection: Connection) => {
|
||||||
|
if (!connection || !connection.connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlay = getOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
||||||
|
if (!overlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionsOverlay = getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
||||||
|
if (actionsOverlay && actionsOverlay.visible) {
|
||||||
|
overlay.setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [diffX, diffY] = getConnectorLengths(connection);
|
||||||
|
|
||||||
|
if (diffX < MIN_X_TO_SHOW_OUTPUT_LABEL && diffY < MIN_Y_TO_SHOW_OUTPUT_LABEL) {
|
||||||
|
overlay.setVisible(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
overlay.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const innerElement = overlay.canvas && overlay.canvas.querySelector('span');
|
||||||
|
if (innerElement) {
|
||||||
|
if (diffY === 0 || isLoopingBackwards(connection)) {
|
||||||
|
innerElement.classList.add('floating');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
innerElement.classList.remove('floating');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getIcon = (name: string): string => {
|
||||||
|
if (name === 'trash') {
|
||||||
|
return `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trash" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-trash fa-w-14 Icon__medium_ctPPJ"><path data-v-66d5c7e2="" fill="currentColor" d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z" class=""></path></svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'plus') {
|
||||||
|
return `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plus" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-plus fa-w-14 Icon__medium_ctPPJ"><path data-v-301ed208="" fill="currentColor" d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z" class=""></path></svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const canUsePosition = (position1: XYPosition, position2: XYPosition) => {
|
||||||
|
if (Math.abs(position1[0] - position2[0]) <= 100) {
|
||||||
|
if (Math.abs(position1[1] - position2[1]) <= 50) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNewNodePosition = (nodes: INodeUi[], newPosition: XYPosition, movePosition?: XYPosition): XYPosition => {
|
||||||
|
const targetPosition: XYPosition = [...newPosition];
|
||||||
|
|
||||||
|
targetPosition[0] = closestNumberDivisibleBy(targetPosition[0], GRID_SIZE);
|
||||||
|
targetPosition[1] = closestNumberDivisibleBy(targetPosition[1], GRID_SIZE);
|
||||||
|
|
||||||
|
if (!movePosition) {
|
||||||
|
movePosition = [40, 40];
|
||||||
|
}
|
||||||
|
|
||||||
|
let conflictFound = false;
|
||||||
|
let i, node;
|
||||||
|
do {
|
||||||
|
conflictFound = false;
|
||||||
|
for (i = 0; i < nodes.length; i++) {
|
||||||
|
node = nodes[i];
|
||||||
|
if (!canUsePosition(node.position, targetPosition)) {
|
||||||
|
conflictFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conflictFound === true) {
|
||||||
|
targetPosition[0] += movePosition[0];
|
||||||
|
targetPosition[1] += movePosition[1];
|
||||||
|
}
|
||||||
|
} while (conflictFound === true);
|
||||||
|
|
||||||
|
return targetPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMousePosition = (e: MouseEvent | TouchEvent): XYPosition => {
|
||||||
|
// @ts-ignore
|
||||||
|
const x = e.pageX !== undefined ? e.pageX : (e.touches && e.touches[0] && e.touches[0].pageX ? e.touches[0].pageX : 0);
|
||||||
|
// @ts-ignore
|
||||||
|
const y = e.pageY !== undefined ? e.pageY : (e.touches && e.touches[0] && e.touches[0].pageY ? e.touches[0].pageY : 0);
|
||||||
|
|
||||||
|
return [x, y];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRelativePosition = (x: number, y: number, scale: number, offset: XYPosition): XYPosition => {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBackgroundStyles = (scale: number, offsetPosition: XYPosition, executionPreview: boolean) => {
|
||||||
|
const squareSize = GRID_SIZE * scale;
|
||||||
|
const dotSize = 1 * scale;
|
||||||
|
const dotPosition = (GRID_SIZE / 2) * scale;
|
||||||
|
|
||||||
|
if (executionPreview) {
|
||||||
|
return {
|
||||||
|
'background-image': 'linear-gradient(135deg, #f9f9fb 25%, #ffffff 25%, #ffffff 50%, #f9f9fb 50%, #f9f9fb 75%, #ffffff 75%, #ffffff 100%)',
|
||||||
|
'background-size': `${squareSize}px ${squareSize}px`,
|
||||||
|
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles: object = {
|
||||||
|
'background-size': `${squareSize}px ${squareSize}px`,
|
||||||
|
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
|
||||||
|
};
|
||||||
|
if (squareSize > 10.5) {
|
||||||
|
const dotColor = getStyleTokenValue('--color-canvas-dot');
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
'background-image': `radial-gradient(circle at ${dotPosition}px ${dotPosition}px, ${dotColor} ${dotSize}px, transparent 0)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hideConnectionActions = (connection: Connection | null) => {
|
||||||
|
if (connection && connection.connector) {
|
||||||
|
hideOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
||||||
|
showOrHideItemsLabel(connection);
|
||||||
|
showOrHideMidpointArrow(connection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showConnectionActions = (connection: Connection | null) => {
|
||||||
|
if (connection && connection.connector) {
|
||||||
|
showOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID);
|
||||||
|
hideOverlay(connection, OVERLAY_RUN_ITEMS_ID);
|
||||||
|
if (!getOverlay(connection, OVERLAY_RUN_ITEMS_ID)) {
|
||||||
|
hideOverlay(connection, OVERLAY_MIDPOINT_ARROW_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputConnections) => {
|
||||||
|
const outputMap: {[sourceOutputIndex: string]: {[targetNodeName: string]: {[targetInputIndex: string]: {total: number, iterations: number}}}} = {};
|
||||||
|
|
||||||
|
data.forEach((run: ITaskData) => {
|
||||||
|
if (!run.data || !run.data.main) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
run.data.main.forEach((output: INodeExecutionData[] | null, i: number) => {
|
||||||
|
const sourceOutputIndex = i;
|
||||||
|
|
||||||
|
if (!outputMap[sourceOutputIndex]) {
|
||||||
|
outputMap[sourceOutputIndex] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY]) {
|
||||||
|
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY] = {};
|
||||||
|
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0] = {
|
||||||
|
total: 0,
|
||||||
|
iterations: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOutput = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
||||||
|
defaultOutput.total += output ? output.length : 0;
|
||||||
|
defaultOutput.iterations += output ? 1 : 0;
|
||||||
|
|
||||||
|
if (!nodeConnections[sourceOutputIndex]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeConnections[sourceOutputIndex]
|
||||||
|
.map((connection: IConnection) => {
|
||||||
|
const targetNodeName = connection.node;
|
||||||
|
const targetInputIndex = connection.index;
|
||||||
|
|
||||||
|
if (!outputMap[sourceOutputIndex][targetNodeName]) {
|
||||||
|
outputMap[sourceOutputIndex][targetNodeName] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputMap[sourceOutputIndex][targetNodeName][targetInputIndex]) {
|
||||||
|
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex] = {
|
||||||
|
total: 0,
|
||||||
|
iterations: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].total += output ? output.length : 0;
|
||||||
|
outputMap[sourceOutputIndex][targetNodeName][targetInputIndex].iterations += output ? 1 : 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetConnection = (connection: Connection) => {
|
||||||
|
connection.removeOverlay(OVERLAY_RUN_ITEMS_ID);
|
||||||
|
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_DEFAULT);
|
||||||
|
showOrHideMidpointArrow(connection);
|
||||||
|
if (connection.canvas) {
|
||||||
|
connection.canvas.classList.remove('success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRunItemsLabel = (output: {total: number, iterations: number}): string => {
|
||||||
|
let label = `${output.total}`;
|
||||||
|
label = output.total > 1 ? `${label} items` : `${label} item`;
|
||||||
|
label = output.iterations > 1 ? `${label} total` : label;
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addConnectionOutputSuccess = (connection: Connection, output: {total: number, iterations: number}) => {
|
||||||
|
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_SUCCESS);
|
||||||
|
if (connection.canvas) {
|
||||||
|
connection.canvas.classList.add('success');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getOverlay(connection, OVERLAY_RUN_ITEMS_ID)) {
|
||||||
|
connection.removeOverlay(OVERLAY_RUN_ITEMS_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.addOverlay([
|
||||||
|
'Label',
|
||||||
|
{
|
||||||
|
id: OVERLAY_RUN_ITEMS_ID,
|
||||||
|
label: `<span>${getRunItemsLabel(output)}</span>`,
|
||||||
|
cssClass: 'connection-run-items-label',
|
||||||
|
location: .5,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
showOrHideItemsLabel(connection);
|
||||||
|
showOrHideMidpointArrow(connection);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getContentDimensions = (): { editorWidth: number, editorHeight: number } => {
|
||||||
|
let contentWidth = window.innerWidth;
|
||||||
|
let contentHeight = window.innerHeight;
|
||||||
|
const nodeViewRoot = document.getElementById('node-view-root');
|
||||||
|
|
||||||
|
if (nodeViewRoot) {
|
||||||
|
const contentBounds = nodeViewRoot.getBoundingClientRect();
|
||||||
|
contentWidth = contentBounds.width;
|
||||||
|
contentHeight = contentBounds.height;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
editorWidth: contentWidth,
|
||||||
|
editorHeight: contentHeight,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getZoomToFit = (nodes: INodeUi[], addFooterPadding = true): {offset: XYPosition, zoomLevel: number} => {
|
||||||
|
const { minX, minY, maxX, maxY } = getWorkflowCorners(nodes);
|
||||||
|
const { editorWidth, editorHeight } = getContentDimensions();
|
||||||
|
const footerHeight = addFooterPadding ? 200 : 100;
|
||||||
|
|
||||||
|
const PADDING = NODE_SIZE * 4;
|
||||||
|
|
||||||
|
const diffX = maxX - minX + PADDING;
|
||||||
|
const scaleX = editorWidth / diffX;
|
||||||
|
|
||||||
|
const diffY = maxY - minY + PADDING;
|
||||||
|
const scaleY = editorHeight / diffY;
|
||||||
|
|
||||||
|
const zoomLevel = Math.min(scaleX, scaleY, 1);
|
||||||
|
|
||||||
|
let xOffset = (minX * -1) * zoomLevel; // find top right corner
|
||||||
|
xOffset += (editorWidth - (maxX - minX) * zoomLevel) / 2; // add padding to center workflow
|
||||||
|
|
||||||
|
let yOffset = (minY * -1) * zoomLevel; // find top right corner
|
||||||
|
yOffset += (editorHeight - (maxY - minY + footerHeight) * zoomLevel) / 2; // add padding to center workflow
|
||||||
|
|
||||||
|
return {
|
||||||
|
zoomLevel,
|
||||||
|
offset: [closestNumberDivisibleBy(xOffset, GRID_SIZE), closestNumberDivisibleBy(yOffset, GRID_SIZE)],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showDropConnectionState = (connection: Connection, targetEndpoint?: Endpoint) => {
|
||||||
|
if (connection && connection.connector) {
|
||||||
|
if (targetEndpoint) {
|
||||||
|
connection.connector.setTargetEndpoint(targetEndpoint);
|
||||||
|
}
|
||||||
|
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_PRIMARY);
|
||||||
|
hideOverlay(connection, OVERLAY_DROP_NODE_ID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showPullConnectionState = (connection: Connection) => {
|
||||||
|
if (connection && connection.connector) {
|
||||||
|
connection.connector.resetTargetEndpoint();
|
||||||
|
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_PULL);
|
||||||
|
showOverlay(connection, OVERLAY_DROP_NODE_ID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetConnectionAfterPull = (connection: Connection) => {
|
||||||
|
if (connection && connection.connector) {
|
||||||
|
connection.connector.resetTargetEndpoint();
|
||||||
|
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_DEFAULT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetInputLabelPosition = (targetEndpoint: Endpoint) => {
|
||||||
|
const inputNameOverlay = getOverlay(targetEndpoint, OVERLAY_INPUT_NAME_LABEL);
|
||||||
|
if (inputNameOverlay) {
|
||||||
|
inputNameOverlay.setLocation(OVERLAY_INPUT_NAME_LABEL_POSITION);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moveBackInputLabelPosition = (targetEndpoint: Endpoint) => {
|
||||||
|
const inputNameOverlay = getOverlay(targetEndpoint, OVERLAY_INPUT_NAME_LABEL);
|
||||||
|
if (inputNameOverlay) {
|
||||||
|
inputNameOverlay.setLocation(OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addConnectionActionsOverlay = (connection: Connection, onDelete: Function, onAdd: Function) => {
|
||||||
|
if (getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID)) {
|
||||||
|
return; // avoid free floating actions when moving connection from one node to another
|
||||||
|
}
|
||||||
|
connection.addOverlay([
|
||||||
|
'Label',
|
||||||
|
{
|
||||||
|
id: OVERLAY_CONNECTION_ACTIONS_ID,
|
||||||
|
label: `<div class="add">${getIcon('plus')}</div> <div class="delete">${getIcon('trash')}</div>`,
|
||||||
|
cssClass: OVERLAY_CONNECTION_ACTIONS_ID,
|
||||||
|
visible: false,
|
||||||
|
events: {
|
||||||
|
mousedown: (overlay: Overlay, event: MouseEvent) => {
|
||||||
|
const element = event.target as HTMLElement;
|
||||||
|
if (element.classList.contains('delete') || (element.parentElement && element.parentElement.classList.contains('delete'))) {
|
||||||
|
onDelete();
|
||||||
|
}
|
||||||
|
else if (element.classList.contains('add') || (element.parentElement && element.parentElement.classList.contains('add'))) {
|
||||||
|
onAdd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOutputEndpointUUID = (nodeId: string, outputIndex: number) => {
|
||||||
|
return `${nodeId}${OUTPUT_UUID_KEY}${outputIndex}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInputEndpointUUID = (nodeId: string, inputIndex: number) => {
|
||||||
|
return `${nodeId}${INPUT_UUID_KEY}${inputIndex}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFixedNodesList = (workflowNodes: INode[]) => {
|
||||||
|
const nodes = [...workflowNodes];
|
||||||
|
|
||||||
|
const leftmostTop = getLeftmostTopNode(nodes);
|
||||||
|
|
||||||
|
const diffX = DEFAULT_START_POSITION_X - leftmostTop.position[0];
|
||||||
|
const diffY = DEFAULT_START_POSITION_Y - leftmostTop.position[1];
|
||||||
|
|
||||||
|
nodes.map((node) => {
|
||||||
|
node.position[0] += diffX + (NODE_SIZE * 2);
|
||||||
|
node.position[1] += diffY;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
};
|
|
@ -172,41 +172,6 @@ import NodeSettings from '@/components/NodeSettings.vue';
|
||||||
import Sticky from '@/components/Sticky.vue';
|
import Sticky from '@/components/Sticky.vue';
|
||||||
import CanvasAddButton from './CanvasAddButton.vue';
|
import CanvasAddButton from './CanvasAddButton.vue';
|
||||||
|
|
||||||
import {
|
|
||||||
CONNECTOR_ARROW_OVERLAYS,
|
|
||||||
CONNECTOR_FLOWCHART_TYPE,
|
|
||||||
CONNECTOR_PAINT_STYLE_DEFAULT,
|
|
||||||
CONNECTOR_PAINT_STYLE_PRIMARY,
|
|
||||||
GRID_SIZE,
|
|
||||||
MAX_X_TO_PUSH_DOWNSTREAM_NODES,
|
|
||||||
NODE_SIZE,
|
|
||||||
PUSH_NODES_OFFSET,
|
|
||||||
WELCOME_STICKY_NODE,
|
|
||||||
addConnectionActionsOverlay,
|
|
||||||
addConnectionOutputSuccess,
|
|
||||||
getAccountAge,
|
|
||||||
getBackgroundStyles,
|
|
||||||
getConnectorLengths,
|
|
||||||
getFixedNodesList,
|
|
||||||
getMousePosition,
|
|
||||||
getNewNodePosition,
|
|
||||||
getNodeViewTab,
|
|
||||||
getInputEndpointUUID,
|
|
||||||
getOutputEndpointUUID,
|
|
||||||
getOutputSummary,
|
|
||||||
getRunItemsLabel,
|
|
||||||
hideConnectionActions,
|
|
||||||
moveBackInputLabelPosition,
|
|
||||||
showDropConnectionState,
|
|
||||||
showOrHideMidpointArrow,
|
|
||||||
showOrHideItemsLabel,
|
|
||||||
showPullConnectionState,
|
|
||||||
resetConnection,
|
|
||||||
resetConnectionAfterPull,
|
|
||||||
resetInputLabelPosition,
|
|
||||||
showConnectionActions,
|
|
||||||
} from '@/utils';
|
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import {
|
import {
|
||||||
|
@ -265,6 +230,8 @@ import { useNodeCreatorStore } from '@/stores/nodeCreator';
|
||||||
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
|
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
|
||||||
import { useCanvasStore } from '@/stores/canvas';
|
import { useCanvasStore } from '@/stores/canvas';
|
||||||
import useWorkflowsEEStore from "@/stores/workflows.ee";
|
import useWorkflowsEEStore from "@/stores/workflows.ee";
|
||||||
|
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||||
|
import { getAccountAge, getNodeViewTab } from '@/utils';
|
||||||
|
|
||||||
interface AddNodeOptions {
|
interface AddNodeOptions {
|
||||||
position?: XYPosition;
|
position?: XYPosition;
|
||||||
|
@ -488,7 +455,7 @@ export default mixins(
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
backgroundStyle(): object {
|
backgroundStyle(): object {
|
||||||
return getBackgroundStyles(
|
return NodeViewUtils.getBackgroundStyles(
|
||||||
this.nodeViewScale,
|
this.nodeViewScale,
|
||||||
this.uiStore.nodeViewOffsetPosition,
|
this.uiStore.nodeViewOffsetPosition,
|
||||||
this.isExecutionPreview,
|
this.isExecutionPreview,
|
||||||
|
@ -549,7 +516,7 @@ export default mixins(
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
GRID_SIZE,
|
GRID_SIZE: NodeViewUtils.GRID_SIZE,
|
||||||
STICKY_NODE_TYPE,
|
STICKY_NODE_TYPE,
|
||||||
createNodeActive: false,
|
createNodeActive: false,
|
||||||
lastSelectedConnection: null as null | Connection,
|
lastSelectedConnection: null as null | Connection,
|
||||||
|
@ -781,7 +748,7 @@ export default mixins(
|
||||||
throw new Error('Invalid workflow object');
|
throw new Error('Invalid workflow object');
|
||||||
}
|
}
|
||||||
this.resetWorkspace();
|
this.resetWorkspace();
|
||||||
data.workflow.nodes = getFixedNodesList(data.workflow.nodes);
|
data.workflow.nodes = NodeViewUtils.getFixedNodesList(data.workflow.nodes);
|
||||||
|
|
||||||
await this.addNodes(data.workflow.nodes as INodeUi[], data.workflow.connections);
|
await this.addNodes(data.workflow.nodes as INodeUi[], data.workflow.connections);
|
||||||
|
|
||||||
|
@ -820,7 +787,7 @@ export default mixins(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.workflow.nodes = getFixedNodesList(data.workflow.nodes) as INodeUi[];
|
data.workflow.nodes = NodeViewUtils.getFixedNodesList(data.workflow.nodes) as INodeUi[];
|
||||||
|
|
||||||
this.blankRedirect = true;
|
this.blankRedirect = true;
|
||||||
this.$router.replace({ name: VIEWS.NEW_WORKFLOW, query: { templateId } });
|
this.$router.replace({ name: VIEWS.NEW_WORKFLOW, query: { templateId } });
|
||||||
|
@ -1453,7 +1420,7 @@ export default mixins(
|
||||||
// Fix the node position as it could be totally offscreen
|
// Fix the node position as it could be totally offscreen
|
||||||
// and the pasted nodes would so not be directly visible to
|
// and the pasted nodes would so not be directly visible to
|
||||||
// the user
|
// the user
|
||||||
this.updateNodePositions(workflowData, getNewNodePosition(this.nodes, this.lastClickPosition));
|
this.updateNodePositions(workflowData, NodeViewUtils.getNewNodePosition(this.nodes, this.lastClickPosition));
|
||||||
|
|
||||||
const data = await this.addNodesToWorkflow(workflowData);
|
const data = await this.addNodesToWorkflow(workflowData);
|
||||||
|
|
||||||
|
@ -1522,8 +1489,8 @@ export default mixins(
|
||||||
|
|
||||||
this.addNode(nodeTypeName, {
|
this.addNode(nodeTypeName, {
|
||||||
position: [
|
position: [
|
||||||
mousePosition[0] - NODE_SIZE / 2,
|
mousePosition[0] - NodeViewUtils.NODE_SIZE / 2,
|
||||||
mousePosition[1] - NODE_SIZE / 2,
|
mousePosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||||
],
|
],
|
||||||
dragAndDrop: true,
|
dragAndDrop: true,
|
||||||
});
|
});
|
||||||
|
@ -1664,21 +1631,21 @@ export default mixins(
|
||||||
const lastSelectedNode = this.lastSelectedNode;
|
const lastSelectedNode = this.lastSelectedNode;
|
||||||
|
|
||||||
if (options.position) {
|
if (options.position) {
|
||||||
newNodeData.position = getNewNodePosition(this.canvasStore.getNodesWithPlaceholderNode(), options.position);
|
newNodeData.position = NodeViewUtils.getNewNodePosition(this.canvasStore.getNodesWithPlaceholderNode(), options.position);
|
||||||
} else if (lastSelectedNode) {
|
} else if (lastSelectedNode) {
|
||||||
const lastSelectedConnection = this.lastSelectedConnection;
|
const lastSelectedConnection = this.lastSelectedConnection;
|
||||||
if (lastSelectedConnection) { // set when injecting into a connection
|
if (lastSelectedConnection) { // set when injecting into a connection
|
||||||
const [diffX] = getConnectorLengths(lastSelectedConnection);
|
const [diffX] = NodeViewUtils.getConnectorLengths(lastSelectedConnection);
|
||||||
if (diffX <= MAX_X_TO_PUSH_DOWNSTREAM_NODES) {
|
if (diffX <= NodeViewUtils.MAX_X_TO_PUSH_DOWNSTREAM_NODES) {
|
||||||
this.pushDownstreamNodes(lastSelectedNode.name, PUSH_NODES_OFFSET);
|
this.pushDownstreamNodes(lastSelectedNode.name, NodeViewUtils.PUSH_NODES_OFFSET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set when pulling connections
|
// set when pulling connections
|
||||||
if (this.newNodeInsertPosition) {
|
if (this.newNodeInsertPosition) {
|
||||||
newNodeData.position = getNewNodePosition(this.nodes, [
|
newNodeData.position = NodeViewUtils.getNewNodePosition(this.nodes, [
|
||||||
this.newNodeInsertPosition[0] + GRID_SIZE,
|
this.newNodeInsertPosition[0] + NodeViewUtils.GRID_SIZE,
|
||||||
this.newNodeInsertPosition[1] - NODE_SIZE / 2,
|
this.newNodeInsertPosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||||
]);
|
]);
|
||||||
this.newNodeInsertPosition = null;
|
this.newNodeInsertPosition = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1696,9 +1663,9 @@ export default mixins(
|
||||||
|
|
||||||
// If a node is active then add the new node directly after the current one
|
// If a node is active then add the new node directly after the current one
|
||||||
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
||||||
newNodeData.position = getNewNodePosition(
|
newNodeData.position = NodeViewUtils.getNewNodePosition(
|
||||||
this.nodes,
|
this.nodes,
|
||||||
[lastSelectedNode.position[0] + PUSH_NODES_OFFSET, lastSelectedNode.position[1] + yOffset],
|
[lastSelectedNode.position[0] + NodeViewUtils.PUSH_NODES_OFFSET, lastSelectedNode.position[1] + yOffset],
|
||||||
[100, 0],
|
[100, 0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1710,7 +1677,7 @@ export default mixins(
|
||||||
// If no node is active find a free spot
|
// If no node is active find a free spot
|
||||||
: this.lastClickPosition as XYPosition;
|
: this.lastClickPosition as XYPosition;
|
||||||
|
|
||||||
newNodeData.position = getNewNodePosition(this.nodes, position);
|
newNodeData.position = NodeViewUtils.getNewNodePosition(this.nodes, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1820,12 +1787,12 @@ export default mixins(
|
||||||
},
|
},
|
||||||
initNodeView() {
|
initNodeView() {
|
||||||
this.instance.importDefaults({
|
this.instance.importDefaults({
|
||||||
Connector: CONNECTOR_FLOWCHART_TYPE,
|
Connector: NodeViewUtils.CONNECTOR_FLOWCHART_TYPE,
|
||||||
Endpoint: ['Dot', { radius: 5 }],
|
Endpoint: ['Dot', { radius: 5 }],
|
||||||
DragOptions: { cursor: 'pointer', zIndex: 5000 },
|
DragOptions: { cursor: 'pointer', zIndex: 5000 },
|
||||||
PaintStyle: CONNECTOR_PAINT_STYLE_DEFAULT,
|
PaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_DEFAULT,
|
||||||
HoverPaintStyle: CONNECTOR_PAINT_STYLE_PRIMARY,
|
HoverPaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_PRIMARY,
|
||||||
ConnectionOverlays: CONNECTOR_ARROW_OVERLAYS,
|
ConnectionOverlays: NodeViewUtils.CONNECTOR_ARROW_OVERLAYS,
|
||||||
Container: '#node-view',
|
Container: '#node-view',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1921,7 +1888,7 @@ export default mixins(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
resetConnection(info.connection);
|
NodeViewUtils.resetConnection(info.connection);
|
||||||
|
|
||||||
if (!this.isReadOnly) {
|
if (!this.isReadOnly) {
|
||||||
let exitTimer: NodeJS.Timeout | undefined;
|
let exitTimer: NodeJS.Timeout | undefined;
|
||||||
|
@ -1941,14 +1908,14 @@ export default mixins(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hideConnectionActions(activeConnection);
|
NodeViewUtils.hideConnectionActions(activeConnection);
|
||||||
|
|
||||||
|
|
||||||
enterTimer = setTimeout(() => {
|
enterTimer = setTimeout(() => {
|
||||||
enterTimer = undefined;
|
enterTimer = undefined;
|
||||||
if (info.connection) {
|
if (info.connection) {
|
||||||
activeConnection = info.connection;
|
activeConnection = info.connection;
|
||||||
showConnectionActions(info.connection);
|
NodeViewUtils.showConnectionActions(info.connection);
|
||||||
}
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1975,7 +1942,7 @@ export default mixins(
|
||||||
exitTimer = undefined;
|
exitTimer = undefined;
|
||||||
|
|
||||||
if (info.connection && activeConnection === info.connection) {
|
if (info.connection && activeConnection === info.connection) {
|
||||||
hideConnectionActions(activeConnection);
|
NodeViewUtils.hideConnectionActions(activeConnection);
|
||||||
activeConnection = null;
|
activeConnection = null;
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
@ -1984,7 +1951,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addConnectionActionsOverlay(info.connection,
|
NodeViewUtils.addConnectionActionsOverlay(info.connection,
|
||||||
() => {
|
() => {
|
||||||
activeConnection = null;
|
activeConnection = null;
|
||||||
this.__deleteJSPlumbConnection(info.connection);
|
this.__deleteJSPlumbConnection(info.connection);
|
||||||
|
@ -2001,7 +1968,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
moveBackInputLabelPosition(info.targetEndpoint);
|
NodeViewUtils.moveBackInputLabelPosition(info.targetEndpoint);
|
||||||
|
|
||||||
this.workflowsStore.addConnection({
|
this.workflowsStore.addConnection({
|
||||||
connection: [
|
connection: [
|
||||||
|
@ -2029,7 +1996,7 @@ export default mixins(
|
||||||
// calls the "connection" event but not the "connectionDetached" one. So we listen
|
// calls the "connection" event but not the "connectionDetached" one. So we listen
|
||||||
// additionally to the "connectionMoved" event and then only delete the existing connection.
|
// additionally to the "connectionMoved" event and then only delete the existing connection.
|
||||||
|
|
||||||
resetInputLabelPosition(info.originalTargetEndpoint);
|
NodeViewUtils.resetInputLabelPosition(info.originalTargetEndpoint);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sourceInfo = info.originalSourceEndpoint.getParameters();
|
const sourceInfo = info.originalSourceEndpoint.getParameters();
|
||||||
|
@ -2057,7 +2024,7 @@ export default mixins(
|
||||||
|
|
||||||
this.instance.bind('connectionDetached', (info) => {
|
this.instance.bind('connectionDetached', (info) => {
|
||||||
try {
|
try {
|
||||||
resetInputLabelPosition(info.targetEndpoint);
|
NodeViewUtils.resetInputLabelPosition(info.targetEndpoint);
|
||||||
info.connection.removeOverlays();
|
info.connection.removeOverlays();
|
||||||
this.__removeConnectionByConnectionInfo(info, false);
|
this.__removeConnectionByConnectionInfo(info, false);
|
||||||
|
|
||||||
|
@ -2080,7 +2047,7 @@ export default mixins(
|
||||||
this.pullConnActiveNodeName = null;
|
this.pullConnActiveNodeName = null;
|
||||||
this.pullConnActive = true;
|
this.pullConnActive = true;
|
||||||
this.newNodeInsertPosition = null;
|
this.newNodeInsertPosition = null;
|
||||||
resetConnection(connection);
|
NodeViewUtils.resetConnection(connection);
|
||||||
|
|
||||||
const nodes = [...document.querySelectorAll('.node-default')];
|
const nodes = [...document.querySelectorAll('.node-default')];
|
||||||
|
|
||||||
|
@ -2092,14 +2059,14 @@ export default mixins(
|
||||||
const element = document.querySelector('.jtk-endpoint.dropHover');
|
const element = document.querySelector('.jtk-endpoint.dropHover');
|
||||||
if (element) {
|
if (element) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
showDropConnectionState(connection, element._jsPlumb);
|
NodeViewUtils.showDropConnectionState(connection, element._jsPlumb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputMargin = 24;
|
const inputMargin = 24;
|
||||||
const intersecting = nodes.find((element: Element) => {
|
const intersecting = nodes.find((element: Element) => {
|
||||||
const { top, left, right, bottom } = element.getBoundingClientRect();
|
const { top, left, right, bottom } = element.getBoundingClientRect();
|
||||||
const [x, y] = getMousePosition(e);
|
const [x, y] = NodeViewUtils.getMousePosition(e);
|
||||||
if (top <= y && bottom >= y && (left - inputMargin) <= x && right >= x) {
|
if (top <= y && bottom >= y && (left - inputMargin) <= x && right >= x) {
|
||||||
const nodeName = (element as HTMLElement).dataset['name'] as string;
|
const nodeName = (element as HTMLElement).dataset['name'] as string;
|
||||||
const node = this.workflowsStore.getNodeByName(nodeName) as INodeUi | null;
|
const node = this.workflowsStore.getNodeByName(nodeName) as INodeUi | null;
|
||||||
|
@ -2111,7 +2078,7 @@ export default mixins(
|
||||||
if (endpointUUID) {
|
if (endpointUUID) {
|
||||||
const endpoint = this.instance.getEndpoint(endpointUUID);
|
const endpoint = this.instance.getEndpoint(endpointUUID);
|
||||||
|
|
||||||
showDropConnectionState(connection, endpoint);
|
NodeViewUtils.showDropConnectionState(connection, endpoint);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2123,7 +2090,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!intersecting) {
|
if (!intersecting) {
|
||||||
showPullConnectionState(connection);
|
NodeViewUtils.showPullConnectionState(connection);
|
||||||
this.pullConnActiveNodeName = null;
|
this.pullConnActiveNodeName = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2131,7 +2098,7 @@ export default mixins(
|
||||||
const onMouseUp = (e: MouseEvent | TouchEvent) => {
|
const onMouseUp = (e: MouseEvent | TouchEvent) => {
|
||||||
this.pullConnActive = false;
|
this.pullConnActive = false;
|
||||||
this.newNodeInsertPosition = this.getMousePositionWithinNodeView(e);
|
this.newNodeInsertPosition = this.getMousePositionWithinNodeView(e);
|
||||||
resetConnectionAfterPull(connection);
|
NodeViewUtils.resetConnectionAfterPull(connection);
|
||||||
window.removeEventListener('mousemove', onMouseMove);
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
window.removeEventListener('mouseup', onMouseUp);
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
};
|
};
|
||||||
|
@ -2164,7 +2131,7 @@ export default mixins(
|
||||||
this.workflowsStore.activeWorkflowExecution = null;
|
this.workflowsStore.activeWorkflowExecution = null;
|
||||||
|
|
||||||
this.uiStore.stateIsDirty = false;
|
this.uiStore.stateIsDirty = false;
|
||||||
this.canvasStore.setZoomLevel(1);
|
this.canvasStore.setZoomLevel(1, 0);
|
||||||
this.canvasStore.zoomToFit();
|
this.canvasStore.zoomToFit();
|
||||||
},
|
},
|
||||||
tryToAddWelcomeSticky: once(async function(this: any) {
|
tryToAddWelcomeSticky: once(async function(this: any) {
|
||||||
|
@ -2174,19 +2141,19 @@ export default mixins(
|
||||||
// Inject welcome sticky note and zoom to fit
|
// Inject welcome sticky note and zoom to fit
|
||||||
|
|
||||||
if (newWorkflow?.onboardingFlowEnabled && !this.isReadOnly) {
|
if (newWorkflow?.onboardingFlowEnabled && !this.isReadOnly) {
|
||||||
const collisionPadding = GRID_SIZE + NODE_SIZE;
|
const collisionPadding = NodeViewUtils.GRID_SIZE + NodeViewUtils.NODE_SIZE;
|
||||||
// Position the welcome sticky left to the added trigger node
|
// Position the welcome sticky left to the added trigger node
|
||||||
let position: XYPosition = [...(this.triggerNodes[0].position as XYPosition)];
|
let position: XYPosition = [...(this.triggerNodes[0].position as XYPosition)];
|
||||||
|
|
||||||
position[0] -= WELCOME_STICKY_NODE.parameters.width + (GRID_SIZE * 4);
|
position[0] -= NodeViewUtils.WELCOME_STICKY_NODE.parameters.width + (NodeViewUtils.GRID_SIZE * 4);
|
||||||
position = getNewNodePosition(this.nodes, position, [collisionPadding, collisionPadding]);
|
position = NodeViewUtils.getNewNodePosition(this.nodes, position, [collisionPadding, collisionPadding]);
|
||||||
|
|
||||||
await this.addNodes([{
|
await this.addNodes([{
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
...WELCOME_STICKY_NODE,
|
...NodeViewUtils.WELCOME_STICKY_NODE,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Use parameters from the template but add translated content
|
// Use parameters from the template but add translated content
|
||||||
...WELCOME_STICKY_NODE.parameters,
|
...NodeViewUtils.WELCOME_STICKY_NODE.parameters,
|
||||||
content: this.$locale.baseText('onboardingWorkflow.stickyContent'),
|
content: this.$locale.baseText('onboardingWorkflow.stickyContent'),
|
||||||
},
|
},
|
||||||
position,
|
position,
|
||||||
|
@ -2285,7 +2252,7 @@ export default mixins(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getOutputEndpointUUID(node.id, index);
|
return NodeViewUtils.getOutputEndpointUUID(node.id, index);
|
||||||
},
|
},
|
||||||
getInputEndpointUUID(nodeName: string, index: number) {
|
getInputEndpointUUID(nodeName: string, index: number) {
|
||||||
const node = this.workflowsStore.getNodeByName(nodeName);
|
const node = this.workflowsStore.getNodeByName(nodeName);
|
||||||
|
@ -2293,7 +2260,7 @@ export default mixins(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getInputEndpointUUID(node.id, index);
|
return NodeViewUtils.getInputEndpointUUID(node.id, index);
|
||||||
},
|
},
|
||||||
__addConnection(connection: [IConnection, IConnection], addVisualConnection = false) {
|
__addConnection(connection: [IConnection, IConnection], addVisualConnection = false) {
|
||||||
if (addVisualConnection) {
|
if (addVisualConnection) {
|
||||||
|
@ -2408,7 +2375,7 @@ export default mixins(
|
||||||
type: newNodeData.type,
|
type: newNodeData.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
newNodeData.position = getNewNodePosition(
|
newNodeData.position = NodeViewUtils.getNewNodePosition(
|
||||||
this.nodes,
|
this.nodes,
|
||||||
[node.position[0], node.position[1] + 140],
|
[node.position[0], node.position[1] + 140],
|
||||||
[0, 140],
|
[0, 140],
|
||||||
|
@ -2460,8 +2427,8 @@ export default mixins(
|
||||||
const sourceId = sourceNode.id;
|
const sourceId = sourceNode.id;
|
||||||
const targetId = targetNode.id;
|
const targetId = targetNode.id;
|
||||||
|
|
||||||
const sourceEndpoint = getOutputEndpointUUID(sourceId, sourceOutputIndex);
|
const sourceEndpoint = NodeViewUtils.getOutputEndpointUUID(sourceId, sourceOutputIndex);
|
||||||
const targetEndpoint = getInputEndpointUUID(targetId, targetInputIndex);
|
const targetEndpoint = NodeViewUtils.getInputEndpointUUID(targetId, targetInputIndex);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const connections = this.instance.getConnections({
|
const connections = this.instance.getConnections({
|
||||||
|
@ -2508,8 +2475,8 @@ export default mixins(
|
||||||
const { incoming, outgoing } = this.getIncomingOutgoingConnections(node.name);
|
const { incoming, outgoing } = this.getIncomingOutgoingConnections(node.name);
|
||||||
|
|
||||||
[...incoming, ...outgoing].forEach((connection: Connection) => {
|
[...incoming, ...outgoing].forEach((connection: Connection) => {
|
||||||
showOrHideMidpointArrow(connection);
|
NodeViewUtils.showOrHideMidpointArrow(connection);
|
||||||
showOrHideItemsLabel(connection);
|
NodeViewUtils.showOrHideItemsLabel(connection);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onNodeRun({ name, data, waiting }: { name: string, data: ITaskData[] | null, waiting: boolean }) {
|
onNodeRun({ name, data, waiting }: { name: string, data: ITaskData[] | null, waiting: boolean }) {
|
||||||
|
@ -2528,7 +2495,7 @@ export default mixins(
|
||||||
}) as Connection[];
|
}) as Connection[];
|
||||||
|
|
||||||
outgoing.forEach((connection: Connection) => {
|
outgoing.forEach((connection: Connection) => {
|
||||||
resetConnection(connection);
|
NodeViewUtils.resetConnection(connection);
|
||||||
});
|
});
|
||||||
const endpoints = this.getJSPlumbEndpoints(sourceNodeName);
|
const endpoints = this.getJSPlumbEndpoints(sourceNodeName);
|
||||||
endpoints.forEach((endpoint: Endpoint) => {
|
endpoints.forEach((endpoint: Endpoint) => {
|
||||||
|
@ -2542,7 +2509,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeConnections = this.workflowsStore.outgoingConnectionsByNodeName(sourceNodeName).main;
|
const nodeConnections = this.workflowsStore.outgoingConnectionsByNodeName(sourceNodeName).main;
|
||||||
const outputMap = getOutputSummary(data, nodeConnections || []);
|
const outputMap = NodeViewUtils.getOutputSummary(data, nodeConnections || []);
|
||||||
|
|
||||||
Object.keys(outputMap).forEach((sourceOutputIndex: string) => {
|
Object.keys(outputMap).forEach((sourceOutputIndex: string) => {
|
||||||
Object.keys(outputMap[sourceOutputIndex]).forEach((targetNodeName: string) => {
|
Object.keys(outputMap[sourceOutputIndex]).forEach((targetNodeName: string) => {
|
||||||
|
@ -2554,10 +2521,10 @@ export default mixins(
|
||||||
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
|
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
|
||||||
|
|
||||||
if (!output || !output.total) {
|
if (!output || !output.total) {
|
||||||
resetConnection(connection);
|
NodeViewUtils.resetConnection(connection);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
addConnectionOutputSuccess(connection, output);
|
NodeViewUtils.addConnectionOutputSuccess(connection, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2566,7 +2533,7 @@ export default mixins(
|
||||||
if (endpoint && endpoint.endpoint) {
|
if (endpoint && endpoint.endpoint) {
|
||||||
const output = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
const output = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
||||||
if (output && output.total > 0) {
|
if (output && output.total > 0) {
|
||||||
(endpoint.endpoint as N8nPlusEndpoint).setSuccessOutput(getRunItemsLabel(output));
|
(endpoint.endpoint as N8nPlusEndpoint).setSuccessOutput(NodeViewUtils.getRunItemsLabel(output));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
|
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
|
||||||
|
@ -3237,7 +3204,7 @@ export default mixins(
|
||||||
}) as Connection[];
|
}) as Connection[];
|
||||||
|
|
||||||
connections.forEach((connection) => {
|
connections.forEach((connection) => {
|
||||||
addConnectionOutputSuccess(connection, {
|
NodeViewUtils.addConnectionOutputSuccess(connection, {
|
||||||
total: pinData[nodeName].length,
|
total: pinData[nodeName].length,
|
||||||
iterations: 0,
|
iterations: 0,
|
||||||
});
|
});
|
||||||
|
@ -3256,7 +3223,7 @@ export default mixins(
|
||||||
source: node.id,
|
source: node.id,
|
||||||
}) as Connection[];
|
}) as Connection[];
|
||||||
|
|
||||||
connections.forEach(resetConnection);
|
connections.forEach(NodeViewUtils.resetConnection);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onToggleNodeCreator({ source, createNodeActive }: { source?: string; createNodeActive: boolean }) {
|
onToggleNodeCreator({ source, createNodeActive }: { source?: string; createNodeActive: boolean }) {
|
||||||
|
|
Loading…
Reference in a new issue