Files
n8n-enterprise-unlocked/packages/editor-ui/src/components/mixins/nodeBase.ts
Mutasem Aldmour 76c63790b4 Add plus endpoint (#2450)
* fix

* refactor

* clean up css

* refactor setzoom level

* refactor

* refactor

* refactor func

* remove scope

* remove localstorage caching

* clean up imports

* update zero case

* add delete connection

* update selected state

* add base type, remove straight line

* add stub offset back

* rename param

* add label offset

* update font size of items

* move up label

* fix error state while executing

* disrespect stubs

* check for errors

* refactor position

* clean up extra space

* make entire node connectable

* Revert "make entire node connectable"

e304f7c5b8ff1b41268450c60ca4bc3b2ada5d4a

* always show border

* add border to zoom buttons

* update spacing

* update colors

* allow connecting to entire node

* fix pull conn active

* two line names

* apply select to all lines

* increase input margin

* override target pos

* reset conn after pull

* fix types

* update orientation

* fix up connectors snapping

* hide arrow on pull

* update overrides for connectors

* change text

* update pull colors

* set to 1 line when selected

* fix executions bug

* build

* refactor node component

* remove comment

* refactor more

* remove prop

* fix build issue

* fix input drag bug in executions

* reset offset

* update select background

* handle issue when endpoints are not set

* fix connection aborted issue

* add try catch to help show errors

* wrap bind with try/catch

* set default styles

* reset pos despite zoom

* add more checks

* clean up impl

* update icon

* handle unknown types

* hide items on init

* fix importing unknown types with credentials

* change opacity

* push up item label

* update color

* update label class and colors

* add to drop distance

* fix z-index to match node

* disable eslint

* fix lasso tool selection

* update background color

* update waiting state

* update tooltip positions

* update wait node border

* fix selection bug mostly

* if selected, move above other nodes

* add line through disabled nodes

* remove node color option

* move label above connection

* success color for line through

* update options index

* hide waiting icon when disabled

* fix gmail icon

* refactor icons

* clear execution data on disable/delete

* fix selected node

* fix executing behavior

* optional __meta

* set grid size

* remove default color

* remove node color

* add comments

* comments

* add comments

* remove empty space

* update comment

* refactor uuids

* fix type issue

* Revert "fix type issue"

9523b34f9604f75253ae0631f29fc27267a99d78

* Revert "fix type issue"

9523b34f9604f75253ae0631f29fc27267a99d78

* Revert "refactor uuids"

07f6848065cb9a98475fddb8330846106f9e70ad

* fix build issues

* refactor

* update uuid

* child nodes

* skip nodes behind when pushing in loop

* shift output icon for switch node

* don't show output if waiting

* waiting on init

* build

* change to bezier

* implement plus

* revert connector change

* add bezier type

* fix snapping

* clean up impl

* refactor func

* make const

* rename type

* refactor to simplify

* Revert "refactor to simplify"

2db0ed504c752c33de975370d86a83a04ffcda14

* enable flowchart mode

* clean up flowchart type

* refactor type

* merge types

* configure curviness

* set in localstorage

* fix straight line arrow bug

* show arrow when pulling

* refactor / simplify

* fix target gap in bezier

* refactor target gap

* add comments

* add comment

* fix dragging connections

* fix bug when moving connection

* update comment

* rename file

* update values

* update minor

* update straight line box

* clean up conn types

* clean up z-indexes

* move color filters to node icon

* update background color

* update to use grid size value

* fix endpoint offsets

* set yspan range lower

* remove overlays when moving conn

* prevent unwanted connections

* fix messed up connections

* remove console log

* clear execution issues on workflow run

* update corner radius

* fix drag/delete bug

* increase offset

* update disabled state

* hide if endpoint has conn

* node insert on click

* simplify impl

* fix z-indexes

* add drop hover message

* address comments

* refactor

* refactor func

* remove drop conn overlay

* update messagE

* update colors

* remove console log

* rebuild impl

* add stalk

* fix disabled state

* fix dragging bug

* remove node selection

* disable plus in executions

* add success output to talk

* add success output

* set output

* fix bug

* fix switch size

* update size for 3 output nodes

* update loops

* fix bug when node is renamed

* set final values

* update loop behavior

* update comment

* hide output labels on multi outputs

* center output

* fix flicker when deleting nodes

* fix dragging space

* increase stalk if if

* more type checks

* rename event source

* rename event source

* support touch when dragging

* offset center

* center plus

* fix repaint behavior

* remove extending output

* add offset to switch

* fix merge node bug

* rename endpoint

* refactor css, fix merge bug

* fix unrelated issues

* space out

* remove flowchart type

* rename constant key

* clean up css

* rename var

* fix more type issues

* update types

* fix spacing

* simplify vertical offset

* refactor css

* add license

* update css

* clean up scss

* update to use css var

* clean up code

* update params

* show message

* fix plus position

* fix merge node bugs

* hide box when not dragging and hidden

* fix potential issue

* address comments
2021-12-03 18:53:55 +01:00

325 lines
11 KiB
TypeScript

import { IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
import mixins from 'vue-typed-mixins';
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
import { nodeIndex } from '@/components/mixins/nodeIndex';
import { NODE_NAME_PREFIX, NO_OP_NODE_TYPE } from '@/constants';
import * as CanvasHelpers from '@/views/canvasHelpers';
import { Endpoint } from 'jsplumb';
import {
INodeTypeDescription,
} from 'n8n-workflow';
import { getStyleTokenValue } from '../helpers';
export const nodeBase = mixins(
deviceSupportHelpers,
nodeIndex,
).extend({
mounted () {
// Initialize the node
if (this.data !== null) {
this.__addNode(this.data);
}
},
computed: {
data (): INodeUi {
return this.$store.getters.getNodeByName(this.name);
},
nodeId (): string {
return NODE_NAME_PREFIX + this.nodeIndex;
},
nodeIndex (): string {
return this.$store.getters.getNodeIndex(this.data.name).toString();
},
},
props: [
'name',
'instance',
'isReadOnly',
'isActive',
'hideActions',
],
methods: {
__addInputEndpoints (node: INodeUi, nodeTypeData: INodeTypeDescription) {
// Add Inputs
let index;
const indexData: {
[key: string]: number;
} = {};
nodeTypeData.inputs.forEach((inputName: string, i: number) => {
// Increment the index for inputs with current name
if (indexData.hasOwnProperty(inputName)) {
indexData[inputName]++;
} else {
indexData[inputName] = 0;
}
index = indexData[inputName];
// Get the position of the anchor depending on how many it has
const anchorPosition = CanvasHelpers.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
const newEndpointData: IEndpointOptions = {
uuid: CanvasHelpers.getInputEndpointUUID(this.nodeIndex, index),
anchor: anchorPosition,
maxConnections: -1,
endpoint: 'Rectangle',
endpointStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
endpointHoverStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-primary'),
isSource: false,
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
index,
},
enabled: !this.isReadOnly, // enabled in default case to allow dragging
cssClass: 'rect-input-endpoint',
dragAllowedWhenFull: true,
dropOptions: {
tolerance: 'touch',
hoverClass: 'dropHover',
},
};
if (nodeTypeData.inputNames) {
// Apply input names if they got set
newEndpointData.overlays = [
CanvasHelpers.getInputNameOverlay(nodeTypeData.inputNames[index]),
];
}
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
endpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.inputs.length,
};
// TODO: Activate again if it makes sense. Currently makes problems when removing
// connection on which the input has a name. It does not get hidden because
// the endpoint to which it connects when letting it go over the node is
// different to the regular one (have different ids). So that seems to make
// problems when hiding the input-name.
// if (index === 0 && inputName === 'main') {
// // Make the first main-input the default one to connect to when connection gets dropped on node
// this.instance.makeTarget(this.nodeId, newEndpointData);
// }
});
},
__addOutputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
let index;
const indexData: {
[key: string]: number;
} = {};
nodeTypeData.outputs.forEach((inputName: string, i: number) => {
// Increment the index for outputs with current name
if (indexData.hasOwnProperty(inputName)) {
indexData[inputName]++;
} else {
indexData[inputName] = 0;
}
index = indexData[inputName];
// Get the position of the anchor depending on how many it has
const anchorPosition = CanvasHelpers.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
const newEndpointData: IEndpointOptions = {
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
anchor: anchorPosition,
maxConnections: -1,
endpoint: 'Dot',
endpointStyle: CanvasHelpers.getOutputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
endpointHoverStyle: CanvasHelpers.getOutputEndpointStyle(nodeTypeData, '--color-primary'),
isSource: true,
isTarget: false,
enabled: !this.isReadOnly,
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
index,
},
cssClass: 'dot-output-endpoint',
dragAllowedWhenFull: false,
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
};
if (nodeTypeData.outputNames) {
// Apply output names if they got set
newEndpointData.overlays = [
CanvasHelpers.getOutputNameOverlay(nodeTypeData.outputNames[index]),
];
}
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
endpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.outputs.length,
};
if (!this.isReadOnly) {
const plusEndpointData: IEndpointOptions = {
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
anchor: anchorPosition,
maxConnections: -1,
endpoint: 'N8nPlus',
isSource: true,
isTarget: false,
enabled: !this.isReadOnly,
endpointStyle: {
fill: getStyleTokenValue('--color-xdark'),
outlineStroke: 'none',
hover: false,
showOutputLabel: nodeTypeData.outputs.length === 1,
size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium',
},
endpointHoverStyle: {
fill: getStyleTokenValue('--color-primary'),
outlineStroke: 'none',
hover: true, // hack to distinguish hover state
},
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
index,
},
cssClass: 'plus-draggable-endpoint',
dragAllowedWhenFull: false,
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
};
const plusEndpoint: Endpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
plusEndpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.outputs.length,
};
}
});
},
__makeInstanceDraggable(node: INodeUi) {
// TODO: This caused problems with displaying old information
// https://github.com/jsplumb/katavorio/wiki
// https://jsplumb.github.io/jsplumb/home.html
// Make nodes draggable
this.instance.draggable(this.nodeId, {
grid: [CanvasHelpers.GRID_SIZE, CanvasHelpers.GRID_SIZE],
start: (params: { e: MouseEvent }) => {
if (this.isReadOnly === true) {
// Do not allow to move nodes in readOnly mode
return false;
}
// @ts-ignore
this.dragging = true;
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is
// undefined. So check if the currently dragged node is selected and if not clear
// the drag-selection.
this.instance.clearDragSelection();
this.$store.commit('resetSelectedNodes');
}
this.$store.commit('addActiveAction', 'dragActive');
return true;
},
stop: (params: { e: MouseEvent }) => {
// @ts-ignore
this.dragging = false;
if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
if (!selectedNodeNames.includes(this.data.name)) {
// If the current node is not in selected add it to the nodes which
// got moved manually
moveNodes.push(this.data);
}
// This does for some reason just get called once for the node that got clicked
// even though "start" and "drag" gets called for all. So lets do for now
// some dirty DOM query to get the new positions till I have more time to
// create a proper solution
let newNodePositon: XYPosition;
moveNodes.forEach((node: INodeUi) => {
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
const element = document.getElementById(nodeElement);
if (element === null) {
return;
}
newNodePositon = [
parseInt(element.style.left!.slice(0, -2), 10),
parseInt(element.style.top!.slice(0, -2), 10),
];
const updateInformation = {
name: node.name,
properties: {
// @ts-ignore, draggable does not have definitions
position: newNodePositon,
},
};
this.$store.commit('updateNodeProperties', updateInformation);
});
this.$emit('moved', node);
}
},
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
});
},
__addNode (node: INodeUi) {
let nodeTypeData = this.$store.getters.nodeType(node.type) as INodeTypeDescription | null;
if (!nodeTypeData) {
// If node type is not know use by default the base.noOp data to display it
nodeTypeData = this.$store.getters.nodeType(NO_OP_NODE_TYPE) as INodeTypeDescription;
}
this.__addInputEndpoints(node, nodeTypeData);
this.__addOutputEndpoints(node, nodeTypeData);
this.__makeInstanceDraggable(node);
},
touchEnd(e: MouseEvent) {
if (this.isTouchDevice) {
if (this.$store.getters.isActionActive('dragActive')) {
this.$store.commit('removeActiveAction', 'dragActive');
}
}
},
mouseLeftClick (e: MouseEvent) {
// @ts-ignore
const path = e.path || (e.composedPath && e.composedPath());
for (let index = 0; index < path.length; index++) {
if (path[index].className && typeof path[index].className === 'string' && path[index].className.includes('no-select-on-click')) {
return;
}
}
if (!this.isTouchDevice) {
if (this.$store.getters.isActionActive('dragActive')) {
this.$store.commit('removeActiveAction', 'dragActive');
} else {
if (this.isCtrlKeyPressed(e) === false) {
this.$emit('deselectAllNodes');
}
if (this.$store.getters.isNodeSelected(this.data.name)) {
this.$emit('deselectNode', this.name);
} else {
this.$emit('nodeSelected', this.name);
}
}
}
},
},
});