2019-06-23 03:35:23 -07:00
< template >
2021-11-19 01:17:13 -08:00
< div : class = "{'node-wrapper': true, selected: isSelected}" :style ="nodePosition" >
< div class = "select-background" v-show ="isSelected" > < / div >
< div : class = "{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name ="data.name" :ref ="data.name" >
< div :class ="nodeClass" :style ="nodeStyle" @dblclick ="setNodeActive" @click.left ="mouseLeftClick" v -touch :start ="touchStart" v -touch :end ="touchEnd" >
< div v-if ="!data.disabled" :class="{'node-info-icon': true, 'shift-icon': shiftOutputCount}" >
< div v-if ="hasIssues" class="node-issues" >
< n8n -tooltip placement = "bottom" >
< div slot = "content" v-html ="nodeIssues" > < / div >
< font -awesome -icon icon = "exclamation-triangle" / >
< / n 8 n - t o o l t i p >
< / div >
< div v -else -if = " waiting " class = "waiting" >
< n8n -tooltip placement = "bottom" >
< div slot = "content" v-html ="waiting" > < / div >
< font -awesome -icon icon = "clock" / >
< / n 8 n - t o o l t i p >
< / div >
< span v -else -if = " workflowDataItems " class = "data-count" >
< font -awesome -icon icon = "check" / >
< span v-if ="workflowDataItems > 1" class="items-count" > {{ workflowDataItems }} < / span >
< / span >
< / div >
2019-07-25 12:57:27 -07:00
2021-11-20 06:58:21 -08:00
< div class = "node-executing-info" :title ="$baseText('node.nodeIsExecuting')" >
2021-11-19 01:17:13 -08:00
< font -awesome -icon icon = "sync-alt" spin / >
< / div >
2021-08-21 05:11:32 -07:00
2021-11-19 01:17:13 -08:00
< NodeIcon class = "node-icon" :nodeType ="nodeType" :size ="40" :shrink ="false" :disabled ="this.data.disabled" / >
2019-06-23 03:35:23 -07:00
< / div >
2021-11-19 01:17:13 -08:00
< div class = "node-options no-select-on-click" v-if ="!isReadOnly" v-show="!hideActions" >
2021-11-19 06:35:38 -08:00
< div v -touch :tap ="deleteNode" class = "option" :title ="$baseText('node.deleteNode')" >
2019-07-25 12:57:27 -07:00
< font -awesome -icon icon = "trash" / >
< / div >
2021-11-10 10:41:40 -08:00
< div v -touch :tap ="disableNode" class = "option" :title ="$baseText('node.activateDeactivateNode')" >
2019-07-25 12:57:27 -07:00
< font -awesome -icon :icon ="nodeDisabledIcon" / >
< / div >
2021-11-10 10:41:40 -08:00
< div v -touch :tap ="duplicateNode" class = "option" :title ="$baseText('node.duplicateNode')" >
2019-07-25 12:57:27 -07:00
< font -awesome -icon icon = "clone" / >
< / div >
2021-11-10 10:41:40 -08:00
< div v -touch :tap ="setNodeActive" class = "option touch" :title ="$baseText('node.editNode')" v-if ="!isReadOnly" >
2020-02-15 17:29:08 -08:00
< font -awesome -icon class = "execute-icon" icon = "cog" / >
< / div >
2021-11-10 10:41:40 -08:00
< div v -touch :tap ="executeNode" class = "option" :title ="$baseText('node.executeNode')" v-if ="!isReadOnly && !workflowRunning" >
2019-07-25 12:57:27 -07:00
< font -awesome -icon class = "execute-icon" icon = "play-circle" / >
< / div >
2019-06-23 03:35:23 -07:00
< / div >
2021-11-19 01:17:13 -08:00
< div : class = "{'disabled-linethrough': true, success: workflowDataItems > 0}" v-if ="showDisabledLinethrough" > < / div >
2019-06-23 03:35:23 -07:00
< / div >
2019-07-25 09:50:45 -07:00
< div class = "node-description" >
< div class = "node-name" :title ="data.name" >
2021-11-20 06:58:21 -08:00
< p >
{ { this . $headerText ( {
key : ` headers. ${ shortNodeType } .displayName ` ,
fallback : data . name ,
} )
} }
< / p >
< p v-if ="data.disabled" > ( {{ $ baseText ( ' node.disabled ' ) }}} < / p >
2019-07-25 09:50:45 -07:00
< / div >
< div v-if ="nodeSubtitle !== undefined" class="node-subtitle" :title ="nodeSubtitle" >
2021-11-20 06:58:21 -08:00
{ { nodeSubtitle } }
2019-07-25 09:50:45 -07:00
< / div >
2019-06-23 03:35:23 -07:00
< / div >
< / div >
< / template >
< script lang = "ts" >
import Vue from 'vue' ;
2021-08-21 05:11:32 -07:00
import { WAIT _TIME _UNLIMITED } from '@/constants' ;
2021-06-18 13:47:03 -07:00
import { externalHooks } from '@/components/mixins/externalHooks' ;
2019-06-23 03:35:23 -07:00
import { nodeBase } from '@/components/mixins/nodeBase' ;
2021-05-05 17:46:33 -07:00
import { nodeHelpers } from '@/components/mixins/nodeHelpers' ;
2021-11-10 10:41:40 -08:00
import { renderText } from '@/components/mixins/renderText' ;
2019-07-12 05:14:36 -07:00
import { workflowHelpers } from '@/components/mixins/workflowHelpers' ;
2019-06-23 03:35:23 -07:00
import {
INodeTypeDescription ,
2021-11-19 01:17:13 -08:00
ITaskData ,
2019-06-23 03:35:23 -07:00
NodeHelpers ,
} from 'n8n-workflow' ;
import NodeIcon from '@/components/NodeIcon.vue' ;
import mixins from 'vue-typed-mixins' ;
2021-08-21 05:11:32 -07:00
import { get } from 'lodash' ;
2021-11-19 01:17:13 -08:00
import { getStyleTokenValue } from './helpers' ;
import { INodeUi , XYPosition } from '@/Interface' ;
2021-08-21 05:11:32 -07:00
2021-11-10 10:41:40 -08:00
export default mixins ( externalHooks , nodeBase , nodeHelpers , renderText , workflowHelpers ) . extend ( {
2019-06-23 03:35:23 -07:00
name : 'Node' ,
components : {
NodeIcon ,
} ,
computed : {
2021-11-19 01:17:13 -08:00
nodeRunData ( ) : ITaskData [ ] {
return this . $store . getters . getWorkflowResultDataByNodeName ( this . data . name ) ;
} ,
hasIssues ( ) : boolean {
if ( this . data . issues !== undefined && Object . keys ( this . data . issues ) . length ) {
return true ;
}
return false ;
} ,
workflowDataItems ( ) : number {
const workflowResultDataNode = this . nodeRunData ;
2019-06-24 01:28:18 -07:00
if ( workflowResultDataNode === null ) {
2019-06-23 03:35:23 -07:00
return 0 ;
}
2019-06-24 01:28:18 -07:00
return workflowResultDataNode . length ;
2019-06-23 03:35:23 -07:00
} ,
isExecuting ( ) : boolean {
return this . $store . getters . executingNode === this . data . name ;
} ,
nodeType ( ) : INodeTypeDescription | null {
return this . $store . getters . nodeType ( this . data . type ) ;
} ,
2021-11-19 01:17:13 -08:00
nodeClass ( ) : object {
return {
'node-box' : true ,
disabled : this . data . disabled ,
executing : this . isExecuting ,
} ;
2019-06-23 03:35:23 -07:00
} ,
nodeIssues ( ) : string {
if ( this . data . issues === undefined ) {
return '' ;
}
const nodeIssues = NodeHelpers . nodeIssuesToString ( this . data . issues , this . data ) ;
2021-11-20 06:58:21 -08:00
return ` ${ this . $baseText ( 'node.issues' ) } :<br /> - ` + nodeIssues . join ( '<br /> - ' ) ;
2019-06-23 03:35:23 -07:00
} ,
nodeDisabledIcon ( ) : string {
if ( this . data . disabled === false ) {
return 'pause' ;
} else {
return 'play' ;
}
} ,
2021-11-19 01:17:13 -08:00
position ( ) : XYPosition {
const node = this . $store . getters . nodesByName [ this . name ] as INodeUi ; // position responsive to store changes
return node . position ;
} ,
showDisabledLinethrough ( ) : boolean {
return ! ! ( this . data . disabled && this . nodeType && this . nodeType . inputs . length === 1 && this . nodeType . outputs . length === 1 ) ;
} ,
nodePosition ( ) : object {
const returnStyles : {
[ key : string ] : string ;
} = {
left : this . position [ 0 ] + 'px' ,
top : this . position [ 1 ] + 'px' ,
} ;
return returnStyles ;
} ,
2021-11-20 06:58:21 -08:00
shortNodeType ( ) : string {
return this . data . type . replace ( 'n8n-nodes-base.' , '' ) ;
2021-11-19 01:17:13 -08:00
} ,
2021-08-21 05:11:32 -07:00
waiting ( ) : string | undefined {
const workflowExecution = this . $store . getters . getWorkflowExecution ;
if ( workflowExecution && workflowExecution . waitTill ) {
const lastNodeExecuted = get ( workflowExecution , 'data.resultData.lastNodeExecuted' ) ;
if ( this . name === lastNodeExecuted ) {
const waitDate = new Date ( workflowExecution . waitTill ) ;
if ( waitDate . toISOString ( ) === WAIT _TIME _UNLIMITED ) {
2021-11-20 06:58:21 -08:00
return this . $baseText ( 'node.theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall' ) ;
2021-08-21 05:11:32 -07:00
}
2021-11-20 06:58:21 -08:00
return this . $baseText (
'node.nodeIsWaitingTill' ,
{
interpolate : {
date : waitDate . toLocaleDateString ( ) ,
time : waitDate . toLocaleTimeString ( ) ,
} ,
} ,
) ;
2021-08-21 05:11:32 -07:00
}
}
return ;
} ,
2019-06-23 03:35:23 -07:00
workflowRunning ( ) : boolean {
return this . $store . getters . isActionActive ( 'workflowRunning' ) ;
} ,
2021-11-19 01:17:13 -08:00
nodeStyle ( ) : object {
let borderColor = getStyleTokenValue ( '--color-foreground-xdark' ) ;
if ( this . data . disabled ) {
borderColor = getStyleTokenValue ( '--color-foreground-base' ) ;
}
else if ( ! this . isExecuting ) {
if ( this . hasIssues ) {
borderColor = getStyleTokenValue ( '--color-danger' ) ;
}
else if ( this . waiting ) {
borderColor = getStyleTokenValue ( '--color-secondary' ) ;
}
else if ( this . workflowDataItems ) {
borderColor = getStyleTokenValue ( '--color-success' ) ;
}
}
const returnStyles : {
[ key : string ] : string ;
} = {
'border-color' : borderColor ,
} ;
return returnStyles ;
} ,
isSelected ( ) : boolean {
return this . $store . getters . getSelectedNodes . find ( ( node : INodeUi ) => node . name === this . data . name ) ;
} ,
shiftOutputCount ( ) : boolean {
return ! ! ( this . nodeType && this . nodeType . outputs . length > 2 ) ;
} ,
2021-08-07 00:35:59 -07:00
} ,
watch : {
isActive ( newValue , oldValue ) {
if ( ! newValue && oldValue ) {
this . setSubtitle ( ) ;
}
2019-07-12 05:14:36 -07:00
} ,
2021-11-19 01:17:13 -08:00
nodeRunData ( newValue ) {
this . $emit ( 'run' , { name : this . data . name , data : newValue , waiting : ! ! this . waiting } ) ;
} ,
2019-06-23 03:35:23 -07:00
} ,
2021-08-07 00:35:59 -07:00
mounted ( ) {
this . setSubtitle ( ) ;
2021-11-19 01:17:13 -08:00
setTimeout ( ( ) => {
this . $emit ( 'run' , { name : this . data . name , data : this . nodeRunData , waiting : ! ! this . waiting } ) ;
} , 0 ) ;
2021-08-07 00:35:59 -07:00
} ,
2019-06-23 03:35:23 -07:00
data ( ) {
return {
2020-10-24 11:01:13 -07:00
isTouchActive : false ,
2021-08-07 00:35:59 -07:00
nodeSubtitle : '' ,
2019-06-23 03:35:23 -07:00
} ;
} ,
methods : {
2021-08-07 00:35:59 -07:00
setSubtitle ( ) {
this . nodeSubtitle = this . getNodeSubtitle ( this . data , this . nodeType , this . getWorkflow ( ) ) || '' ;
} ,
2019-06-23 03:35:23 -07:00
disableNode ( ) {
2020-02-09 23:18:44 -08:00
this . disableNodes ( [ this . data ] ) ;
2021-10-18 20:57:49 -07:00
this . $telemetry . track ( 'User set node enabled status' , { node _type : this . data . type , is _enabled : ! this . data . disabled , workflow _id : this . $store . getters . workflowId } ) ;
2019-06-23 03:35:23 -07:00
} ,
executeNode ( ) {
2021-05-05 17:46:33 -07:00
this . $emit ( 'runWorkflow' , this . data . name , 'Node.executeNode' ) ;
2019-06-23 03:35:23 -07:00
} ,
deleteNode ( ) {
2021-06-18 13:47:03 -07:00
this . $externalHooks ( ) . run ( 'node.deleteNode' , { node : this . data } ) ;
2021-10-18 20:57:49 -07:00
this . $telemetry . track ( 'User deleted node' , { node _type : this . data . type , workflow _id : this . $store . getters . workflowId } ) ;
2019-06-23 03:35:23 -07:00
Vue . nextTick ( ( ) => {
// Wait a tick else vue causes problems because the data is gone
this . $emit ( 'removeNode' , this . data . name ) ;
} ) ;
} ,
duplicateNode ( ) {
Vue . nextTick ( ( ) => {
// Wait a tick else vue causes problems because the data is gone
this . $emit ( 'duplicateNode' , this . data . name ) ;
} ) ;
} ,
setNodeActive ( ) {
this . $store . commit ( 'setActiveNode' , this . data . name ) ;
} ,
2020-10-24 11:01:13 -07:00
touchStart ( ) {
if ( this . isTouchDevice === true && this . isMacOs === false && this . isTouchActive === false ) {
this . isTouchActive = true ;
setTimeout ( ( ) => {
this . isTouchActive = false ;
} , 2000 ) ;
}
} ,
2019-06-23 03:35:23 -07:00
} ,
} ) ;
< / script >
2021-11-19 01:17:13 -08:00
< style lang = "scss" scoped >
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - wrapper {
2019-06-23 03:35:23 -07:00
position : absolute ;
2019-07-25 09:50:45 -07:00
width : 100 px ;
height : 100 px ;
2019-06-23 03:35:23 -07:00
2019-07-25 09:50:45 -07:00
. node - description {
2019-06-23 03:35:23 -07:00
position : absolute ;
2021-11-19 01:17:13 -08:00
top : 100 px ;
2019-07-25 09:50:45 -07:00
left : - 50 px ;
2021-11-19 01:17:13 -08:00
line - height : 1.5 ;
2019-07-25 09:50:45 -07:00
text - align : center ;
2019-07-25 12:57:27 -07:00
cursor : default ;
2021-11-19 01:17:13 -08:00
padding : 8 px ;
width : 200 px ;
pointer - events : none ; // prevent container from being draggable
2019-07-25 12:57:27 -07:00
2021-11-19 01:17:13 -08:00
. node - name > p { // must be paragraph tag to have two lines in safari
2019-07-25 12:57:27 -07:00
text - overflow : ellipsis ;
2021-11-19 01:17:13 -08:00
display : - webkit - box ;
- webkit - box - orient : vertical ;
- webkit - line - clamp : 2 ;
overflow : hidden ;
overflow - wrap : anywhere ;
font - weight : var ( -- font - weight - bold ) ;
line - height : var ( -- font - line - height - compact ) ;
2019-07-25 12:57:27 -07:00
}
. node - subtitle {
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
font - weight : 400 ;
color : $ -- custom - font - light ;
font - size : 0.8 em ;
}
2019-06-23 03:35:23 -07:00
}
2019-07-25 12:57:27 -07:00
. node - default {
2021-11-19 01:17:13 -08:00
position : absolute ;
2019-07-25 09:50:45 -07:00
width : 100 % ;
height : 100 % ;
2019-07-25 12:57:27 -07:00
cursor : pointer ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
. node - box {
width : 100 % ;
height : 100 % ;
border : 2 px solid var ( -- color - foreground - xdark ) ;
border - radius : var ( -- border - radius - large ) ;
background - color : var ( -- color - background - xlight ) ;
2019-07-25 12:57:27 -07:00
2021-11-19 01:17:13 -08:00
& . executing {
background - color : $ -- color - primary - light ! important ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
. node - executing - info {
display : inline - block ;
}
2019-07-25 12:57:27 -07:00
}
2019-06-23 03:35:23 -07:00
}
2020-10-24 11:01:13 -07:00
& . touch - active ,
2019-07-25 12:57:27 -07:00
& : hover {
. node - execute {
display : initial ;
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - options {
display : initial ;
}
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - executing - info {
display : none ;
position : absolute ;
left : 0 px ;
top : 0 px ;
z - index : 12 ;
width : 100 % ;
height : 100 % ;
font - size : 3.75 em ;
line - height : 1.65 em ;
text - align : center ;
color : rgba ( $ -- color - primary , 0.7 ) ;
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - icon {
position : absolute ;
2021-11-19 01:17:13 -08:00
top : calc ( 50 % - 20 px ) ;
left : calc ( 50 % - 20 px ) ;
2019-07-25 12:57:27 -07:00
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - info - icon {
position : absolute ;
2021-11-19 01:17:13 -08:00
bottom : 6 px ;
right : 6 px ;
2019-06-23 03:35:23 -07:00
2021-11-19 01:17:13 -08:00
& . shift - icon {
right : 12 px ;
}
. data - count {
2019-07-25 12:57:27 -07:00
font - weight : 600 ;
2021-11-19 01:17:13 -08:00
color : var ( -- color - success ) ;
2019-06-23 03:35:23 -07:00
}
2021-08-21 05:11:32 -07:00
2021-11-19 01:17:13 -08:00
. node - issues {
color : var ( -- color - danger ) ;
2021-08-21 05:11:32 -07:00
}
2019-07-25 09:50:45 -07:00
2021-11-19 01:17:13 -08:00
. items - count {
font - size : var ( -- font - size - s ) ;
}
2019-07-25 12:57:27 -07:00
}
2021-08-21 05:11:32 -07:00
. waiting {
2021-11-19 01:17:13 -08:00
color : var ( -- color - secondary ) ;
2021-08-21 05:11:32 -07:00
}
2019-07-25 12:57:27 -07:00
. node - options {
display : none ;
position : absolute ;
2019-07-25 22:49:00 -07:00
top : - 25 px ;
2019-07-25 12:57:27 -07:00
left : - 10 px ;
width : 120 px ;
2021-11-19 01:17:13 -08:00
height : 24 px ;
2019-07-25 12:57:27 -07:00
font - size : 0.9 em ;
text - align : left ;
z - index : 10 ;
color : # aaa ;
text - align : center ;
. option {
2021-08-29 04:36:17 -07:00
width : 28 px ;
2019-07-25 12:57:27 -07:00
display : inline - block ;
2020-02-15 17:29:08 -08:00
& . touch {
display : none ;
}
2019-07-25 12:57:27 -07:00
& : hover {
color : $ -- color - primary ;
}
. execute - icon {
position : relative ;
top : 2 px ;
font - size : 1.2 em ;
}
2019-06-23 03:35:23 -07:00
}
}
2019-07-25 22:49:00 -07:00
2020-02-15 17:29:08 -08:00
& . is - touch - device . node - options {
left : - 25 px ;
width : 150 px ;
2020-02-09 00:06:01 -08:00
2020-02-15 17:29:08 -08:00
. option . touch {
display : initial ;
2020-02-09 00:06:01 -08:00
}
}
2021-11-19 01:17:13 -08:00
}
}
2020-02-09 00:06:01 -08:00
2021-11-19 01:17:13 -08:00
. select - background {
display : block ;
background - color : hsla ( var ( -- color - foreground - base - h ) , var ( -- color - foreground - base - s ) , var ( -- color - foreground - base - l ) , 60 % ) ;
border - radius : var ( -- border - radius - xlarge ) ;
overflow : hidden ;
position : absolute ;
left : - 8 px ! important ;
top : - 8 px ! important ;
height : 116 px ;
width : 116 px ! important ;
}
. disabled - linethrough {
border : 1 px solid var ( -- color - foreground - dark ) ;
position : absolute ;
top : 49 px ;
left : - 3 px ;
width : 111 px ;
pointer - events : none ;
& . success {
border - color : var ( -- color - success - light ) ;
2019-06-23 03:35:23 -07:00
}
}
< / style >
2021-11-19 01:17:13 -08:00
< style lang = "scss" >
/** node */
. node - wrapper . selected {
z - index : 2 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
/** connector */
2019-06-23 03:35:23 -07:00
. jtk - connector {
2021-11-19 01:17:13 -08:00
z - index : 3 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
. jtk - connector path {
transition : stroke .1 s ease - in - out ;
}
. jtk - connector . success {
z - index : 4 ;
}
/** node endpoints */
2019-06-23 03:35:23 -07:00
. jtk - endpoint {
z - index : 5 ;
}
2021-11-19 01:17:13 -08:00
. jtk - connector . jtk - hover {
z - index : 6 ;
}
. disabled - linethrough {
z - index : 6 ;
}
. jtk - endpoint . jtk - hover {
z - index : 7 ;
}
2019-06-23 03:35:23 -07:00
. jtk - overlay {
2021-11-19 01:17:13 -08:00
z - index : 7 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
. jtk - connector . jtk - dragging {
z - index : 8 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
. jtk - endpoint . jtk - drag - active {
z - index : 9 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
. connection - actions {
z - index : 10 ;
2019-06-23 03:35:23 -07:00
}
2021-11-19 01:17:13 -08:00
. node - options {
z - index : 10 ;
}
. drop - add - node - label {
z - index : 10 ;
}
2019-06-23 03:35:23 -07:00
< / style >