mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-30 13:51:21 -08:00
allow connecting to entire node
This commit is contained in:
parent
6859f85b9b
commit
42a5217893
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="node-wrapper" :style="nodePosition">
|
<div class="node-wrapper" :style="nodePosition">
|
||||||
<div :class="{'selected': true, 'has-subtitles': !!nodeSubtitle}" v-show="isSelected"></div>
|
<div :class="{'selected': true, 'has-subtitles': !!nodeSubtitle}" v-show="isSelected"></div>
|
||||||
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
<div class="node-default" :data-name="data.name" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
||||||
<div v-if="hasIssues" class="node-info-icon node-issues">
|
<div v-if="hasIssues" class="node-info-icon node-issues">
|
||||||
<n8n-tooltip placement="top" >
|
<n8n-tooltip placement="top" >
|
||||||
<div slot="content" v-html="nodeIssues"></div>
|
<div slot="content" v-html="nodeIssues"></div>
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<div class="node-executing-info" title="Node is executing">
|
<div class="node-executing-info" title="Node is executing">
|
||||||
<font-awesome-icon icon="sync-alt" spin />
|
<font-awesome-icon icon="sync-alt" spin />
|
||||||
</div>
|
</div>
|
||||||
<div class="node-options no-select-on-click" v-if="!isReadOnly">
|
<div class="node-options no-select-on-click" v-if="!isReadOnly" v-show="!hideActions">
|
||||||
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
||||||
<font-awesome-icon icon="trash" />
|
<font-awesome-icon icon="trash" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="node-icon-wrapper" :style="iconStyleData">
|
<div class="node-icon-wrapper" :style="iconStyleData">
|
||||||
<div v-if="nodeIconData !== null" class="icon">
|
<div v-if="nodeIconData !== null" class="icon">
|
||||||
<img v-if="nodeIconData.type === 'file'" :src="nodeIconData.fileBuffer || nodeIconData.path" style="max-width: 100%; max-height: 100%;" />
|
<img v-if="nodeIconData.type === 'file'" :src="nodeIconData.fileBuffer || nodeIconData.path" :style="imageStyleData" />
|
||||||
<font-awesome-icon v-else :icon="nodeIconData.icon || nodeIconData.path" :style="fontStyleData" />
|
<font-awesome-icon v-else :icon="nodeIconData.icon || nodeIconData.path" :style="fontStyleData" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="node-icon-placeholder">
|
<div v-else class="node-icon-placeholder">
|
||||||
|
@ -12,25 +12,37 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { IVersionNode } from '@/Interface';
|
||||||
|
import { INodeTypeDescription } from 'n8n-workflow';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
interface NodeIconData {
|
interface NodeIconData {
|
||||||
type: string;
|
type: string;
|
||||||
path: string;
|
path?: string;
|
||||||
fileExtension?: string;
|
fileExtension?: string;
|
||||||
|
fileBuffer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'NodeIcon',
|
name: 'NodeIcon',
|
||||||
props: [
|
props: {
|
||||||
'nodeType',
|
nodeType: {},
|
||||||
'size',
|
size: {
|
||||||
'disabled',
|
type: Number,
|
||||||
'circle',
|
},
|
||||||
],
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
circle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconStyleData (): object {
|
iconStyleData (): object {
|
||||||
const color = this.disabled ? '#ccc' : this.nodeType.defaults && this.nodeType.defaults.color;
|
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
||||||
|
const color = this.disabled ? '#ccc' : (nodeType ? nodeType.defaults && nodeType!.defaults.color: '');
|
||||||
if (!this.size) {
|
if (!this.size) {
|
||||||
return {color};
|
return {color};
|
||||||
}
|
}
|
||||||
|
@ -49,6 +61,12 @@ export default Vue.extend({
|
||||||
'max-width': this.size + 'px',
|
'max-width': this.size + 'px',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
imageStyleData (): object {
|
||||||
|
return {
|
||||||
|
'max-width': '100%',
|
||||||
|
'max-height': '100%',
|
||||||
|
};
|
||||||
|
},
|
||||||
isSvgIcon (): boolean {
|
isSvgIcon (): boolean {
|
||||||
if (this.nodeIconData && this.nodeIconData.type === 'file' && this.nodeIconData.fileExtension === 'svg') {
|
if (this.nodeIconData && this.nodeIconData.type === 'file' && this.nodeIconData.fileExtension === 'svg') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -56,26 +74,27 @@ export default Vue.extend({
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
nodeIconData (): null | NodeIconData {
|
nodeIconData (): null | NodeIconData {
|
||||||
if (this.nodeType === null) {
|
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
||||||
|
if (nodeType === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.nodeType.iconData) {
|
if ((nodeType as IVersionNode).iconData) {
|
||||||
return this.nodeType.iconData;
|
return (nodeType as IVersionNode).iconData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const restUrl = this.$store.getters.getRestUrl;
|
const restUrl = this.$store.getters.getRestUrl;
|
||||||
|
|
||||||
if (this.nodeType.icon) {
|
if (nodeType.icon) {
|
||||||
let type, path;
|
let type, path;
|
||||||
[type, path] = this.nodeType.icon.split(':');
|
[type, path] = nodeType.icon.split(':');
|
||||||
const returnData: NodeIconData = {
|
const returnData: NodeIconData = {
|
||||||
type,
|
type,
|
||||||
path,
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
returnData.path = restUrl + '/node-icon/' + this.nodeType.name;
|
returnData.path = restUrl + '/node-icon/' + nodeType.name;
|
||||||
returnData.fileExtension = path.split('.').slice(-1).join();
|
returnData.fileExtension = path.split('.').slice(-1).join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,13 +82,17 @@ export const nodeBase = mixins(
|
||||||
nodeIndex (): string {
|
nodeIndex (): string {
|
||||||
return this.$store.getters.getNodeIndex(this.data.name).toString();
|
return this.$store.getters.getNodeIndex(this.data.name).toString();
|
||||||
},
|
},
|
||||||
|
position (): XYPosition {
|
||||||
|
const node = this.$store.getters.nodesByName[this.name] as INodeUi; // position responsive to store changes
|
||||||
|
|
||||||
|
return node.position;
|
||||||
|
},
|
||||||
nodePosition (): object {
|
nodePosition (): object {
|
||||||
const node = this.$store.getters.nodesByName[this.name]; // position responsive to store changes
|
|
||||||
const returnStyles: {
|
const returnStyles: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
} = {
|
} = {
|
||||||
left: node.position[0] + 'px',
|
left: this.position[0] + 'px',
|
||||||
top: node.position[1] + 'px',
|
top: this.position[1] + 'px',
|
||||||
};
|
};
|
||||||
|
|
||||||
return returnStyles;
|
return returnStyles;
|
||||||
|
@ -100,6 +104,7 @@ export const nodeBase = mixins(
|
||||||
'instance',
|
'instance',
|
||||||
'isReadOnly',
|
'isReadOnly',
|
||||||
'isActive',
|
'isActive',
|
||||||
|
'hideActions',
|
||||||
],
|
],
|
||||||
methods: {
|
methods: {
|
||||||
__addNode (node: INodeUi) {
|
__addNode (node: INodeUi) {
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
:instance="instance"
|
:instance="instance"
|
||||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||||
|
:hideActions="pullConnActive"
|
||||||
></node>
|
></node>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -309,7 +310,10 @@ export default mixins(
|
||||||
stopExecutionInProgress: false,
|
stopExecutionInProgress: false,
|
||||||
blankRedirect: false,
|
blankRedirect: false,
|
||||||
credentialsUpdated: false,
|
credentialsUpdated: false,
|
||||||
newNodeInsertPosition: null as null | XYPosition,
|
newNodeInsertPosition: null as XYPosition | null,
|
||||||
|
pullConnActiveNodeName: null as string | null,
|
||||||
|
pullConnActive: false,
|
||||||
|
dropPrevented: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
@ -1191,7 +1195,21 @@ export default mixins(
|
||||||
|
|
||||||
return newNodeData;
|
return newNodeData;
|
||||||
},
|
},
|
||||||
|
getConnection (sourceNodeName: string, sourceNodeOutputIndex: number, targetNodeName: string, targetNodeOuputIndex: number): IConnection | undefined {
|
||||||
|
const nodeConnections = (this.$store.getters.outgoingConnectionsByNodeName(sourceNodeName) as INodeConnections).main;
|
||||||
|
if (nodeConnections) {
|
||||||
|
const connections: IConnection[] = nodeConnections[sourceNodeOutputIndex];
|
||||||
|
|
||||||
|
return connections.find((connection: IConnection) => connection.node === targetNodeName && connection.index === targetNodeOuputIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
connectTwoNodes (sourceNodeName: string, sourceNodeOutputIndex: number, targetNodeName: string, targetNodeOuputIndex: number) {
|
connectTwoNodes (sourceNodeName: string, sourceNodeOutputIndex: number, targetNodeName: string, targetNodeOuputIndex: number) {
|
||||||
|
if (this.getConnection(sourceNodeName, sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const connectionData = [
|
const connectionData = [
|
||||||
{
|
{
|
||||||
node: sourceNodeName,
|
node: sourceNodeName,
|
||||||
|
@ -1266,17 +1284,25 @@ export default mixins(
|
||||||
this.openNodeCreator(info.eventSource);
|
this.openNodeCreator(info.eventSource);
|
||||||
};
|
};
|
||||||
|
|
||||||
let dropPrevented = false;
|
this.instance.bind('connectionAborted', (connection) => {
|
||||||
|
this.pullConnActive = false;
|
||||||
|
|
||||||
this.instance.bind('connectionAborted', (info) => {
|
if (this.dropPrevented) {
|
||||||
if (dropPrevented) {
|
this.dropPrevented = false;
|
||||||
dropPrevented = false;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pullConnActiveNodeName) {
|
||||||
|
const sourceNodeName = this.$store.getters.getNodeNameByIndex(connection.sourceId.slice(NODE_NAME_PREFIX.length));
|
||||||
|
const outputIndex = connection.getParameters().index;
|
||||||
|
|
||||||
|
this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertNodeAfterSelected({
|
insertNodeAfterSelected({
|
||||||
sourceId: info.sourceId,
|
sourceId: connection.sourceId,
|
||||||
index: info.getParameters().index,
|
index: connection.getParameters().index,
|
||||||
eventSource: 'node_connection_drop',
|
eventSource: 'node_connection_drop',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1290,8 +1316,8 @@ export default mixins(
|
||||||
const targetNodeName = this.$store.getters.getNodeNameByIndex(targetInfo.nodeIndex);
|
const targetNodeName = this.$store.getters.getNodeNameByIndex(targetInfo.nodeIndex);
|
||||||
|
|
||||||
// check for duplicates
|
// check for duplicates
|
||||||
if (this.getJSPlumbConnection(sourceNodeName, sourceInfo.index, targetNodeName, targetInfo.index)) {
|
if (this.getConnection(sourceNodeName, sourceInfo.index, targetNodeName, targetInfo.index)) {
|
||||||
dropPrevented = true;
|
this.dropPrevented = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1445,23 +1471,43 @@ export default mixins(
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.instance.bind('connectionDrag', (connection: Connection) => {
|
this.instance.bind('connectionDrag', (connection: Connection) => {
|
||||||
|
this.pullConnActive = true;
|
||||||
this.newNodeInsertPosition = null;
|
this.newNodeInsertPosition = null;
|
||||||
CanvasHelpers.addOverlays(connection, CanvasHelpers.CONNECTOR_DROP_NODE_OVERLAY);
|
CanvasHelpers.addOverlays(connection, CanvasHelpers.CONNECTOR_DROP_NODE_OVERLAY);
|
||||||
|
const nodes = [...document.querySelectorAll('.node-default')];
|
||||||
|
|
||||||
let droppable = false;
|
const onMouseMove = (e: MouseEvent) => {
|
||||||
const onMouseMove = () => {
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = document.querySelector('div.jtk-endpoint.dropHover');
|
const elements = document.querySelector('.jtk-endpoint.dropHover');
|
||||||
if (elements && !droppable) {
|
if (elements) {
|
||||||
droppable = true;
|
|
||||||
CanvasHelpers.showDropConnectionState(connection);
|
CanvasHelpers.showDropConnectionState(connection);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (!elements && droppable) {
|
|
||||||
droppable = false;
|
const intersecting = nodes.find((element: Element) => {
|
||||||
|
const {top, left, right, bottom} = element.getBoundingClientRect();
|
||||||
|
if (top <= e.pageY && bottom >= e.pageY && left <= e.pageX && right >= e.pageX) {
|
||||||
|
const nodeName = (element as HTMLElement).dataset['name'];
|
||||||
|
const node = this.$store.getters.getNodeByName(nodeName) as INodeUi | null;
|
||||||
|
if (node) {
|
||||||
|
const nodeType = this.$store.getters.nodeType(node.type) as INodeTypeDescription;
|
||||||
|
if (nodeType.inputs.length === 1) {
|
||||||
|
this.pullConnActiveNodeName = node.name;
|
||||||
|
CanvasHelpers.showDropConnectionState(connection);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!intersecting) {
|
||||||
CanvasHelpers.showPullConnectionState(connection);
|
CanvasHelpers.showPullConnectionState(connection);
|
||||||
|
this.pullConnActiveNodeName = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue