2021-11-19 01:17:13 -08:00
import { IEndpointOptions , INodeUi , XYPosition } from '@/Interface' ;
2019-06-23 03:35:23 -07:00
import mixins from 'vue-typed-mixins' ;
2020-10-23 04:44:34 -07:00
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers' ;
2019-06-23 03:35:23 -07:00
import { nodeIndex } from '@/components/mixins/nodeIndex' ;
2021-10-18 20:57:49 -07:00
import { NODE_NAME_PREFIX , NO_OP_NODE_TYPE } from '@/constants' ;
2021-11-19 01:17:13 -08:00
import * as CanvasHelpers from '@/views/canvasHelpers' ;
import { Endpoint } from 'jsplumb' ;
import {
INodeTypeDescription ,
} from 'n8n-workflow' ;
2021-12-03 09:53:55 -08:00
import { getStyleTokenValue } from '../helpers' ;
2019-06-23 03:35:23 -07:00
2020-10-23 04:44:34 -07:00
export const nodeBase = mixins (
deviceSupportHelpers ,
nodeIndex ,
) . extend ( {
2019-06-23 03:35:23 -07:00
mounted ( ) {
// Initialize the node
if ( this . data !== null ) {
this . __addNode ( this . data ) ;
}
} ,
computed : {
data ( ) : INodeUi {
2021-11-19 01:17:13 -08:00
return this . $store . getters . getNodeByName ( this . name ) ;
2019-06-23 03:35:23 -07:00
} ,
2021-11-19 01:17:13 -08:00
nodeId ( ) : string {
2019-06-23 03:35:23 -07:00
return NODE_NAME_PREFIX + this . nodeIndex ;
} ,
nodeIndex ( ) : string {
return this . $store . getters . getNodeIndex ( this . data . name ) . toString ( ) ;
} ,
} ,
props : [
'name' ,
'instance' ,
2020-05-24 05:06:22 -07:00
'isReadOnly' ,
2021-08-07 00:35:59 -07:00
'isActive' ,
2021-11-19 01:17:13 -08:00
'hideActions' ,
2019-06-23 03:35:23 -07:00
] ,
methods : {
2021-11-19 01:17:13 -08:00
__addInputEndpoints ( node : INodeUi , nodeTypeData : INodeTypeDescription ) {
2019-06-23 03:35:23 -07:00
// Add Inputs
2021-11-19 01:17:13 -08:00
let index ;
const indexData : {
2019-06-23 03:35:23 -07:00
[ key : string ] : number ;
} = { } ;
2021-11-19 01:17:13 -08:00
nodeTypeData . inputs . forEach ( ( inputName : string , i : number ) = > {
2019-06-23 03:35:23 -07:00
// 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
2021-11-19 01:17:13 -08:00
const anchorPosition = CanvasHelpers . ANCHOR_POSITIONS . input [ nodeTypeData . inputs . length ] [ index ] ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
const newEndpointData : IEndpointOptions = {
uuid : CanvasHelpers.getInputEndpointUUID ( this . nodeIndex , index ) ,
2019-06-23 03:35:23 -07:00
anchor : anchorPosition ,
2021-11-19 01:17:13 -08:00
maxConnections : - 1 ,
endpoint : 'Rectangle' ,
endpointStyle : CanvasHelpers.getInputEndpointStyle ( nodeTypeData , '--color-foreground-xdark' ) ,
endpointHoverStyle : CanvasHelpers.getInputEndpointStyle ( nodeTypeData , '--color-primary' ) ,
2019-06-23 03:35:23 -07:00
isSource : false ,
2021-12-03 09:53:55 -08:00
isTarget : ! this . isReadOnly && nodeTypeData . inputs . length > 1 , // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
2019-06-23 03:35:23 -07:00
parameters : {
nodeIndex : this.nodeIndex ,
type : inputName ,
index ,
} ,
2021-12-03 09:53:55 -08:00
enabled : ! this . isReadOnly , // enabled in default case to allow dragging
cssClass : 'rect-input-endpoint' ,
2021-11-19 01:17:13 -08:00
dragAllowedWhenFull : true ,
2019-06-23 03:35:23 -07:00
dropOptions : {
tolerance : 'touch' ,
hoverClass : 'dropHover' ,
} ,
} ;
2019-08-02 06:56:05 -07:00
if ( nodeTypeData . inputNames ) {
// Apply input names if they got set
newEndpointData . overlays = [
2021-11-19 01:17:13 -08:00
CanvasHelpers . getInputNameOverlay ( nodeTypeData . inputNames [ index ] ) ,
2019-08-02 06:56:05 -07:00
] ;
}
2021-11-19 01:17:13 -08:00
const endpoint : Endpoint = this . instance . addEndpoint ( this . nodeId , newEndpointData ) ;
endpoint . __meta = {
nodeName : node.name ,
2021-12-03 09:53:55 -08:00
nodeId : this.nodeId ,
2021-11-19 01:17:13 -08:00
index : i ,
2021-12-03 09:53:55 -08:00
totalEndpoints : nodeTypeData.inputs.length ,
2021-11-19 01:17:13 -08:00
} ;
2019-06-23 03:35:23 -07:00
2019-08-02 06:56:05 -07:00
// 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
2021-11-19 01:17:13 -08:00
// this.instance.makeTarget(this.nodeId, newEndpointData);
2019-08-02 06:56:05 -07:00
// }
2019-06-23 03:35:23 -07:00
} ) ;
2021-11-19 01:17:13 -08:00
} ,
__addOutputEndpoints ( node : INodeUi , nodeTypeData : INodeTypeDescription ) {
let index ;
const indexData : {
[ key : string ] : number ;
} = { } ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
nodeTypeData . outputs . forEach ( ( inputName : string , i : number ) = > {
2019-06-23 03:35:23 -07:00
// 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
2021-11-19 01:17:13 -08:00
const anchorPosition = CanvasHelpers . ANCHOR_POSITIONS . output [ nodeTypeData . outputs . length ] [ index ] ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
const newEndpointData : IEndpointOptions = {
uuid : CanvasHelpers.getOutputEndpointUUID ( this . nodeIndex , index ) ,
2019-06-23 03:35:23 -07:00
anchor : anchorPosition ,
2021-11-19 01:17:13 -08:00
maxConnections : - 1 ,
endpoint : 'Dot' ,
endpointStyle : CanvasHelpers.getOutputEndpointStyle ( nodeTypeData , '--color-foreground-xdark' ) ,
endpointHoverStyle : CanvasHelpers.getOutputEndpointStyle ( nodeTypeData , '--color-primary' ) ,
isSource : true ,
2019-06-23 03:35:23 -07:00
isTarget : false ,
2021-11-19 01:17:13 -08:00
enabled : ! this . isReadOnly ,
2019-06-23 03:35:23 -07:00
parameters : {
nodeIndex : this.nodeIndex ,
type : inputName ,
index ,
} ,
2021-12-03 09:53:55 -08:00
cssClass : 'dot-output-endpoint' ,
2021-11-19 01:17:13 -08:00
dragAllowedWhenFull : false ,
2019-06-23 03:35:23 -07:00
dragProxy : [ 'Rectangle' , { width : 1 , height : 1 , strokeWidth : 0 } ] ,
} ;
if ( nodeTypeData . outputNames ) {
// Apply output names if they got set
newEndpointData . overlays = [
2021-11-19 01:17:13 -08:00
CanvasHelpers . getOutputNameOverlay ( nodeTypeData . outputNames [ index ] ) ,
2019-06-23 03:35:23 -07:00
] ;
}
2021-12-03 09:53:55 -08:00
const endpoint : Endpoint = this . instance . addEndpoint ( this . nodeId , { . . . newEndpointData } ) ;
2021-11-19 01:17:13 -08:00
endpoint . __meta = {
nodeName : node.name ,
2021-12-03 09:53:55 -08:00
nodeId : this.nodeId ,
2021-11-19 01:17:13 -08:00
index : i ,
2021-12-03 09:53:55 -08:00
totalEndpoints : nodeTypeData.outputs.length ,
2021-11-19 01:17:13 -08:00
} ;
2021-12-03 09:53:55 -08:00
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' ,
2021-12-15 04:16:53 -08:00
hoverMessage : this.$locale.baseText ( 'nodeBase.clickToAddNodeOrDragToConnect' ) ,
2021-12-03 09:53:55 -08:00
} ,
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 ,
} ;
}
2019-06-23 03:35:23 -07:00
} ) ;
2021-11-19 01:17:13 -08:00
} ,
__makeInstanceDraggable ( node : INodeUi ) {
2020-06-07 15:34:15 -07:00
// TODO: This caused problems with displaying old information
// https://github.com/jsplumb/katavorio/wiki
// https://jsplumb.github.io/jsplumb/home.html
// Make nodes draggable
2021-11-19 01:17:13 -08:00
this . instance . draggable ( this . nodeId , {
grid : [ CanvasHelpers . GRID_SIZE , CanvasHelpers . GRID_SIZE ] ,
2020-06-07 15:34:15 -07:00
start : ( params : { e : MouseEvent } ) = > {
if ( this . isReadOnly === true ) {
// Do not allow to move nodes in readOnly mode
return false ;
}
:sparkles: Improve Waiting Webhook call state in WF Canvas (#2430)
* N8N-2586 Improve Waiting Webhook call state in WF Canvas
* N8N-2586 Added watcher for showing Webhook's Node Tooltip on execution
* N8N-2586 Show helping tooltip for trigger node if wokrflow is running, it is a trigger node, if it is only one trigger node in WF
* N8N-2586 Rework/Move logic to computed property, Created getter for ActveTriggerNodesInWokrflow, Add style to trigger node's tooltip, remove comments
* N8N-2586 Added EventTriggerDescription prop in INodeTypeDescription Interface, Updated Logic for TriggerNode Tooltip based on the new prop
* N8N-2586 Add new use cases/watcher to show Trigger Nodes Tooltip / If has issues, if paused, if wokrlfow is running, Refactor Getter
* N8N-2586 Added z-index to tooltip, Added new Scenario for Tooltip if it is Draged&Droped on the WF
* N8N-2586 Refactor computed property for draged nodes
* N8N-2586 Fixed Conflicts
* N8N-2586 Fixed Tooltip
* N8N-2586 Dont show tooltip on core trigger nodes that execute automatically
* N8N-2586 Fixed Webhook tooltip when adding/deleting canvas during WF execution
* N8N-2586 Updated Logic, Simplify the code
* N8N-2586 Simplify Code
* N8N-2586 Added check for nodetype
* update dragging to use local state
* N8N-2586 Added eventTriggerDescription to Interval Node
* add comment, use new getter
* update to always check
Co-authored-by: Mutasem <mutdmour@gmail.com>
2021-11-25 14:33:41 -08:00
// @ts-ignore
this . dragging = true ;
2020-06-07 15:34:15 -07:00
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 } ) = > {
:sparkles: Improve Waiting Webhook call state in WF Canvas (#2430)
* N8N-2586 Improve Waiting Webhook call state in WF Canvas
* N8N-2586 Added watcher for showing Webhook's Node Tooltip on execution
* N8N-2586 Show helping tooltip for trigger node if wokrflow is running, it is a trigger node, if it is only one trigger node in WF
* N8N-2586 Rework/Move logic to computed property, Created getter for ActveTriggerNodesInWokrflow, Add style to trigger node's tooltip, remove comments
* N8N-2586 Added EventTriggerDescription prop in INodeTypeDescription Interface, Updated Logic for TriggerNode Tooltip based on the new prop
* N8N-2586 Add new use cases/watcher to show Trigger Nodes Tooltip / If has issues, if paused, if wokrlfow is running, Refactor Getter
* N8N-2586 Added z-index to tooltip, Added new Scenario for Tooltip if it is Draged&Droped on the WF
* N8N-2586 Refactor computed property for draged nodes
* N8N-2586 Fixed Conflicts
* N8N-2586 Fixed Tooltip
* N8N-2586 Dont show tooltip on core trigger nodes that execute automatically
* N8N-2586 Fixed Webhook tooltip when adding/deleting canvas during WF execution
* N8N-2586 Updated Logic, Simplify the code
* N8N-2586 Simplify Code
* N8N-2586 Added check for nodetype
* update dragging to use local state
* N8N-2586 Added eventTriggerDescription to Interval Node
* add comment, use new getter
* update to always check
Co-authored-by: Mutasem <mutdmour@gmail.com>
2021-11-25 14:33:41 -08:00
// @ts-ignore
this . dragging = false ;
2020-06-07 15:34:15 -07:00
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 ) ;
2019-06-23 03:35:23 -07:00
}
2020-06-07 15:34:15 -07:00
// 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
2021-11-19 01:17:13 -08:00
let newNodePositon : XYPosition ;
2020-06-07 15:34:15 -07:00
moveNodes . forEach ( ( node : INodeUi ) = > {
const nodeElement = ` node- ${ this . getNodeIndex ( node . name ) } ` ;
const element = document . getElementById ( nodeElement ) ;
if ( element === null ) {
return ;
2019-06-23 03:35:23 -07:00
}
2020-06-07 15:34:15 -07:00
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 ) ;
} ) ;
2021-11-19 01:17:13 -08:00
this . $emit ( 'moved' , node ) ;
2020-06-07 15:34:15 -07:00
}
} ,
filter : '.node-description, .node-description .node-name, .node-description .node-subtitle' ,
} ) ;
2021-11-19 01:17:13 -08:00
} ,
__addNode ( node : INodeUi ) {
2022-02-05 03:57:48 -08:00
let nodeTypeData = this . $store . getters . nodeType ( node . type , node . typeVersion ) as INodeTypeDescription | null ;
2021-11-19 01:17:13 -08:00
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 ;
}
2020-06-07 15:34:15 -07:00
2021-11-19 01:17:13 -08:00
this . __addInputEndpoints ( node , nodeTypeData ) ;
this . __addOutputEndpoints ( node , nodeTypeData ) ;
this . __makeInstanceDraggable ( node ) ;
2019-06-23 03:35:23 -07:00
} ,
2021-08-08 02:14:09 -07:00
touchEnd ( e : MouseEvent ) {
if ( this . isTouchDevice ) {
if ( this . $store . getters . isActionActive ( 'dragActive' ) ) {
this . $store . commit ( 'removeActiveAction' , 'dragActive' ) ;
}
}
} ,
2020-10-23 09:15:52 -07:00
mouseLeftClick ( e : MouseEvent ) {
2020-12-23 23:37:13 -08:00
// @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 ;
}
}
2020-10-23 09:15:52 -07:00
if ( ! this . isTouchDevice ) {
if ( this . $store . getters . isActionActive ( 'dragActive' ) ) {
this . $store . commit ( 'removeActiveAction' , 'dragActive' ) ;
2019-07-17 10:05:03 -07:00
} else {
2020-10-23 09:15:52 -07:00
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 ) ;
}
2019-07-17 10:05:03 -07:00
}
2019-06-23 03:35:23 -07:00
}
} ,
} ,
} ) ;