2019-06-23 03:35:23 -07:00
< template >
2019-07-25 12:57:27 -07:00
< div class = "node-wrapper" :style ="nodePosition" >
2021-08-08 02:14:09 -07:00
< div class = "node-default" :ref ="data.name" :style ="nodeStyle" :class ="nodeClass" @dblclick ="setNodeActive" @click.left ="mouseLeftClick" v -touch :start ="touchStart" v -touch :end ="touchEnd" >
2019-07-25 12:57:27 -07:00
< div v-if ="hasIssues" class="node-info-icon node-issues" >
2021-08-29 04:36:17 -07:00
< n8n -tooltip placement = "top" >
2019-07-25 12:57:27 -07:00
< div slot = "content" v-html ="nodeIssues" > < / div >
< font -awesome -icon icon = "exclamation-triangle" / >
2021-08-29 04:36:17 -07:00
< / n 8 n - t o o l t i p >
2019-07-25 09:50:45 -07:00
< / div >
2019-07-25 12:57:27 -07:00
< el -badge v -else : hidden = "workflowDataItems === 0" class = "node-info-icon data-count" :value ="workflowDataItems" > < / e l - b a d g e >
2021-08-21 05:11:32 -07:00
< div v-if ="waiting" class="node-info-icon waiting" >
2021-08-29 04:36:17 -07:00
< n8n -tooltip placement = "top" >
2021-08-21 05:11:32 -07:00
< div slot = "content" v-html ="waiting" > < / div >
< font -awesome -icon icon = "clock" / >
2021-08-29 04:36:17 -07:00
< / n 8 n - t o o l t i p >
2021-08-21 05:11:32 -07:00
< / div >
2021-11-10 10:41:40 -08:00
< div class = "node-executing-info" :title ="$baseText('node.nodeIsExecuting')" >
2019-07-25 12:57:27 -07:00
< font -awesome -icon icon = "sync-alt" spin / >
2019-06-23 03:35:23 -07:00
< / div >
2020-12-23 23:37:13 -08:00
< div class = "node-options no-select-on-click" v-if ="!isReadOnly" >
2021-11-10 10:41:40 -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 >
2019-07-25 12:57:27 -07:00
2021-09-11 01:15:36 -07:00
< NodeIcon class = "node-icon" :nodeType ="nodeType" size = "60" :circle ="true" :shrink ="true" :disabled ="this.data.disabled" / >
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" >
{ { data . name } }
< / div >
< div v-if ="nodeSubtitle !== undefined" class="node-subtitle" :title ="nodeSubtitle" >
{ { nodeSubtitle } }
< / 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 ,
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-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 : {
workflowDataItems ( ) {
2019-06-24 01:28:18 -07:00
const workflowResultDataNode = this . $store . getters . getWorkflowResultDataByNodeName ( this . data . name ) ;
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 ) ;
} ,
nodeClass ( ) {
const classes = [ ] ;
if ( this . data . disabled ) {
classes . push ( 'disabled' ) ;
}
if ( this . isExecuting ) {
classes . push ( 'executing' ) ;
}
if ( this . workflowDataItems !== 0 ) {
classes . push ( 'has-data' ) ;
}
2019-07-25 22:49:00 -07:00
if ( this . hasIssues ) {
classes . push ( 'has-issues' ) ;
}
2020-02-15 17:29:08 -08:00
if ( this . isTouchDevice ) {
classes . push ( 'is-touch-device' ) ;
}
2020-10-24 11:01:13 -07:00
if ( this . isTouchActive ) {
classes . push ( 'touch-active' ) ;
}
2019-06-23 03:35:23 -07:00
return classes ;
} ,
nodeIssues ( ) : string {
if ( this . data . issues === undefined ) {
return '' ;
}
const nodeIssues = NodeHelpers . nodeIssuesToString ( this . data . issues , this . data ) ;
return 'Issues:<br /> - ' + nodeIssues . join ( '<br /> - ' ) ;
} ,
nodeDisabledIcon ( ) : string {
if ( this . data . disabled === false ) {
return 'pause' ;
} else {
return 'play' ;
}
} ,
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 ) {
return 'The node is waiting indefinitely for an incoming webhook call.' ;
}
return ` Node is waiting till ${ waitDate . toLocaleDateString ( ) } ${ waitDate . toLocaleTimeString ( ) } ` ;
}
}
return ;
} ,
2019-06-23 03:35:23 -07:00
workflowRunning ( ) : boolean {
return this . $store . getters . isActionActive ( 'workflowRunning' ) ;
} ,
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
} ,
2019-06-23 03:35:23 -07:00
} ,
2021-08-07 00:35:59 -07:00
mounted ( ) {
this . setSubtitle ( ) ;
} ,
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 >
< style lang = "scss" >
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 {
2021-08-29 04:36:17 -07:00
line - height : 1.5 ;
2019-06-23 03:35:23 -07:00
position : absolute ;
2019-07-25 12:57:27 -07:00
bottom : - 55 px ;
2019-07-25 09:50:45 -07:00
left : - 50 px ;
width : 200 px ;
2019-07-25 12:57:27 -07:00
height : 50 px ;
2019-07-25 09:50:45 -07:00
text - align : center ;
2019-07-25 12:57:27 -07:00
cursor : default ;
. node - name {
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
font - weight : 500 ;
}
. 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 {
2019-07-25 09:50:45 -07:00
width : 100 % ;
height : 100 % ;
2019-07-25 12:57:27 -07:00
background - color : # fff ;
border - radius : 25 px ;
2019-06-23 03:35:23 -07:00
text - align : center ;
2019-07-25 12:57:27 -07:00
z - index : 24 ;
cursor : pointer ;
color : # 444 ;
border : 1 px dashed grey ;
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
& . has - data {
border - style : solid ;
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
& . disabled {
color : # a0a0a0 ;
text - decoration : line - through ;
border : 1 px solid # eee ! important ;
background - color : # eee ;
}
& . executing {
background - color : $ -- color - primary - light ! important ;
border - color : $ -- color - primary ! important ;
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - executing - info {
display : inline - block ;
}
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 ;
top : calc ( 50 % - 30 px ) ;
left : calc ( 50 % - 30 px ) ;
}
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
. node - info - icon {
position : absolute ;
2021-08-29 04:36:17 -07:00
top : - 14 px ;
2019-07-25 12:57:27 -07:00
right : 12 px ;
2021-08-21 05:11:32 -07:00
z - index : 11 ;
2019-06-23 03:35:23 -07:00
2019-07-25 12:57:27 -07:00
& . data - count {
font - weight : 600 ;
top : - 12 px ;
2019-06-23 03:35:23 -07:00
}
2021-08-21 05:11:32 -07:00
& . waiting {
left : 10 px ;
top : - 12 px ;
}
2019-07-25 12:57:27 -07:00
}
2019-07-25 09:50:45 -07:00
2019-07-25 12:57:27 -07:00
. node - issues {
width : 25 px ;
height : 25 px ;
font - size : 20 px ;
color : # ff0000 ;
}
2021-08-21 05:11:32 -07:00
. waiting {
width : 25 px ;
height : 25 px ;
font - size : 20 px ;
color : # 5 e5efa ;
}
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 ;
height : 45 px ;
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
}
}
2020-02-15 17:29:08 -08:00
& . has - data . node - options ,
& . has - issues . node - options {
top : - 35 px ;
}
2019-06-23 03:35:23 -07:00
}
}
< / style >
< style >
. el - badge _ _content {
border - width : 2 px ;
background - color : # 67 c23a ;
}
. jtk - connector {
z - index : 4 ;
}
. jtk - endpoint {
z - index : 5 ;
}
. jtk - overlay {
z - index : 6 ;
}
. jtk - endpoint . dropHover {
border : 2 px solid # ff2244 ;
}
2019-07-25 12:57:27 -07:00
. jtk - drag - selected . node - default {
2019-06-23 03:35:23 -07:00
/* https://www.cssmatic.com/box-shadow */
- webkit - box - shadow : 0 px 0 px 6 px 2 px rgba ( 50 , 75 , 216 , 0.37 ) ;
- moz - box - shadow : 0 px 0 px 6 px 2 px rgba ( 50 , 75 , 216 , 0.37 ) ;
box - shadow : 0 px 0 px 6 px 2 px rgba ( 50 , 75 , 216 , 0.37 ) ;
}
. disabled . node - icon img {
- webkit - filter : contrast ( 40 % ) brightness ( 1.5 ) grayscale ( 100 % ) ;
filter : contrast ( 40 % ) brightness ( 1.5 ) grayscale ( 100 % ) ;
}
< / style >