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>
|
||||
<div class="node-wrapper" :style="nodePosition">
|
||||
<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">
|
||||
<n8n-tooltip placement="top" >
|
||||
<div slot="content" v-html="nodeIssues"></div>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<div class="node-executing-info" title="Node is executing">
|
||||
<font-awesome-icon icon="sync-alt" spin />
|
||||
</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" >
|
||||
<font-awesome-icon icon="trash" />
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="node-icon-wrapper" :style="iconStyleData">
|
||||
<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" />
|
||||
</div>
|
||||
<div v-else class="node-icon-placeholder">
|
||||
|
@ -12,25 +12,37 @@
|
|||
|
||||
<script lang="ts">
|
||||
|
||||
import { IVersionNode } from '@/Interface';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
import Vue from 'vue';
|
||||
|
||||
interface NodeIconData {
|
||||
type: string;
|
||||
path: string;
|
||||
path?: string;
|
||||
fileExtension?: string;
|
||||
fileBuffer?: string;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'NodeIcon',
|
||||
props: [
|
||||
'nodeType',
|
||||
'size',
|
||||
'disabled',
|
||||
'circle',
|
||||
],
|
||||
props: {
|
||||
nodeType: {},
|
||||
size: {
|
||||
type: Number,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
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) {
|
||||
return {color};
|
||||
}
|
||||
|
@ -49,6 +61,12 @@ export default Vue.extend({
|
|||
'max-width': this.size + 'px',
|
||||
};
|
||||
},
|
||||
imageStyleData (): object {
|
||||
return {
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
};
|
||||
},
|
||||
isSvgIcon (): boolean {
|
||||
if (this.nodeIconData && this.nodeIconData.type === 'file' && this.nodeIconData.fileExtension === 'svg') {
|
||||
return true;
|
||||
|
@ -56,26 +74,27 @@ export default Vue.extend({
|
|||
return false;
|
||||
},
|
||||
nodeIconData (): null | NodeIconData {
|
||||
if (this.nodeType === null) {
|
||||
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
||||
if (nodeType === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.nodeType.iconData) {
|
||||
return this.nodeType.iconData;
|
||||
if ((nodeType as IVersionNode).iconData) {
|
||||
return (nodeType as IVersionNode).iconData;
|
||||
}
|
||||
|
||||
const restUrl = this.$store.getters.getRestUrl;
|
||||
|
||||
if (this.nodeType.icon) {
|
||||
if (nodeType.icon) {
|
||||
let type, path;
|
||||
[type, path] = this.nodeType.icon.split(':');
|
||||
[type, path] = nodeType.icon.split(':');
|
||||
const returnData: NodeIconData = {
|
||||
type,
|
||||
path,
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -82,13 +82,17 @@ export const nodeBase = mixins(
|
|||
nodeIndex (): string {
|
||||
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 {
|
||||
const node = this.$store.getters.nodesByName[this.name]; // position responsive to store changes
|
||||
const returnStyles: {
|
||||
[key: string]: string;
|
||||
} = {
|
||||
left: node.position[0] + 'px',
|
||||
top: node.position[1] + 'px',
|
||||
left: this.position[0] + 'px',
|
||||
top: this.position[1] + 'px',
|
||||
};
|
||||
|
||||
return returnStyles;
|
||||
|
@ -100,6 +104,7 @@ export const nodeBase = mixins(
|
|||
'instance',
|
||||
'isReadOnly',
|
||||
'isActive',
|
||||
'hideActions',
|
||||
],
|
||||
methods: {
|
||||
__addNode (node: INodeUi) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
:isReadOnly="isReadOnly"
|
||||
:instance="instance"
|
||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||
:hideActions="pullConnActive"
|
||||
></node>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -309,7 +310,10 @@ export default mixins(
|
|||
stopExecutionInProgress: false,
|
||||
blankRedirect: false,
|
||||
credentialsUpdated: false,
|
||||
newNodeInsertPosition: null as null | XYPosition,
|
||||
newNodeInsertPosition: null as XYPosition | null,
|
||||
pullConnActiveNodeName: null as string | null,
|
||||
pullConnActive: false,
|
||||
dropPrevented: false,
|
||||
};
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
@ -1191,7 +1195,21 @@ export default mixins(
|
|||
|
||||
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) {
|
||||
if (this.getConnection(sourceNodeName, sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionData = [
|
||||
{
|
||||
node: sourceNodeName,
|
||||
|
@ -1266,17 +1284,25 @@ export default mixins(
|
|||
this.openNodeCreator(info.eventSource);
|
||||
};
|
||||
|
||||
let dropPrevented = false;
|
||||
this.instance.bind('connectionAborted', (connection) => {
|
||||
this.pullConnActive = false;
|
||||
|
||||
this.instance.bind('connectionAborted', (info) => {
|
||||
if (dropPrevented) {
|
||||
dropPrevented = false;
|
||||
if (this.dropPrevented) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
insertNodeAfterSelected({
|
||||
sourceId: info.sourceId,
|
||||
index: info.getParameters().index,
|
||||
sourceId: connection.sourceId,
|
||||
index: connection.getParameters().index,
|
||||
eventSource: 'node_connection_drop',
|
||||
});
|
||||
});
|
||||
|
@ -1290,8 +1316,8 @@ export default mixins(
|
|||
const targetNodeName = this.$store.getters.getNodeNameByIndex(targetInfo.nodeIndex);
|
||||
|
||||
// check for duplicates
|
||||
if (this.getJSPlumbConnection(sourceNodeName, sourceInfo.index, targetNodeName, targetInfo.index)) {
|
||||
dropPrevented = true;
|
||||
if (this.getConnection(sourceNodeName, sourceInfo.index, targetNodeName, targetInfo.index)) {
|
||||
this.dropPrevented = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1445,23 +1471,43 @@ export default mixins(
|
|||
|
||||
// @ts-ignore
|
||||
this.instance.bind('connectionDrag', (connection: Connection) => {
|
||||
this.pullConnActive = true;
|
||||
this.newNodeInsertPosition = null;
|
||||
CanvasHelpers.addOverlays(connection, CanvasHelpers.CONNECTOR_DROP_NODE_OVERLAY);
|
||||
const nodes = [...document.querySelectorAll('.node-default')];
|
||||
|
||||
let droppable = false;
|
||||
const onMouseMove = () => {
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = document.querySelector('div.jtk-endpoint.dropHover');
|
||||
if (elements && !droppable) {
|
||||
droppable = true;
|
||||
const elements = document.querySelector('.jtk-endpoint.dropHover');
|
||||
if (elements) {
|
||||
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);
|
||||
this.pullConnActiveNodeName = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue