2013-09-05 16:02:48 +02:00
/ * *
2017-01-11 16:24:33 +01:00
* Copyright JS Foundation and other contributors , http : //js.foundation
2013-09-05 16:02:48 +02:00
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* * /
2013-11-19 09:48:44 +01:00
2013-10-21 00:11:38 +02:00
2019-05-01 23:41:20 +02:00
/ * < d i v > # r e d - u i - w o r k s p a c e - c h a r t
* \ - < svg > "outer"
* \ - < g >
* \ - < g > . red - ui - workspace - chart - event - layer "eventLayer"
* | - < rect > . red - ui - workspace - chart - background
* | - < g > . red - ui - workspace - chart - grid "gridLayer"
2020-03-03 20:04:32 +01:00
* | - < g > "groupLayer"
2020-04-01 15:10:35 +02:00
* | - < g > "groupSelectLayer"
2019-05-01 23:41:20 +02:00
* | - < g > "linkLayer"
* | - < g > "dragGroupLayer"
2020-04-01 15:10:35 +02:00
* | - < g > "nodeLayer"
2019-05-01 23:41:20 +02:00
* /
2014-08-08 01:01:35 +02:00
RED . view = ( function ( ) {
2013-09-05 16:02:48 +02:00
var space _width = 5000 ,
space _height = 5000 ,
lineCurveScale = 0.75 ,
scaleFactor = 1 ,
node _width = 100 ,
node _height = 30 ;
2014-10-09 11:05:45 +02:00
2014-05-15 23:49:07 +02:00
var touchLongPressTimeout = 1000 ,
startTouchDistance = 0 ,
startTouchCenter = [ ] ,
moveTouchCenter = [ ] ,
touchStartTime = 0 ;
2015-03-13 00:38:37 +01:00
var workspaceScrollPositions = { } ;
2016-01-07 15:39:01 +01:00
var gridSize = 20 ;
var snapGrid = false ;
2013-09-05 16:02:48 +02:00
2016-01-14 16:59:45 +01:00
var activeSpliceLink ;
var spliceActive = false ;
var spliceTimer ;
2020-03-03 20:04:32 +01:00
var groupHoverTimer ;
2016-01-14 16:59:45 +01:00
2014-02-25 00:35:11 +01:00
var activeSubflow = null ;
2015-03-12 12:21:05 +01:00
var activeNodes = [ ] ;
var activeLinks = [ ] ;
2016-05-17 10:16:58 +02:00
var activeFlowLinks = [ ] ;
2018-12-18 11:57:53 +01:00
var activeLinkNodes = { } ;
2020-03-03 20:04:32 +01:00
var activeGroup = null ;
var activeHoverGroup = null ;
var activeGroups = [ ] ;
var dirtyGroups = { } ;
2015-07-01 00:42:03 +02:00
2020-03-05 11:43:28 +01:00
var selected _link = null ;
var mousedown _link = null ;
var mousedown _node = null ;
var mousedown _group = null ;
var mousedown _port _type = null ;
var mousedown _port _index = 0 ;
var mouseup _node = null ;
var mouse _offset = [ 0 , 0 ] ;
var mouse _position = null ;
var mouse _mode = 0 ;
var mousedown _group _handle = null ;
var moving _set = [ ] ;
var lasso = null ;
var ghostNode = null ;
var showStatus = false ;
var lastClickNode = null ;
var dblClickPrimed = null ;
var clickTime = 0 ;
var clickElapsed = 0 ;
var scroll _position = [ ] ;
var quickAddActive = false ;
var quickAddLink = null ;
var showAllLinkPorts = - 1 ;
var groupNodeSelectPrimed = false ;
2014-05-13 00:57:14 +02:00
2019-05-23 17:39:06 +02:00
var selectNodesOptions ;
2013-09-05 16:02:48 +02:00
var clipboard = "" ;
2019-05-15 14:54:29 +02:00
// Note: these are the permitted status colour aliases. The actual RGB values
// are set in the CSS - flow.scss/colors.scss
2014-05-08 15:15:54 +02:00
var status _colours = {
2014-05-11 00:33:02 +02:00
"red" : "#c00" ,
"green" : "#5a8" ,
"yellow" : "#F9DF31" ,
"blue" : "#53A3F3" ,
"grey" : "#d3d3d3"
2014-05-08 15:15:54 +02:00
}
2014-05-14 15:22:28 +02:00
2017-01-26 16:38:25 +01:00
var PORT _TYPE _INPUT = 1 ;
var PORT _TYPE _OUTPUT = 0 ;
2019-05-01 23:41:20 +02:00
var chart ;
var outer ;
var eventLayer ;
var gridLayer ;
var linkLayer ;
var dragGroupLayer ;
2020-04-01 15:10:35 +02:00
var groupSelectLayer ;
2019-05-01 23:41:20 +02:00
var nodeLayer ;
2020-03-03 20:04:32 +01:00
var groupLayer ;
2019-05-01 23:41:20 +02:00
var drag _lines ;
2018-06-06 21:51:30 +02:00
2019-05-01 23:41:20 +02:00
function init ( ) {
2015-07-01 00:42:03 +02:00
2019-05-01 23:41:20 +02:00
chart = $ ( "#red-ui-workspace-chart" ) ;
outer = d3 . select ( "#red-ui-workspace-chart" )
. append ( "svg:svg" )
. attr ( "width" , space _width )
. attr ( "height" , space _height )
. attr ( "pointer-events" , "all" )
. style ( "cursor" , "crosshair" )
. on ( "mousedown" , function ( ) {
focusView ( ) ;
} )
. on ( "contextmenu" , function ( ) {
d3 . event . preventDefault ( ) ;
} ) ;
eventLayer = outer
. append ( "svg:g" )
. on ( "dblclick.zoom" , null )
. append ( "svg:g" )
. attr ( 'class' , 'red-ui-workspace-chart-event-layer' )
. on ( "mousemove" , canvasMouseMove )
. on ( "mousedown" , canvasMouseDown )
. on ( "mouseup" , canvasMouseUp )
. on ( "mouseenter" , function ( ) {
if ( lasso ) {
if ( d3 . event . buttons !== 1 ) {
lasso . remove ( ) ;
lasso = null ;
}
} else if ( mouse _mode === RED . state . PANNING && d3 . event . buttons !== 4 ) {
resetMouseVars ( ) ;
2018-06-06 21:51:30 +02:00
}
2019-05-01 23:41:20 +02:00
} )
. on ( "touchend" , function ( ) {
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-05-15 23:56:12 +02:00
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
2014-05-15 23:49:07 +02:00
if ( RED . touch . radialMenu . active ( ) ) {
return ;
}
2019-05-01 23:41:20 +02:00
canvasMouseUp . call ( this ) ;
} )
2020-06-05 16:48:02 +02:00
. on ( "touchcancel" , function ( ) { d3 . event . preventDefault ( ) ; canvasMouseUp . call ( this ) ; } )
2019-05-01 23:41:20 +02:00
. on ( "touchstart" , function ( ) {
2014-08-08 01:01:35 +02:00
var touch0 ;
2019-05-01 23:41:20 +02:00
if ( d3 . event . touches . length > 1 ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
d3 . event . preventDefault ( ) ;
2014-08-08 01:01:35 +02:00
touch0 = d3 . event . touches . item ( 0 ) ;
2014-05-13 00:57:14 +02:00
var touch1 = d3 . event . touches . item ( 1 ) ;
2016-05-17 10:16:58 +02:00
var a = touch0 [ "pageY" ] - touch1 [ "pageY" ] ;
var b = touch0 [ "pageX" ] - touch1 [ "pageX" ] ;
2019-05-01 23:41:20 +02:00
var offset = chart . offset ( ) ;
var scrollPos = [ chart . scrollLeft ( ) , chart . scrollTop ( ) ] ;
startTouchCenter = [
( touch1 [ "pageX" ] + ( b / 2 ) - offset . left + scrollPos [ 0 ] ) / scaleFactor ,
( touch1 [ "pageY" ] + ( a / 2 ) - offset . top + scrollPos [ 1 ] ) / scaleFactor
] ;
moveTouchCenter = [
2016-05-17 10:16:58 +02:00
touch1 [ "pageX" ] + ( b / 2 ) ,
touch1 [ "pageY" ] + ( a / 2 )
2019-05-01 23:41:20 +02:00
]
startTouchDistance = Math . sqrt ( ( a * a ) + ( b * b ) ) ;
} else {
var obj = d3 . select ( document . body ) ;
touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
startTouchDistance = 0 ;
var point = d3 . touches ( this ) [ 0 ] ;
touchStartTime = setTimeout ( function ( ) {
touchStartTime = null ;
showTouchMenu ( obj , pos ) ;
//lasso = eventLayer.append("rect")
// .attr("ox",point[0])
// .attr("oy",point[1])
// .attr("rx",2)
// .attr("ry",2)
// .attr("x",point[0])
// .attr("y",point[1])
// .attr("width",0)
// .attr("height",0)
2019-05-08 14:26:48 +02:00
// .attr("class","nr-ui-view-lasso");
2019-05-01 23:41:20 +02:00
} , touchLongPressTimeout ) ;
2014-05-13 00:57:14 +02:00
}
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2019-05-01 23:41:20 +02:00
} )
. on ( "touchmove" , function ( ) {
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . preventDefault ( ) ;
return ;
}
var touch0 ;
if ( d3 . event . touches . length < 2 ) {
if ( touchStartTime ) {
touch0 = d3 . event . touches . item ( 0 ) ;
var dx = ( touch0 . pageX - startTouchCenter [ 0 ] ) ;
var dy = ( touch0 . pageY - startTouchCenter [ 1 ] ) ;
var d = Math . abs ( dx * dx + dy * dy ) ;
if ( d > 64 ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
}
} else if ( lasso ) {
d3 . event . preventDefault ( ) ;
}
canvasMouseMove . call ( this ) ;
} else {
touch0 = d3 . event . touches . item ( 0 ) ;
var touch1 = d3 . event . touches . item ( 1 ) ;
var a = touch0 [ "pageY" ] - touch1 [ "pageY" ] ;
var b = touch0 [ "pageX" ] - touch1 [ "pageX" ] ;
var offset = chart . offset ( ) ;
var scrollPos = [ chart . scrollLeft ( ) , chart . scrollTop ( ) ] ;
var moveTouchDistance = Math . sqrt ( ( a * a ) + ( b * b ) ) ;
var touchCenter = [
touch1 [ "pageX" ] + ( b / 2 ) ,
touch1 [ "pageY" ] + ( a / 2 )
] ;
2013-09-05 16:02:48 +02:00
2019-05-01 23:41:20 +02:00
if ( ! isNaN ( moveTouchDistance ) ) {
oldScaleFactor = scaleFactor ;
scaleFactor = Math . min ( 2 , Math . max ( 0.3 , scaleFactor + ( Math . floor ( ( ( moveTouchDistance * 100 ) - ( startTouchDistance * 100 ) ) ) / 10000 ) ) ) ;
2013-09-05 16:02:48 +02:00
2019-05-01 23:41:20 +02:00
var deltaTouchCenter = [ // Try to pan whilst zooming - not 100%
startTouchCenter [ 0 ] * ( scaleFactor - oldScaleFactor ) , //-(touchCenter[0]-moveTouchCenter[0]),
startTouchCenter [ 1 ] * ( scaleFactor - oldScaleFactor ) //-(touchCenter[1]-moveTouchCenter[1])
] ;
2017-04-28 21:49:01 +02:00
2019-05-01 23:41:20 +02:00
startTouchDistance = moveTouchDistance ;
moveTouchCenter = touchCenter ;
2016-01-09 01:29:04 +01:00
2019-05-01 23:41:20 +02:00
chart . scrollLeft ( scrollPos [ 0 ] + deltaTouchCenter [ 0 ] ) ;
chart . scrollTop ( scrollPos [ 1 ] + deltaTouchCenter [ 1 ] ) ;
redraw ( ) ;
}
}
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2019-05-01 23:41:20 +02:00
} ) ;
2015-07-01 00:42:03 +02:00
2019-05-01 23:41:20 +02:00
// Workspace Background
eventLayer . append ( "svg:rect" )
. attr ( "class" , "red-ui-workspace-chart-background" )
. attr ( "width" , space _width )
. attr ( "height" , space _height ) ;
2015-07-01 00:42:03 +02:00
2019-05-01 23:41:20 +02:00
gridLayer = eventLayer . append ( "g" ) . attr ( "class" , "red-ui-workspace-chart-grid" ) ;
updateGrid ( ) ;
2015-03-13 00:38:37 +01:00
2020-03-03 20:04:32 +01:00
groupLayer = eventLayer . append ( "g" ) ;
2020-04-01 15:10:35 +02:00
groupSelectLayer = eventLayer . append ( "g" ) ;
2019-05-01 23:41:20 +02:00
linkLayer = eventLayer . append ( "g" ) ;
dragGroupLayer = eventLayer . append ( "g" ) ;
nodeLayer = eventLayer . append ( "g" ) ;
2020-04-01 15:10:35 +02:00
2019-05-01 23:41:20 +02:00
drag _lines = [ ] ;
2016-12-04 23:59:43 +01:00
2015-07-10 20:49:31 +02:00
RED . events . on ( "workspace:change" , function ( event ) {
2015-03-13 00:38:37 +01:00
if ( event . old !== 0 ) {
workspaceScrollPositions [ event . old ] = {
2014-02-25 00:42:24 +01:00
left : chart . scrollLeft ( ) ,
top : chart . scrollTop ( )
} ;
}
var scrollStartLeft = chart . scrollLeft ( ) ;
var scrollStartTop = chart . scrollTop ( ) ;
2015-07-01 00:42:03 +02:00
2015-03-13 00:38:37 +01:00
activeSubflow = RED . nodes . subflow ( event . workspace ) ;
2015-07-01 00:42:03 +02:00
2015-09-27 21:18:21 +02:00
RED . menu . setDisabled ( "menu-item-workspace-edit" , activeSubflow ) ;
RED . menu . setDisabled ( "menu-item-workspace-delete" , RED . workspaces . count ( ) == 1 || activeSubflow ) ;
2015-07-01 00:42:03 +02:00
2015-03-13 00:38:37 +01:00
if ( workspaceScrollPositions [ event . workspace ] ) {
chart . scrollLeft ( workspaceScrollPositions [ event . workspace ] . left ) ;
chart . scrollTop ( workspaceScrollPositions [ event . workspace ] . top ) ;
2014-02-25 00:42:24 +01:00
} else {
chart . scrollLeft ( 0 ) ;
chart . scrollTop ( 0 ) ;
}
var scrollDeltaLeft = chart . scrollLeft ( ) - scrollStartLeft ;
var scrollDeltaTop = chart . scrollTop ( ) - scrollStartTop ;
if ( mouse _position != null ) {
mouse _position [ 0 ] += scrollDeltaLeft ;
mouse _position [ 1 ] += scrollDeltaTop ;
}
2018-10-30 23:18:16 +01:00
if ( RED . workspaces . selection ( ) . length === 0 ) {
clearSelection ( ) ;
}
2014-02-25 00:42:24 +01:00
RED . nodes . eachNode ( function ( n ) {
2015-03-12 12:21:05 +01:00
n . dirty = true ;
2019-09-30 10:56:51 +02:00
n . dirtyStatus = true ;
2014-02-25 00:42:24 +01:00
} ) ;
2015-03-12 01:08:47 +01:00
updateSelection ( ) ;
2015-03-12 12:21:05 +01:00
updateActiveNodes ( ) ;
2014-02-25 00:42:24 +01:00
redraw ( ) ;
2014-08-20 22:58:54 +02:00
} ) ;
2015-07-01 00:42:03 +02:00
2019-03-26 23:22:13 +01:00
RED . statusBar . add ( {
id : "view-zoom-controls" ,
align : "right" ,
2019-04-29 21:53:04 +02:00
element : $ ( '<span class="button-group">' +
'<button class="red-ui-footer-button" id="red-ui-view-zoom-out"><i class="fa fa-minus"></i></button>' +
'<button class="red-ui-footer-button" id="red-ui-view-zoom-zero"><i class="fa fa-circle-o"></i></button>' +
'<button class="red-ui-footer-button" id="red-ui-view-zoom-in"><i class="fa fa-plus"></i></button>' +
2019-03-26 23:22:13 +01:00
'</span>' )
} )
2019-05-01 23:41:20 +02:00
$ ( "#red-ui-view-zoom-out" ) . on ( "click" , zoomOut ) ;
2019-04-29 21:53:04 +02:00
RED . popover . tooltip ( $ ( "#red-ui-view-zoom-out" ) , RED . _ ( 'actions.zoom-out' ) , 'core:zoom-out' ) ;
2019-05-01 23:41:20 +02:00
$ ( "#red-ui-view-zoom-zero" ) . on ( "click" , zoomZero ) ;
2019-04-29 21:53:04 +02:00
RED . popover . tooltip ( $ ( "#red-ui-view-zoom-zero" ) , RED . _ ( 'actions.zoom-reset' ) , 'core:zoom-reset' ) ;
2019-05-01 23:41:20 +02:00
$ ( "#red-ui-view-zoom-in" ) . on ( "click" , zoomIn ) ;
2019-04-29 21:53:04 +02:00
RED . popover . tooltip ( $ ( "#red-ui-view-zoom-in" ) , RED . _ ( 'actions.zoom-in' ) , 'core:zoom-in' ) ;
2019-05-01 23:41:20 +02:00
chart . on ( "DOMMouseScroll mousewheel" , function ( evt ) {
2015-03-14 23:53:31 +01:00
if ( evt . altKey ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
var move = - ( evt . originalEvent . detail ) || evt . originalEvent . wheelDelta ;
if ( move <= 0 ) { zoomOut ( ) ; }
else { zoomIn ( ) ; }
}
} ) ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
// Handle nodes dragged from the palette
2019-05-01 23:41:20 +02:00
chart . droppable ( {
2019-04-29 23:38:14 +02:00
accept : ".red-ui-palette-node" ,
2015-03-14 23:53:31 +01:00
drop : function ( event , ui ) {
d3 . event = event ;
2019-05-24 23:13:21 +02:00
var selected _tool = $ ( ui . draggable [ 0 ] ) . attr ( "data-palette-type" ) ;
2016-11-07 22:25:09 +01:00
var result = addNode ( selected _tool ) ;
if ( ! result ) {
return ;
2015-07-30 12:03:37 +02:00
}
2016-11-07 22:25:09 +01:00
var historyEvent = result . historyEvent ;
var nn = result . node ;
2015-07-30 12:03:37 +02:00
2018-10-22 16:38:43 +02:00
var showLabel = RED . utils . getMessageProperty ( RED . settings . get ( 'editor' ) , "view.view-node-show-label" ) ;
if ( showLabel !== undefined && ! /^link (in|out)$/ . test ( nn . _def . type ) && ! nn . _def . defaults . hasOwnProperty ( "l" ) ) {
nn . l = showLabel ;
}
2016-01-08 15:42:05 +01:00
var helperOffset = d3 . touches ( ui . helper . get ( 0 ) ) [ 0 ] || d3 . mouse ( ui . helper . get ( 0 ) ) ;
var mousePos = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
2016-01-08 23:34:10 +01:00
2016-01-08 15:42:05 +01:00
mousePos [ 1 ] += this . scrollTop + ( ( nn . h / 2 ) - helperOffset [ 1 ] ) ;
mousePos [ 0 ] += this . scrollLeft + ( ( nn . w / 2 ) - helperOffset [ 0 ] ) ;
mousePos [ 1 ] /= scaleFactor ;
mousePos [ 0 ] /= scaleFactor ;
if ( snapGrid ) {
mousePos [ 0 ] = gridSize * ( Math . ceil ( mousePos [ 0 ] / gridSize ) ) ;
mousePos [ 1 ] = gridSize * ( Math . ceil ( mousePos [ 1 ] / gridSize ) ) ;
}
nn . x = mousePos [ 0 ] ;
nn . y = mousePos [ 1 ] ;
2016-05-17 10:16:58 +02:00
var spliceLink = $ ( ui . helper ) . data ( "splice" ) ;
2016-01-08 23:34:10 +01:00
if ( spliceLink ) {
2019-08-13 11:31:21 +02:00
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
2016-01-08 23:34:10 +01:00
RED . nodes . removeLink ( spliceLink ) ;
var link1 = {
source : spliceLink . source ,
sourcePort : spliceLink . sourcePort ,
target : nn
} ;
var link2 = {
source : nn ,
sourcePort : 0 ,
target : spliceLink . target
} ;
RED . nodes . addLink ( link1 ) ;
RED . nodes . addLink ( link2 ) ;
historyEvent . links = [ link1 , link2 ] ;
historyEvent . removedLinks = [ spliceLink ] ;
}
2016-01-08 15:42:05 +01:00
2020-04-20 22:30:03 +02:00
RED . nodes . add ( nn ) ;
2020-03-14 00:01:19 +01:00
var group = $ ( ui . helper ) . data ( "group" ) ;
if ( group ) {
RED . group . addToGroup ( group , nn ) ;
historyEvent = {
t : 'multi' ,
events : [ historyEvent ] ,
}
historyEvent . events . push ( {
t : "addToGroup" ,
group : group ,
nodes : nn
} )
}
2015-07-30 12:03:37 +02:00
RED . history . push ( historyEvent ) ;
2015-03-14 23:53:31 +01:00
RED . editor . validateNode ( nn ) ;
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2015-03-14 23:53:31 +01:00
// auto select dropped node - so info shows (if visible)
2020-03-14 00:01:19 +01:00
exitActiveGroup ( ) ;
2015-03-14 23:53:31 +01:00
clearSelection ( ) ;
nn . selected = true ;
moving _set . push ( { n : nn } ) ;
2020-03-14 00:01:19 +01:00
if ( group ) {
selectGroup ( group , false ) ;
2020-03-26 16:27:34 +01:00
enterActiveGroup ( group ) ;
2020-03-14 00:01:19 +01:00
activeGroup = group ;
}
2015-03-14 23:53:31 +01:00
updateActiveNodes ( ) ;
updateSelection ( ) ;
redraw ( ) ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
if ( nn . _def . autoedit ) {
RED . editor . edit ( nn ) ;
}
}
} ) ;
2019-05-01 23:41:20 +02:00
chart . on ( "focus" , function ( ) {
$ ( "#red-ui-workspace-tabs" ) . addClass ( "red-ui-workspace-focussed" ) ;
2017-01-24 17:14:03 +01:00
} ) ;
2019-05-01 23:41:20 +02:00
chart . on ( "blur" , function ( ) {
$ ( "#red-ui-workspace-tabs" ) . removeClass ( "red-ui-workspace-focussed" ) ;
2017-01-24 17:14:03 +01:00
} ) ;
2015-07-01 00:42:03 +02:00
2017-01-11 12:35:48 +01:00
RED . actions . add ( "core:copy-selection-to-internal-clipboard" , copySelection ) ;
RED . actions . add ( "core:cut-selection-to-internal-clipboard" , function ( ) { copySelection ( ) ; deleteSelection ( ) ; } ) ;
RED . actions . add ( "core:paste-from-internal-clipboard" , function ( ) { importNodes ( clipboard ) ; } ) ;
RED . actions . add ( "core:delete-selection" , deleteSelection ) ;
RED . actions . add ( "core:edit-selected-node" , editSelection ) ;
2016-12-04 23:59:43 +01:00
RED . actions . add ( "core:undo" , RED . history . pop ) ;
2019-06-22 09:05:50 +02:00
RED . actions . add ( "core:redo" , RED . history . redo ) ;
2017-01-11 12:35:48 +01:00
RED . actions . add ( "core:select-all-nodes" , selectAll ) ;
2016-12-04 23:59:43 +01:00
RED . actions . add ( "core:zoom-in" , zoomIn ) ;
RED . actions . add ( "core:zoom-out" , zoomOut ) ;
RED . actions . add ( "core:zoom-reset" , zoomZero ) ;
2019-06-17 23:46:34 +02:00
RED . actions . add ( "core:enable-selected-nodes" , function ( ) { setSelectedNodeState ( false ) } ) ;
RED . actions . add ( "core:disable-selected-nodes" , function ( ) { setSelectedNodeState ( true ) } ) ;
2016-12-04 23:59:43 +01:00
RED . actions . add ( "core:toggle-show-grid" , function ( state ) {
if ( state === undefined ) {
2017-04-28 21:49:01 +02:00
RED . userSettings . toggle ( "view-show-grid" ) ;
2016-12-04 23:59:43 +01:00
} else {
toggleShowGrid ( state ) ;
}
} ) ;
RED . actions . add ( "core:toggle-snap-grid" , function ( state ) {
if ( state === undefined ) {
2017-04-28 21:49:01 +02:00
RED . userSettings . toggle ( "view-snap-grid" ) ;
2016-12-04 23:59:43 +01:00
} else {
toggleSnapGrid ( state ) ;
}
} ) ;
RED . actions . add ( "core:toggle-status" , function ( state ) {
if ( state === undefined ) {
2017-04-28 21:49:01 +02:00
RED . userSettings . toggle ( "view-node-status" ) ;
2016-12-04 23:59:43 +01:00
} else {
toggleStatus ( state ) ;
}
} ) ;
2019-05-01 23:41:20 +02:00
RED . view . navigator . init ( ) ;
2019-01-18 22:19:03 +01:00
RED . view . tools . init ( ) ;
2013-10-28 21:28:44 +01:00
}
2013-11-19 09:48:44 +01:00
2019-05-01 23:41:20 +02:00
function updateGrid ( ) {
var gridTicks = [ ] ;
for ( var i = 0 ; i < space _width ; i += + gridSize ) {
gridTicks . push ( i ) ;
}
2019-05-15 14:54:29 +02:00
gridLayer . selectAll ( "line.red-ui-workspace-chart-grid-h" ) . remove ( ) ;
gridLayer . selectAll ( "line.red-ui-workspace-chart-grid-h" ) . data ( gridTicks ) . enter ( )
2019-05-01 23:41:20 +02:00
. append ( "line" )
. attr (
{
2019-05-15 14:54:29 +02:00
"class" : "red-ui-workspace-chart-grid-h" ,
2019-05-01 23:41:20 +02:00
"x1" : 0 ,
"x2" : space _width ,
"y1" : function ( d ) { return d ; } ,
"y2" : function ( d ) { return d ; }
} ) ;
2019-05-15 14:54:29 +02:00
gridLayer . selectAll ( "line.red-ui-workspace-chart-grid-v" ) . remove ( ) ;
gridLayer . selectAll ( "line.red-ui-workspace-chart-grid-v" ) . data ( gridTicks ) . enter ( )
2019-05-01 23:41:20 +02:00
. append ( "line" )
. attr (
{
2019-05-15 14:54:29 +02:00
"class" : "red-ui-workspace-chart-grid-v" ,
2019-05-01 23:41:20 +02:00
"y1" : 0 ,
"y2" : space _width ,
"x1" : function ( d ) { return d ; } ,
"x2" : function ( d ) { return d ; }
} ) ;
}
function showDragLines ( nodes ) {
showAllLinkPorts = - 1 ;
for ( var i = 0 ; i < nodes . length ; i ++ ) {
var node = nodes [ i ] ;
2019-05-15 14:54:29 +02:00
node . el = dragGroupLayer . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-drag-line" ) ;
2019-05-01 23:41:20 +02:00
if ( ( node . node . type === "link out" && node . portType === PORT _TYPE _OUTPUT ) ||
( node . node . type === "link in" && node . portType === PORT _TYPE _INPUT ) ) {
2019-05-15 14:54:29 +02:00
node . el . attr ( "class" , "red-ui-flow-link-link red-ui-flow-drag-line" ) ;
2019-05-01 23:41:20 +02:00
node . virtualLink = true ;
showAllLinkPorts = ( node . portType === PORT _TYPE _OUTPUT ) ? PORT _TYPE _INPUT : PORT _TYPE _OUTPUT ;
}
drag _lines . push ( node ) ;
}
if ( showAllLinkPorts !== - 1 ) {
activeNodes . forEach ( function ( n ) {
if ( n . type === "link in" || n . type === "link out" ) {
n . dirty = true ;
}
} )
}
}
function hideDragLines ( ) {
if ( showAllLinkPorts !== - 1 ) {
activeNodes . forEach ( function ( n ) {
if ( n . type === "link in" || n . type === "link out" ) {
n . dirty = true ;
}
} )
}
showAllLinkPorts = - 1 ;
while ( drag _lines . length ) {
var line = drag _lines . pop ( ) ;
if ( line . el ) {
line . el . remove ( ) ;
}
}
}
function updateActiveNodes ( ) {
var activeWorkspace = RED . workspaces . active ( ) ;
activeNodes = RED . nodes . filterNodes ( { z : activeWorkspace } ) ;
activeLinks = RED . nodes . filterLinks ( {
source : { z : activeWorkspace } ,
target : { z : activeWorkspace }
} ) ;
2020-03-03 20:04:32 +01:00
activeGroups = RED . nodes . groups ( activeWorkspace ) || [ ] ;
2020-03-09 16:10:54 +01:00
activeGroups . forEach ( function ( g ) {
if ( g . g ) {
2020-03-26 23:51:06 +01:00
g . _root = g . g ;
2020-03-09 16:10:54 +01:00
g . _depth = 1 ;
} else {
2020-03-26 23:51:06 +01:00
g . _root = g . id ;
2020-03-09 16:10:54 +01:00
g . _depth = 0 ;
}
} ) ;
var changed = false ;
do {
changed = false ;
activeGroups . forEach ( function ( g ) {
if ( g . g ) {
var parentGroup = RED . nodes . group ( g . g ) ;
if ( parentGroup ) {
var parentDepth = parentGroup . _depth ;
if ( g . _depth !== parentDepth + 1 ) {
g . _depth = parentDepth + 1 ;
changed = true ;
}
2020-03-26 23:51:06 +01:00
if ( g . _root !== parentGroup . _root ) {
g . _root = parentGroup . _root ;
changed = true ;
}
2020-03-09 16:10:54 +01:00
}
}
} ) ;
} while ( changed )
activeGroups . sort ( function ( a , b ) {
2020-03-26 23:51:06 +01:00
if ( a . _root === b . _root ) {
return a . _depth - b . _depth ;
} else {
return a . _root . localeCompare ( b . _root ) ;
}
2020-03-09 16:10:54 +01:00
} ) ;
2020-03-26 23:51:06 +01:00
var group = groupLayer . selectAll ( ".red-ui-flow-group" ) . data ( activeGroups , function ( d ) { return d . id } ) ;
group . sort ( function ( a , b ) {
if ( a . _root === b . _root ) {
return a . _depth - b . _depth ;
} else {
return a . _root . localeCompare ( b . _root ) ;
}
} )
2019-05-01 23:41:20 +02:00
}
2018-06-25 14:18:00 +02:00
function generateLinkPath ( origX , origY , destX , destY , sc ) {
var dy = destY - origY ;
var dx = destX - origX ;
var delta = Math . sqrt ( dy * dy + dx * dx ) ;
var scale = lineCurveScale ;
var scaleY = 0 ;
if ( dx * sc > 0 ) {
if ( delta < node _width ) {
scale = 0.75 - 0.75 * ( ( node _width - delta ) / node _width ) ;
// scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
// if (Math.abs(dy) < 3*node_height) {
// scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
// }
}
} else {
scale = 0.4 - 0.2 * ( Math . max ( 0 , ( node _width - Math . min ( Math . abs ( dx ) , Math . abs ( dy ) ) ) / node _width ) ) ;
}
if ( dx * sc > 0 ) {
return "M " + origX + " " + origY +
" C " + ( origX + sc * ( node _width * scale ) ) + " " + ( origY + scaleY * node _height ) + " " +
( destX - sc * ( scale ) * node _width ) + " " + ( destY - scaleY * node _height ) + " " +
destX + " " + destY
} else {
var midX = Math . floor ( destX - dx / 2 ) ;
var midY = Math . floor ( destY - dy / 2 ) ;
//
2018-07-02 16:32:29 +02:00
if ( dy === 0 ) {
2018-06-25 14:18:00 +02:00
midY = destY + node _height ;
}
var cp _height = node _height / 2 ;
var y1 = ( destY + midY ) / 2
var topX = origX + sc * node _width * scale ;
var topY = dy > 0 ? Math . min ( y1 - dy / 2 , origY + cp _height ) : Math . max ( y1 - dy / 2 , origY - cp _height ) ;
var bottomX = destX - sc * node _width * scale ;
var bottomY = dy > 0 ? Math . max ( y1 , destY - cp _height ) : Math . min ( y1 , destY + cp _height ) ;
var x1 = ( origX + topX ) / 2 ;
var scy = dy > 0 ? 1 : - 1 ;
var cp = [
// Orig -> Top
[ x1 , origY ] ,
[ topX , dy > 0 ? Math . max ( origY , topY - cp _height ) : Math . min ( origY , topY + cp _height ) ] ,
// Top -> Mid
// [Mirror previous cp]
[ x1 , dy > 0 ? Math . min ( midY , topY + cp _height ) : Math . max ( midY , topY - cp _height ) ] ,
// Mid -> Bottom
// [Mirror previous cp]
[ bottomX , dy > 0 ? Math . max ( midY , bottomY - cp _height ) : Math . min ( midY , bottomY + cp _height ) ] ,
// Bottom -> Dest
// [Mirror previous cp]
[ ( destX + bottomX ) / 2 , destY ]
] ;
if ( cp [ 2 ] [ 1 ] === topY + scy * cp _height ) {
if ( Math . abs ( dy ) < cp _height * 10 ) {
cp [ 1 ] [ 1 ] = topY - scy * cp _height / 2 ;
cp [ 3 ] [ 1 ] = bottomY - scy * cp _height / 2 ;
}
cp [ 2 ] [ 0 ] = topX ;
}
return "M " + origX + " " + origY +
" C " +
cp [ 0 ] [ 0 ] + " " + cp [ 0 ] [ 1 ] + " " +
cp [ 1 ] [ 0 ] + " " + cp [ 1 ] [ 1 ] + " " +
topX + " " + topY +
" S " +
cp [ 2 ] [ 0 ] + " " + cp [ 2 ] [ 1 ] + " " +
midX + " " + midY +
" S " +
cp [ 3 ] [ 0 ] + " " + cp [ 3 ] [ 1 ] + " " +
bottomX + " " + bottomY +
" S " +
cp [ 4 ] [ 0 ] + " " + cp [ 4 ] [ 1 ] + " " +
destX + " " + destY
}
}
2016-11-07 22:25:09 +01:00
function addNode ( type , x , y ) {
var m = /^subflow:(.+)$/ . exec ( type ) ;
if ( activeSubflow && m ) {
var subflowId = m [ 1 ] ;
if ( subflowId === activeSubflow . id ) {
RED . notify ( RED . _ ( "notification.error" , { message : RED . _ ( "notification.errors.cannotAddSubflowToItself" ) } ) , "error" ) ;
return ;
}
if ( RED . nodes . subflowContains ( m [ 1 ] , activeSubflow . id ) ) {
RED . notify ( RED . _ ( "notification.error" , { message : RED . _ ( "notification.errors.cannotAddCircularReference" ) } ) , "error" ) ;
return ;
}
}
var nn = { id : RED . nodes . id ( ) , z : RED . workspaces . active ( ) } ;
nn . type = type ;
nn . _def = RED . nodes . getType ( nn . type ) ;
if ( ! m ) {
nn . inputs = nn . _def . inputs || 0 ;
nn . outputs = nn . _def . outputs ;
for ( var d in nn . _def . defaults ) {
if ( nn . _def . defaults . hasOwnProperty ( d ) ) {
if ( nn . _def . defaults [ d ] . value !== undefined ) {
nn [ d ] = JSON . parse ( JSON . stringify ( nn . _def . defaults [ d ] . value ) ) ;
}
}
}
if ( nn . _def . onadd ) {
try {
nn . _def . onadd . call ( nn ) ;
} catch ( err ) {
2017-07-05 00:40:37 +02:00
console . log ( "Definition error: " + nn . type + ".onadd:" , err ) ;
2016-11-07 22:25:09 +01:00
}
}
} else {
var subflow = RED . nodes . subflow ( m [ 1 ] ) ;
2018-03-20 21:39:46 +01:00
nn . name = "" ;
2016-11-07 22:25:09 +01:00
nn . inputs = subflow . in . length ;
nn . outputs = subflow . out . length ;
}
nn . changed = true ;
2017-04-24 00:20:36 +02:00
nn . moved = true ;
2016-11-07 22:25:09 +01:00
nn . w = node _width ;
nn . h = Math . max ( node _height , ( nn . outputs || 0 ) * 15 ) ;
2019-05-23 17:39:06 +02:00
nn . resize = true ;
2016-11-07 22:25:09 +01:00
var historyEvent = {
t : "add" ,
nodes : [ nn . id ] ,
dirty : RED . nodes . dirty ( )
}
if ( activeSubflow ) {
var subflowRefresh = RED . subflow . refresh ( true ) ;
if ( subflowRefresh ) {
historyEvent . subflow = {
id : activeSubflow . id ,
changed : activeSubflow . changed ,
instances : subflowRefresh . instances
}
}
}
return {
node : nn ,
historyEvent : historyEvent
}
}
2013-09-05 16:02:48 +02:00
function canvasMouseDown ( ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "canvasMouseDown" , mouse _mode ) ; }
2016-11-09 14:17:26 +01:00
var point ;
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2016-11-09 14:17:26 +01:00
2018-06-06 21:51:30 +02:00
if ( d3 . event . button === 1 ) {
// Middle Click pan
mouse _mode = RED . state . PANNING ;
mouse _position = [ d3 . event . pageX , d3 . event . pageY ]
scroll _position = [ chart . scrollLeft ( ) , chart . scrollTop ( ) ] ;
return ;
}
2020-03-03 20:04:32 +01:00
if ( ! mousedown _node && ! mousedown _link && ! mousedown _group ) {
2013-09-05 16:02:48 +02:00
selected _link = null ;
updateSelection ( ) ;
}
2014-08-08 01:01:35 +02:00
if ( mouse _mode === 0 ) {
2013-09-05 16:02:48 +02:00
if ( lasso ) {
lasso . remove ( ) ;
lasso = null ;
}
2016-11-09 14:17:26 +01:00
}
if ( mouse _mode === 0 || mouse _mode === RED . state . QUICK _JOINING ) {
2016-11-07 22:25:09 +01:00
if ( d3 . event . metaKey || d3 . event . ctrlKey ) {
2019-08-13 11:31:21 +02:00
d3 . event . stopPropagation ( ) ;
2020-03-03 20:04:32 +01:00
clearSelection ( ) ;
point = d3 . mouse ( this ) ;
var clickedGroup = getGroupAt ( point [ 0 ] , point [ 1 ] ) ;
if ( drag _lines . length > 0 ) {
clickedGroup = clickedGroup || RED . nodes . group ( drag _lines [ 0 ] . node . g )
}
showQuickAddDialog ( point , null , clickedGroup ) ;
2019-08-13 11:31:21 +02:00
}
}
if ( mouse _mode === 0 && ! ( d3 . event . metaKey || d3 . event . ctrlKey ) ) {
if ( ! touchStartTime ) {
2016-11-07 22:25:09 +01:00
point = d3 . mouse ( this ) ;
2019-08-13 11:31:21 +02:00
lasso = eventLayer . append ( "rect" )
. attr ( "ox" , point [ 0 ] )
. attr ( "oy" , point [ 1 ] )
. attr ( "rx" , 1 )
. attr ( "ry" , 1 )
. attr ( "x" , point [ 0 ] )
. attr ( "y" , point [ 1 ] )
. attr ( "width" , 0 )
. attr ( "height" , 0 )
. attr ( "class" , "nr-ui-view-lasso" ) ;
d3 . event . preventDefault ( ) ;
}
}
}
2019-01-23 17:27:13 +01:00
2020-06-05 16:48:02 +02:00
function showQuickAddDialog ( point , spliceLink , targetGroup , touchTrigger ) {
2020-03-03 20:04:32 +01:00
if ( targetGroup && ! targetGroup . active ) {
selectGroup ( targetGroup , false ) ;
2020-03-26 16:27:34 +01:00
enterActiveGroup ( targetGroup ) ;
2020-03-03 20:04:32 +01:00
RED . view . redraw ( ) ;
}
2019-08-13 11:31:21 +02:00
var ox = point [ 0 ] ;
var oy = point [ 1 ] ;
2019-01-23 17:27:13 +01:00
2019-08-13 11:31:21 +02:00
if ( RED . settings . get ( "editor" ) . view [ 'view-snap-grid' ] ) {
// eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red')
point [ 0 ] = Math . round ( point [ 0 ] / gridSize ) * gridSize ;
point [ 1 ] = Math . round ( point [ 1 ] / gridSize ) * gridSize ;
// eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue')
}
2016-11-09 14:17:26 +01:00
2019-08-13 11:31:21 +02:00
var mainPos = $ ( "#red-ui-main-container" ) . position ( ) ;
if ( mouse _mode !== RED . state . QUICK _JOINING ) {
mouse _mode = RED . state . QUICK _JOINING ;
$ ( window ) . on ( 'keyup' , disableQuickJoinEventHandler ) ;
}
quickAddActive = true ;
if ( ghostNode ) {
ghostNode . remove ( ) ;
}
ghostNode = eventLayer . append ( "g" ) . attr ( 'transform' , 'translate(' + ( point [ 0 ] - node _width / 2 ) + ',' + ( point [ 1 ] - node _height / 2 ) + ')' ) ;
ghostNode . append ( "rect" )
. attr ( "class" , "red-ui-flow-node-placeholder" )
. attr ( "rx" , 5 )
. attr ( "ry" , 5 )
. attr ( "width" , node _width )
. attr ( "height" , node _height )
. attr ( "fill" , "none" )
// var ghostLink = ghostNode.append("svg:path")
// .attr("class","red-ui-flow-link-link")
// .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2))
// .attr("opacity",0);
2020-01-22 12:53:41 +01:00
var filter ;
2019-08-13 11:31:21 +02:00
if ( drag _lines . length > 0 ) {
if ( drag _lines [ 0 ] . virtualLink ) {
filter = { type : drag _lines [ 0 ] . node . type === 'link in' ? 'link out' : 'link in' }
} else if ( drag _lines [ 0 ] . portType === PORT _TYPE _OUTPUT ) {
filter = { input : true }
} else {
filter = { output : true }
}
quickAddLink = {
node : drag _lines [ 0 ] . node ,
port : drag _lines [ 0 ] . port ,
portType : drag _lines [ 0 ] . portType ,
}
if ( drag _lines [ 0 ] . virtualLink ) {
quickAddLink . virtualLink = true ;
}
hideDragLines ( ) ;
2019-09-10 12:38:35 +02:00
}
if ( spliceLink ) {
2019-08-13 11:31:21 +02:00
filter = { input : true , output : true }
}
2019-09-10 12:38:35 +02:00
2019-08-13 11:31:21 +02:00
var rebuildQuickAddLink = function ( ) {
if ( ! quickAddLink ) {
return ;
}
if ( ! quickAddLink . el ) {
quickAddLink . el = dragGroupLayer . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-drag-line" ) ;
}
var numOutputs = ( quickAddLink . portType === PORT _TYPE _OUTPUT ) ? ( quickAddLink . node . outputs || 1 ) : 1 ;
var sourcePort = quickAddLink . port ;
var portY = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
var sc = ( quickAddLink . portType === PORT _TYPE _OUTPUT ) ? 1 : - 1 ;
quickAddLink . el . attr ( "d" , generateLinkPath ( quickAddLink . node . x + sc * quickAddLink . node . w / 2 , quickAddLink . node . y + portY , point [ 0 ] - sc * node _width / 2 , point [ 1 ] , sc ) ) ;
}
if ( quickAddLink ) {
rebuildQuickAddLink ( ) ;
}
2016-11-09 14:17:26 +01:00
2019-01-23 17:27:13 +01:00
2019-08-13 11:31:21 +02:00
var lastAddedX ;
var lastAddedWidth ;
RED . typeSearch . show ( {
x : d3 . event . clientX - mainPos . left - node _width / 2 - ( ox - point [ 0 ] ) ,
y : d3 . event . clientY - mainPos . top + node _height / 2 + 5 - ( oy - point [ 1 ] ) ,
2020-06-05 16:48:02 +02:00
disableFocus : touchTrigger ,
2019-08-13 11:31:21 +02:00
filter : filter ,
move : function ( dx , dy ) {
2019-01-23 17:27:13 +01:00
if ( ghostNode ) {
2019-08-13 11:31:21 +02:00
var pos = d3 . transform ( ghostNode . attr ( "transform" ) ) . translate ;
ghostNode . attr ( "transform" , "translate(" + ( pos [ 0 ] + dx ) + "," + ( pos [ 1 ] + dy ) + ")" )
point [ 0 ] += dx ;
point [ 1 ] += dy ;
rebuildQuickAddLink ( ) ;
2019-01-23 17:27:13 +01:00
}
2019-08-13 11:31:21 +02:00
} ,
cancel : function ( ) {
if ( quickAddLink ) {
if ( quickAddLink . el ) {
quickAddLink . el . remove ( ) ;
2019-01-23 17:27:13 +01:00
}
2019-08-13 11:31:21 +02:00
quickAddLink = null ;
2018-12-18 11:57:53 +01:00
}
2019-08-13 11:31:21 +02:00
quickAddActive = false ;
if ( ghostNode ) {
ghostNode . remove ( ) ;
2019-01-23 17:27:13 +01:00
}
2019-08-13 11:31:21 +02:00
resetMouseVars ( ) ;
updateSelection ( ) ;
hideDragLines ( ) ;
redraw ( ) ;
} ,
add : function ( type , keepAdding ) {
2020-06-05 16:48:02 +02:00
if ( touchTrigger ) {
keepAdding = false ;
resetMouseVars ( ) ;
}
2019-08-13 11:31:21 +02:00
var result = addNode ( type ) ;
if ( ! result ) {
return ;
}
if ( keepAdding ) {
mouse _mode = RED . state . QUICK _JOINING ;
2019-01-23 17:27:13 +01:00
}
2019-08-13 11:31:21 +02:00
var nn = result . node ;
var historyEvent = result . historyEvent ;
nn . x = point [ 0 ] ;
nn . y = point [ 1 ] ;
var showLabel = RED . utils . getMessageProperty ( RED . settings . get ( 'editor' ) , "view.view-node-show-label" ) ;
if ( showLabel !== undefined && ! /^link (in|out)$/ . test ( nn . _def . type ) && ! nn . _def . defaults . hasOwnProperty ( "l" ) ) {
nn . l = showLabel ;
}
2019-08-14 10:50:34 +02:00
if ( quickAddLink ) {
var drag _line = quickAddLink ;
var src = null , dst , src _port ;
if ( drag _line . portType === PORT _TYPE _OUTPUT && ( nn . inputs > 0 || drag _line . virtualLink ) ) {
src = drag _line . node ;
src _port = drag _line . port ;
dst = nn ;
} else if ( drag _line . portType === PORT _TYPE _INPUT && ( nn . outputs > 0 || drag _line . virtualLink ) ) {
src = nn ;
dst = drag _line . node ;
src _port = 0 ;
}
2019-01-23 17:27:13 +01:00
2019-08-14 10:50:34 +02:00
if ( src !== null ) {
// Joining link nodes via virual wires. Need to update
// the src and dst links property
if ( drag _line . virtualLink ) {
historyEvent = {
t : 'multi' ,
events : [ historyEvent ]
2019-01-23 17:27:13 +01:00
}
2019-08-14 10:50:34 +02:00
var oldSrcLinks = $ . extend ( true , { } , { v : src . links } ) . v
var oldDstLinks = $ . extend ( true , { } , { v : dst . links } ) . v
src . links . push ( dst . id ) ;
dst . links . push ( src . id ) ;
src . dirty = true ;
dst . dirty = true ;
historyEvent . events . push ( {
t : 'edit' ,
node : src ,
dirty : RED . nodes . dirty ( ) ,
changed : src . changed ,
changes : {
links : oldSrcLinks
2016-11-09 14:17:26 +01:00
}
2019-08-14 10:50:34 +02:00
} ) ;
historyEvent . events . push ( {
t : 'edit' ,
node : dst ,
dirty : RED . nodes . dirty ( ) ,
changed : dst . changed ,
changes : {
links : oldDstLinks
2019-01-23 17:27:13 +01:00
}
2019-08-14 10:50:34 +02:00
} ) ;
src . changed = true ;
dst . changed = true ;
2019-01-23 17:27:13 +01:00
} else {
2019-08-14 10:50:34 +02:00
var link = { source : src , sourcePort : src _port , target : dst } ;
RED . nodes . addLink ( link ) ;
historyEvent . links = [ link ] ;
2019-08-13 11:31:21 +02:00
}
if ( ! keepAdding ) {
2019-08-14 10:50:34 +02:00
quickAddLink . el . remove ( ) ;
quickAddLink = null ;
2019-08-13 11:31:21 +02:00
if ( mouse _mode === RED . state . QUICK _JOINING ) {
2019-08-14 10:50:34 +02:00
if ( drag _line . portType === PORT _TYPE _OUTPUT && nn . outputs > 0 ) {
2019-08-13 11:31:21 +02:00
showDragLines ( [ { node : nn , port : 0 , portType : PORT _TYPE _OUTPUT } ] ) ;
2019-08-14 10:50:34 +02:00
} else if ( ! quickAddLink && drag _line . portType === PORT _TYPE _INPUT && nn . inputs > 0 ) {
2019-08-13 11:31:21 +02:00
showDragLines ( [ { node : nn , port : 0 , portType : PORT _TYPE _INPUT } ] ) ;
2016-11-09 14:17:26 +01:00
} else {
resetMouseVars ( ) ;
}
}
2019-01-23 17:27:13 +01:00
} else {
2019-08-14 10:50:34 +02:00
quickAddLink . node = nn ;
quickAddLink . port = 0 ;
2016-11-09 14:17:26 +01:00
}
2019-08-14 10:50:34 +02:00
} else {
hideDragLines ( ) ;
resetMouseVars ( ) ;
}
} else {
if ( ! keepAdding ) {
if ( mouse _mode === RED . state . QUICK _JOINING ) {
2019-08-13 11:31:21 +02:00
if ( nn . outputs > 0 ) {
2019-08-14 10:50:34 +02:00
showDragLines ( [ { node : nn , port : 0 , portType : PORT _TYPE _OUTPUT } ] ) ;
2019-08-13 11:31:21 +02:00
} else if ( nn . inputs > 0 ) {
2019-08-14 10:50:34 +02:00
showDragLines ( [ { node : nn , port : 0 , portType : PORT _TYPE _INPUT } ] ) ;
2019-08-13 11:31:21 +02:00
} else {
resetMouseVars ( ) ;
2019-01-23 17:27:13 +01:00
}
}
2019-08-14 10:50:34 +02:00
} else {
if ( nn . outputs > 0 ) {
quickAddLink = {
node : nn ,
port : 0 ,
portType : PORT _TYPE _OUTPUT
}
} else if ( nn . inputs > 0 ) {
quickAddLink = {
node : nn ,
port : 0 ,
portType : PORT _TYPE _INPUT
2019-01-23 17:27:13 +01:00
}
} else {
2019-08-14 10:50:34 +02:00
resetMouseVars ( ) ;
2019-01-23 17:27:13 +01:00
}
2016-11-07 22:25:09 +01:00
}
2019-08-14 10:50:34 +02:00
}
2020-03-03 20:04:32 +01:00
if ( targetGroup ) {
RED . group . addToGroup ( targetGroup , nn ) ;
2020-03-14 00:01:01 +01:00
if ( historyEvent . t !== "multi" ) {
historyEvent = {
t : 'multi' ,
events : [ historyEvent ]
}
}
historyEvent . events . push ( {
t : "addToGroup" ,
group : targetGroup ,
nodes : nn
} )
2020-03-03 20:04:32 +01:00
}
2019-08-14 10:50:34 +02:00
if ( spliceLink ) {
2019-08-13 11:31:21 +02:00
resetMouseVars ( ) ;
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
RED . nodes . removeLink ( spliceLink ) ;
var link1 = {
source : spliceLink . source ,
sourcePort : spliceLink . sourcePort ,
target : nn
} ;
var link2 = {
source : nn ,
sourcePort : 0 ,
target : spliceLink . target
} ;
RED . nodes . addLink ( link1 ) ;
RED . nodes . addLink ( link2 ) ;
2019-08-14 10:50:34 +02:00
historyEvent . links = ( historyEvent . links || [ ] ) . concat ( [ link1 , link2 ] ) ;
2019-08-13 11:31:21 +02:00
historyEvent . removedLinks = [ spliceLink ] ;
}
RED . history . push ( historyEvent ) ;
RED . nodes . add ( nn ) ;
RED . editor . validateNode ( nn ) ;
RED . nodes . dirty ( true ) ;
// auto select dropped node - so info shows (if visible)
clearSelection ( ) ;
nn . selected = true ;
2020-03-03 20:04:32 +01:00
if ( targetGroup ) {
selectGroup ( targetGroup , false ) ;
2020-03-26 16:27:34 +01:00
enterActiveGroup ( targetGroup ) ;
2020-03-03 20:04:32 +01:00
}
2019-08-13 11:31:21 +02:00
moving _set . push ( { n : nn } ) ;
2016-11-07 22:25:09 +01:00
updateActiveNodes ( ) ;
updateSelection ( ) ;
redraw ( ) ;
2019-08-13 11:31:21 +02:00
// At this point the newly added node will have a real width,
// so check if the position needs nudging
if ( lastAddedX !== undefined ) {
var lastNodeRHEdge = lastAddedX + lastAddedWidth / 2 ;
var thisNodeLHEdge = nn . x - nn . w / 2 ;
var gap = thisNodeLHEdge - lastNodeRHEdge ;
if ( gap != gridSize * 2 ) {
nn . x = nn . x + gridSize * 2 - gap ;
nn . dirty = true ;
nn . x = Math . ceil ( nn . x / gridSize ) * gridSize ;
redraw ( ) ;
}
}
if ( keepAdding ) {
if ( lastAddedX === undefined ) {
// ghostLink.attr("opacity",1);
setTimeout ( function ( ) {
RED . typeSearch . refresh ( { filter : { input : true } } ) ;
} , 100 ) ;
}
lastAddedX = nn . x ;
lastAddedWidth = nn . w ;
point [ 0 ] = nn . x + nn . w / 2 + node _width / 2 + gridSize * 2 ;
ghostNode . attr ( 'transform' , 'translate(' + ( point [ 0 ] - node _width / 2 ) + ',' + ( point [ 1 ] - node _height / 2 ) + ')' ) ;
rebuildQuickAddLink ( ) ;
} else {
quickAddActive = false ;
ghostNode . remove ( ) ;
}
2016-11-09 14:17:26 +01:00
}
2019-08-13 11:31:21 +02:00
} ) ;
updateActiveNodes ( ) ;
updateSelection ( ) ;
redraw ( ) ;
2013-09-05 16:02:48 +02:00
}
function canvasMouseMove ( ) {
2016-01-09 01:31:05 +01:00
var i ;
2016-01-18 12:09:52 +01:00
var node ;
2014-05-14 15:22:28 +02:00
// Prevent touch scrolling...
2014-05-09 00:00:11 +02:00
//if (d3.touches(this)[0]) {
// d3.event.preventDefault();
//}
2013-09-05 16:02:48 +02:00
// TODO: auto scroll the container
//var point = d3.mouse(this);
//if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; }
//console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
2018-06-06 21:51:30 +02:00
if ( mouse _mode === RED . state . PANNING ) {
var pos = [ d3 . event . pageX , d3 . event . pageY ] ;
var deltaPos = [
mouse _position [ 0 ] - pos [ 0 ] ,
mouse _position [ 1 ] - pos [ 1 ]
] ;
chart . scrollLeft ( scroll _position [ 0 ] + deltaPos [ 0 ] )
chart . scrollTop ( scroll _position [ 1 ] + deltaPos [ 1 ] )
return
}
mouse _position = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
2013-09-05 16:02:48 +02:00
if ( lasso ) {
var ox = parseInt ( lasso . attr ( "ox" ) ) ;
var oy = parseInt ( lasso . attr ( "oy" ) ) ;
var x = parseInt ( lasso . attr ( "x" ) ) ;
var y = parseInt ( lasso . attr ( "y" ) ) ;
2014-08-08 01:01:35 +02:00
var w ;
var h ;
2013-09-05 16:02:48 +02:00
if ( mouse _position [ 0 ] < ox ) {
x = mouse _position [ 0 ] ;
w = ox - x ;
} else {
w = mouse _position [ 0 ] - x ;
}
if ( mouse _position [ 1 ] < oy ) {
y = mouse _position [ 1 ] ;
h = oy - y ;
} else {
h = mouse _position [ 1 ] - y ;
}
lasso
. attr ( "x" , x )
. attr ( "y" , y )
. attr ( "width" , w )
. attr ( "height" , h )
;
return ;
}
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2020-03-03 20:04:32 +01:00
if ( mouse _mode != RED . state . QUICK _JOINING && mouse _mode != RED . state . IMPORT _DRAGGING && ! mousedown _node && ! mousedown _group && selected _link == null ) {
2014-08-08 01:01:35 +02:00
return ;
}
2013-09-05 16:02:48 +02:00
2014-08-08 01:01:35 +02:00
var mousePos ;
2020-03-04 22:48:38 +01:00
// if (mouse_mode === RED.state.GROUP_RESIZE) {
// mousePos = mouse_position;
// var nx = mousePos[0] + mousedown_group.dx;
// var ny = mousePos[1] + mousedown_group.dy;
// switch(mousedown_group.activeHandle) {
// case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break;
// case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break;
// case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break;
// case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break;
// }
// mousedown_group.dirty = true;
// }
2016-11-06 01:13:02 +01:00
if ( mouse _mode == RED . state . JOINING || mouse _mode === RED . state . QUICK _JOINING ) {
2013-09-05 16:02:48 +02:00
// update drag line
2018-01-14 00:46:16 +01:00
if ( drag _lines . length === 0 && mousedown _port _type !== null ) {
2016-01-09 01:29:04 +01:00
if ( d3 . event . shiftKey ) {
// Get all the wires we need to detach.
var links = [ ] ;
2016-06-01 00:42:00 +02:00
var existingLinks = [ ] ;
if ( selected _link &&
2017-01-26 16:38:25 +01:00
( ( mousedown _port _type === PORT _TYPE _OUTPUT &&
2016-06-01 00:42:00 +02:00
selected _link . source === mousedown _node &&
selected _link . sourcePort === mousedown _port _index
) ||
2017-01-26 16:38:25 +01:00
( mousedown _port _type === PORT _TYPE _INPUT &&
2016-06-01 00:42:00 +02:00
selected _link . target === mousedown _node
) )
) {
existingLinks = [ selected _link ] ;
2016-01-09 01:29:04 +01:00
} else {
2016-06-01 00:42:00 +02:00
var filter ;
2017-01-26 16:38:25 +01:00
if ( mousedown _port _type === PORT _TYPE _OUTPUT ) {
2016-06-01 00:42:00 +02:00
filter = {
source : mousedown _node ,
sourcePort : mousedown _port _index
}
} else {
filter = {
target : mousedown _node
}
2016-01-09 01:29:04 +01:00
}
2016-06-01 00:42:00 +02:00
existingLinks = RED . nodes . filterLinks ( filter ) ;
2016-01-09 01:29:04 +01:00
}
2016-01-09 01:31:05 +01:00
for ( i = 0 ; i < existingLinks . length ; i ++ ) {
2016-01-09 01:29:04 +01:00
var link = existingLinks [ i ] ;
RED . nodes . removeLink ( link ) ;
links . push ( {
link : link ,
2017-01-26 16:38:25 +01:00
node : ( mousedown _port _type === PORT _TYPE _OUTPUT ) ? link . target : link . source ,
port : ( mousedown _port _type === PORT _TYPE _OUTPUT ) ? 0 : link . sourcePort ,
portType : ( mousedown _port _type === PORT _TYPE _OUTPUT ) ? PORT _TYPE _INPUT : PORT _TYPE _OUTPUT
2016-01-09 01:29:04 +01:00
} )
}
2016-11-06 01:13:02 +01:00
if ( links . length === 0 ) {
resetMouseVars ( ) ;
redraw ( ) ;
} else {
showDragLines ( links ) ;
mouse _mode = 0 ;
updateActiveNodes ( ) ;
redraw ( ) ;
mouse _mode = RED . state . JOINING ;
}
2019-01-23 17:27:13 +01:00
} else if ( mousedown _node && ! quickAddLink ) {
2016-01-09 01:29:04 +01:00
showDragLines ( [ { node : mousedown _node , port : mousedown _port _index , portType : mousedown _port _type } ] ) ;
2013-09-28 22:15:32 +02:00
}
2016-06-01 00:42:00 +02:00
selected _link = null ;
2013-09-28 22:15:32 +02:00
}
2016-01-09 01:29:04 +01:00
mousePos = mouse _position ;
2016-01-09 01:31:05 +01:00
for ( i = 0 ; i < drag _lines . length ; i ++ ) {
2016-01-09 01:29:04 +01:00
var drag _line = drag _lines [ i ] ;
2017-01-26 16:38:25 +01:00
var numOutputs = ( drag _line . portType === PORT _TYPE _OUTPUT ) ? ( drag _line . node . outputs || 1 ) : 1 ;
2016-01-09 01:29:04 +01:00
var sourcePort = drag _line . port ;
var portY = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
2017-01-26 16:38:25 +01:00
var sc = ( drag _line . portType === PORT _TYPE _OUTPUT ) ? 1 : - 1 ;
2018-06-25 14:18:00 +02:00
drag _line . el . attr ( "d" , generateLinkPath ( drag _line . node . x + sc * drag _line . node . w / 2 , drag _line . node . y + portY , mousePos [ 0 ] , mousePos [ 1 ] , sc ) ) ;
2016-01-09 01:29:04 +01:00
}
2014-05-14 14:48:47 +02:00
d3 . event . preventDefault ( ) ;
2013-09-05 16:02:48 +02:00
} else if ( mouse _mode == RED . state . MOVING ) {
2014-10-26 23:22:08 +01:00
mousePos = d3 . mouse ( document . body ) ;
if ( isNaN ( mousePos [ 0 ] ) ) {
mousePos = d3 . touches ( document . body ) [ 0 ] ;
}
2014-08-08 01:01:35 +02:00
var d = ( mouse _offset [ 0 ] - mousePos [ 0 ] ) * ( mouse _offset [ 0 ] - mousePos [ 0 ] ) + ( mouse _offset [ 1 ] - mousePos [ 1 ] ) * ( mouse _offset [ 1 ] - mousePos [ 1 ] ) ;
2014-10-26 23:22:08 +01:00
if ( d > 3 ) {
2013-09-05 16:02:48 +02:00
mouse _mode = RED . state . MOVING _ACTIVE ;
2014-05-06 00:28:24 +02:00
clickElapsed = 0 ;
2016-01-14 16:59:45 +01:00
spliceActive = false ;
if ( moving _set . length === 1 ) {
node = moving _set [ 0 ] ;
2016-08-26 14:34:29 +02:00
spliceActive = node . n . hasOwnProperty ( "_def" ) &&
2019-08-16 15:59:15 +02:00
( ( node . n . hasOwnProperty ( "inputs" ) && node . n . inputs > 0 ) || ( ! node . n . hasOwnProperty ( "inputs" ) && node . n . _def . inputs > 0 ) ) &&
( ( node . n . hasOwnProperty ( "outputs" ) && node . n . outputs > 0 ) || ( ! node . n . hasOwnProperty ( "outputs" ) && node . n . _def . outputs > 0 ) ) &&
2016-01-14 16:59:45 +01:00
RED . nodes . filterLinks ( { source : node . n } ) . length === 0 &&
RED . nodes . filterLinks ( { target : node . n } ) . length === 0 ;
}
2013-09-05 16:02:48 +02:00
}
} else if ( mouse _mode == RED . state . MOVING _ACTIVE || mouse _mode == RED . state . IMPORT _DRAGGING ) {
2014-08-08 01:01:35 +02:00
mousePos = mouse _position ;
2013-12-28 21:03:43 +01:00
var minX = 0 ;
var minY = 0 ;
2016-08-26 17:22:06 +02:00
var maxX = space _width ;
var maxY = space _height ;
2014-03-22 14:47:47 +01:00
for ( var n = 0 ; n < moving _set . length ; n ++ ) {
2014-08-08 01:01:35 +02:00
node = moving _set [ n ] ;
2014-03-22 14:47:47 +01:00
if ( d3 . event . shiftKey ) {
node . n . ox = node . n . x ;
node . n . oy = node . n . y ;
}
2013-09-05 16:02:48 +02:00
node . n . x = mousePos [ 0 ] + node . dx ;
node . n . y = mousePos [ 1 ] + node . dy ;
2014-03-22 14:47:47 +01:00
node . n . dirty = true ;
2020-03-26 16:27:34 +01:00
if ( node . n . type === "group" ) {
RED . group . markDirty ( node . n ) ;
minX = Math . min ( node . n . x - 5 , minX ) ;
minY = Math . min ( node . n . y - 5 , minY ) ;
maxX = Math . max ( node . n . x + node . n . w + 5 , maxX ) ;
maxY = Math . max ( node . n . y + node . n . h + 5 , maxY ) ;
} else {
minX = Math . min ( node . n . x - node . n . w / 2 - 5 , minX ) ;
minY = Math . min ( node . n . y - node . n . h / 2 - 5 , minY ) ;
maxX = Math . max ( node . n . x + node . n . w / 2 + 5 , maxX ) ;
maxY = Math . max ( node . n . y + node . n . h / 2 + 5 , maxY ) ;
}
2013-12-28 21:03:43 +01:00
}
2014-08-08 01:01:35 +02:00
if ( minX !== 0 || minY !== 0 ) {
for ( i = 0 ; i < moving _set . length ; i ++ ) {
node = moving _set [ i ] ;
2014-03-22 14:47:47 +01:00
node . n . x -= minX ;
node . n . y -= minY ;
}
}
2016-08-26 17:22:06 +02:00
if ( maxX !== space _width || maxY !== space _height ) {
for ( i = 0 ; i < moving _set . length ; i ++ ) {
node = moving _set [ i ] ;
node . n . x -= ( maxX - space _width ) ;
node . n . y -= ( maxY - space _height ) ;
}
}
2020-03-04 22:48:38 +01:00
// if (mousedown_group) {
// mousedown_group.x = mousePos[0] + mousedown_group.dx;
// mousedown_group.y = mousePos[1] + mousedown_group.dy;
// mousedown_group.dirty = true;
// }
2020-03-26 16:27:34 +01:00
var gridOffset = [ 0 , 0 ] ;
2016-01-13 10:16:24 +01:00
if ( snapGrid != d3 . event . shiftKey && moving _set . length > 0 ) {
2020-03-26 16:27:34 +01:00
var i = 0 ;
// Prefer to snap nodes to the grid if there is one in the selection
do {
node = moving _set [ i ++ ] ;
} while ( i < moving _set . length && node . n . type === "group" )
if ( node . n . type === "group" ) {
// TODO: Group snap to grid
gridOffset [ 0 ] = node . n . x - ( gridSize * Math . floor ( node . n . x / gridSize ) ) - gridSize / 2 ;
gridOffset [ 1 ] = node . n . y - ( gridSize * Math . floor ( node . n . y / gridSize ) ) - gridSize / 2 ;
} else {
gridOffset [ 0 ] = node . n . x - ( gridSize * Math . floor ( ( node . n . x - node . n . w / 2 ) / gridSize ) + node . n . w / 2 ) ;
gridOffset [ 1 ] = node . n . y - ( gridSize * Math . floor ( node . n . y / gridSize ) ) ;
}
2014-08-08 01:01:35 +02:00
if ( gridOffset [ 0 ] !== 0 || gridOffset [ 1 ] !== 0 ) {
for ( i = 0 ; i < moving _set . length ; i ++ ) {
node = moving _set [ i ] ;
2014-03-22 14:47:47 +01:00
node . n . x -= gridOffset [ 0 ] ;
node . n . y -= gridOffset [ 1 ] ;
if ( node . n . x == node . n . ox && node . n . y == node . n . oy ) {
node . dirty = false ;
}
}
}
2013-09-05 16:02:48 +02:00
}
2020-03-26 16:27:34 +01:00
// Check link splice or group-add
if ( moving _set . length === 1 && moving _set [ 0 ] . n . type !== "group" ) {
2016-01-14 16:59:45 +01:00
node = moving _set [ 0 ] ;
if ( spliceActive ) {
if ( ! spliceTimer ) {
spliceTimer = setTimeout ( function ( ) {
var nodes = [ ] ;
var bestDistance = Infinity ;
var bestLink = null ;
2016-04-17 22:28:56 +02:00
var mouseX = node . n . x ;
var mouseY = node . n . y ;
2016-01-14 16:59:45 +01:00
if ( outer [ 0 ] [ 0 ] . getIntersectionList ) {
var svgRect = outer [ 0 ] [ 0 ] . createSVGRect ( ) ;
svgRect . x = mouseX ;
svgRect . y = mouseY ;
svgRect . width = 1 ;
svgRect . height = 1 ;
nodes = outer [ 0 ] [ 0 ] . getIntersectionList ( svgRect , outer [ 0 ] [ 0 ] ) ;
} else {
2016-05-17 10:16:58 +02:00
// Firefox doesn"t do getIntersectionList and that
2016-01-14 16:59:45 +01:00
// makes us sad
nodes = RED . view . getLinksAtPoint ( mouseX , mouseY ) ;
}
for ( var i = 0 ; i < nodes . length ; i ++ ) {
2019-05-15 14:54:29 +02:00
if ( d3 . select ( nodes [ i ] ) . classed ( "red-ui-flow-link-background" ) ) {
2016-01-14 16:59:45 +01:00
var length = nodes [ i ] . getTotalLength ( ) ;
for ( var j = 0 ; j < length ; j += 10 ) {
var p = nodes [ i ] . getPointAtLength ( j ) ;
var d2 = ( ( p . x - mouseX ) * ( p . x - mouseX ) ) + ( ( p . y - mouseY ) * ( p . y - mouseY ) ) ;
if ( d2 < 200 && d2 < bestDistance ) {
bestDistance = d2 ;
bestLink = nodes [ i ] ;
}
}
}
}
if ( activeSpliceLink && activeSpliceLink !== bestLink ) {
2019-05-15 14:54:29 +02:00
d3 . select ( activeSpliceLink . parentNode ) . classed ( "red-ui-flow-link-splice" , false ) ;
2016-01-14 16:59:45 +01:00
}
if ( bestLink ) {
2019-05-15 14:54:29 +02:00
d3 . select ( bestLink . parentNode ) . classed ( "red-ui-flow-link-splice" , true )
2016-01-14 16:59:45 +01:00
} else {
2019-05-15 14:54:29 +02:00
d3 . select ( ".red-ui-flow-link-splice" ) . classed ( "red-ui-flow-link-splice" , false ) ;
2016-01-14 16:59:45 +01:00
}
activeSpliceLink = bestLink ;
spliceTimer = null ;
} , 100 ) ;
}
}
2020-03-24 15:05:35 +01:00
if ( node . n . type !== 'subflow' && ! node . n . g && activeGroups ) {
2020-03-03 20:04:32 +01:00
if ( ! groupHoverTimer ) {
groupHoverTimer = setTimeout ( function ( ) {
2020-03-04 22:48:38 +01:00
activeHoverGroup = getGroupAt ( node . n . x , node . n . y ) ;
2020-03-03 20:04:32 +01:00
for ( var i = 0 ; i < activeGroups . length ; i ++ ) {
var g = activeGroups [ i ] ;
2020-03-04 22:48:38 +01:00
if ( g === activeHoverGroup ) {
2020-03-03 20:04:32 +01:00
g . hovered = true ;
2020-03-04 22:48:38 +01:00
g . dirty = true ;
} else if ( g . hovered ) {
2020-03-03 20:04:32 +01:00
g . hovered = false ;
2020-03-04 22:48:38 +01:00
g . dirty = true ;
2020-03-03 20:04:32 +01:00
}
}
groupHoverTimer = null ;
} , 50 ) ;
}
}
2016-01-14 16:59:45 +01:00
}
2013-09-05 16:02:48 +02:00
}
2014-02-25 00:35:11 +01:00
if ( mouse _mode !== 0 ) {
redraw ( ) ;
}
2013-09-05 16:02:48 +02:00
}
function canvasMouseUp ( ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "canvasMouseUp" , mouse _mode ) ; }
2016-01-09 01:31:05 +01:00
var i ;
2016-01-14 16:59:45 +01:00
var historyEvent ;
2018-06-06 21:51:30 +02:00
if ( mouse _mode === RED . state . PANNING ) {
resetMouseVars ( ) ;
return
}
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2016-11-06 01:13:02 +01:00
if ( mouse _mode === RED . state . QUICK _JOINING ) {
return ;
}
2013-09-05 16:02:48 +02:00
if ( mousedown _node && mouse _mode == RED . state . JOINING ) {
2016-01-09 01:29:04 +01:00
var removedLinks = [ ] ;
2016-01-09 01:31:05 +01:00
for ( i = 0 ; i < drag _lines . length ; i ++ ) {
2016-01-09 01:29:04 +01:00
if ( drag _lines [ i ] . link ) {
removedLinks . push ( drag _lines [ i ] . link )
}
}
2019-08-09 11:24:52 +02:00
if ( removedLinks . length > 0 ) {
historyEvent = {
t : "delete" ,
links : removedLinks ,
dirty : RED . nodes . dirty ( )
} ;
RED . history . push ( historyEvent ) ;
RED . nodes . dirty ( true ) ;
}
2016-01-09 01:29:04 +01:00
hideDragLines ( ) ;
2013-09-05 16:02:48 +02:00
}
if ( lasso ) {
var x = parseInt ( lasso . attr ( "x" ) ) ;
var y = parseInt ( lasso . attr ( "y" ) ) ;
var x2 = x + parseInt ( lasso . attr ( "width" ) ) ;
var y2 = y + parseInt ( lasso . attr ( "height" ) ) ;
2020-03-03 20:04:32 +01:00
if ( ! d3 . event . shiftKey ) {
2013-09-05 16:02:48 +02:00
clearSelection ( ) ;
}
2020-03-26 16:27:34 +01:00
activeGroups . forEach ( function ( g ) {
if ( ! g . selected ) {
if ( g . x > x && g . x + g . w < x2 && g . y > y && g . y + g . h < y2 ) {
while ( g . g ) {
g = RED . nodes . group ( g . g ) ;
}
if ( ! g . selected ) {
selectGroup ( g , true ) ;
}
}
}
} )
2020-03-03 20:04:32 +01:00
activeNodes . forEach ( function ( n ) {
2020-03-23 23:04:40 +01:00
if ( ! n . selected ) {
2020-03-03 20:04:32 +01:00
if ( n . x > x && n . x < x2 && n . y > y && n . y < y2 ) {
if ( n . g ) {
var group = RED . nodes . group ( n . g ) ;
2020-03-23 23:04:40 +01:00
while ( group . g ) {
group = RED . nodes . group ( group . g ) ;
}
2020-03-03 20:04:32 +01:00
if ( ! group . selected ) {
2020-03-09 12:14:18 +01:00
selectGroup ( group , true ) ;
2020-03-03 20:04:32 +01:00
}
} else {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
2013-09-05 16:02:48 +02:00
}
}
} ) ;
2020-03-26 16:27:34 +01:00
2020-03-23 23:04:40 +01:00
// var selectionChanged = false;
// do {
// selectionChanged = false;
// selectedGroups.forEach(function(g) {
// if (g.g && g.selected && RED.nodes.group(g.g).selected) {
// g.selected = false;
// selectionChanged = true;
// }
// })
// } while(selectionChanged);
2020-03-09 12:14:18 +01:00
2014-02-25 00:35:11 +01:00
if ( activeSubflow ) {
activeSubflow . in . forEach ( function ( n ) {
n . selected = ( n . x > x && n . x < x2 && n . y > y && n . y < y2 ) ;
if ( n . selected ) {
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
} ) ;
activeSubflow . out . forEach ( function ( n ) {
n . selected = ( n . x > x && n . x < x2 && n . y > y && n . y < y2 ) ;
if ( n . selected ) {
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
} ) ;
2019-02-02 00:44:50 +01:00
if ( activeSubflow . status ) {
activeSubflow . status . selected = ( activeSubflow . status . x > x && activeSubflow . status . x < x2 && activeSubflow . status . y > y && activeSubflow . status . y < y2 ) ;
if ( activeSubflow . status . selected ) {
activeSubflow . status . dirty = true ;
moving _set . push ( { n : activeSubflow . status } ) ;
}
}
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
updateSelection ( ) ;
lasso . remove ( ) ;
lasso = null ;
2019-01-20 15:43:17 +01:00
} else if ( mouse _mode == RED . state . DEFAULT && mousedown _link == null && ! d3 . event . ctrlKey && ! d3 . event . metaKey ) {
2014-05-09 00:00:11 +02:00
clearSelection ( ) ;
updateSelection ( ) ;
2013-09-05 16:02:48 +02:00
}
if ( mouse _mode == RED . state . MOVING _ACTIVE ) {
if ( moving _set . length > 0 ) {
2020-03-14 00:01:01 +01:00
var addedToGroup = null ;
2020-03-03 20:04:32 +01:00
if ( activeHoverGroup ) {
for ( var j = 0 ; j < moving _set . length ; j ++ ) {
var n = moving _set [ j ] ;
RED . group . addToGroup ( activeHoverGroup , n . n ) ;
}
2020-03-14 00:01:01 +01:00
addedToGroup = activeHoverGroup ;
2020-03-03 20:04:32 +01:00
activeHoverGroup . hovered = false ;
2020-03-26 16:27:34 +01:00
enterActiveGroup ( activeHoverGroup )
// TODO: check back whether this should add to moving_set
2020-03-03 20:04:32 +01:00
activeGroup . selected = true ;
activeHoverGroup = null ;
}
2013-09-05 16:02:48 +02:00
var ns = [ ] ;
2014-08-08 01:01:35 +02:00
for ( var j = 0 ; j < moving _set . length ; j ++ ) {
2017-06-30 00:23:16 +02:00
var n = moving _set [ j ] ;
if ( n . ox !== n . n . x || n . oy !== n . n . y ) {
ns . push ( { n : n . n , ox : n . ox , oy : n . oy , moved : n . n . moved } ) ;
n . n . dirty = true ;
n . n . moved = true ;
}
2013-09-05 16:02:48 +02:00
}
2020-03-26 16:27:34 +01:00
2017-06-30 00:23:16 +02:00
if ( ns . length > 0 ) {
historyEvent = { t : "move" , nodes : ns , dirty : RED . nodes . dirty ( ) } ;
if ( activeSpliceLink ) {
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
var spliceLink = d3 . select ( activeSpliceLink ) . data ( ) [ 0 ] ;
RED . nodes . removeLink ( spliceLink ) ;
var link1 = {
source : spliceLink . source ,
sourcePort : spliceLink . sourcePort ,
target : moving _set [ 0 ] . n
} ;
var link2 = {
source : moving _set [ 0 ] . n ,
sourcePort : 0 ,
target : spliceLink . target
} ;
RED . nodes . addLink ( link1 ) ;
RED . nodes . addLink ( link2 ) ;
historyEvent . links = [ link1 , link2 ] ;
historyEvent . removedLinks = [ spliceLink ] ;
updateActiveNodes ( ) ;
}
2020-03-14 00:01:01 +01:00
if ( addedToGroup ) {
historyEvent . addToGroup = addedToGroup ;
}
2017-06-30 00:23:16 +02:00
RED . nodes . dirty ( true ) ;
RED . history . push ( historyEvent ) ;
2016-01-14 16:59:45 +01:00
}
2013-09-05 16:02:48 +02:00
}
}
2020-03-03 20:04:32 +01:00
// if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) {
// if (mousedown_node.gSelected) {
// delete mousedown_node.gSelected
// } else {
// if (!d3.event.ctrlKey && !d3.event.metaKey) {
// clearSelection();
// }
// RED.nodes.group(mousedown_node.g).selected = true;
// mousedown_node.selected = true;
// mousedown_node.dirty = true;
// moving_set.push({n:mousedown_node});
// }
// }
2014-04-16 14:39:16 +02:00
if ( mouse _mode == RED . state . MOVING || mouse _mode == RED . state . MOVING _ACTIVE ) {
2020-03-03 20:04:32 +01:00
// if (mousedown_node) {
// delete mousedown_node.gSelected;
// }
2016-01-09 01:31:05 +01:00
for ( i = 0 ; i < moving _set . length ; i ++ ) {
2014-04-16 14:39:16 +02:00
delete moving _set [ i ] . ox ;
delete moving _set [ i ] . oy ;
}
}
2013-09-05 16:02:48 +02:00
if ( mouse _mode == RED . state . IMPORT _DRAGGING ) {
2016-12-04 23:59:43 +01:00
RED . keyboard . remove ( "escape" ) ;
2015-03-12 12:21:05 +01:00
updateActiveNodes ( ) ;
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2013-09-05 16:02:48 +02:00
}
resetMouseVars ( ) ;
2015-03-12 14:26:31 +01:00
redraw ( ) ;
2013-09-05 16:02:48 +02:00
}
function zoomIn ( ) {
if ( scaleFactor < 2 ) {
2019-05-25 23:11:05 +02:00
zoomView ( scaleFactor + 0.1 ) ;
2013-09-05 16:02:48 +02:00
}
}
function zoomOut ( ) {
if ( scaleFactor > 0.3 ) {
2019-05-25 23:11:05 +02:00
zoomView ( scaleFactor - 0.1 ) ;
2013-09-05 16:02:48 +02:00
}
}
2019-05-25 23:11:05 +02:00
function zoomZero ( ) { zoomView ( 1 ) ; }
function zoomView ( factor ) {
var screenSize = [ chart . width ( ) , chart . height ( ) ] ;
var scrollPos = [ chart . scrollLeft ( ) , chart . scrollTop ( ) ] ;
var center = [ ( scrollPos [ 0 ] + screenSize [ 0 ] / 2 ) / scaleFactor , ( scrollPos [ 1 ] + screenSize [ 1 ] / 2 ) / scaleFactor ] ;
scaleFactor = factor ;
var newCenter = [ ( scrollPos [ 0 ] + screenSize [ 0 ] / 2 ) / scaleFactor , ( scrollPos [ 1 ] + screenSize [ 1 ] / 2 ) / scaleFactor ] ;
var delta = [ ( newCenter [ 0 ] - center [ 0 ] ) * scaleFactor , ( newCenter [ 1 ] - center [ 1 ] ) * scaleFactor ]
chart . scrollLeft ( scrollPos [ 0 ] - delta [ 0 ] ) ;
chart . scrollTop ( scrollPos [ 1 ] - delta [ 1 ] ) ;
2018-06-09 00:32:17 +02:00
RED . view . navigator . resize ( ) ;
2013-09-05 16:02:48 +02:00
redraw ( ) ;
}
2019-05-25 23:11:05 +02:00
2013-09-05 16:02:48 +02:00
function selectAll ( ) {
2019-05-24 00:38:42 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE && selectNodesOptions . single ) {
return ;
}
2020-03-09 12:14:18 +01:00
exitActiveGroup ( ) ;
2020-03-03 20:04:32 +01:00
activeGroups . forEach ( function ( g ) {
2020-03-09 12:14:18 +01:00
if ( ! g . g ) {
selectGroup ( g , true ) ;
if ( ! g . selected ) {
g . selected = true ;
g . dirty = true ;
2013-09-05 16:02:48 +02:00
}
2020-03-09 12:14:18 +01:00
} else {
g . selected = false ;
2020-03-03 20:04:32 +01:00
g . dirty = true ;
}
} )
activeNodes . forEach ( function ( n ) {
if ( mouse _mode === RED . state . SELECTING _NODE ) {
if ( selectNodesOptions . filter && ! selectNodesOptions . filter ( n ) ) {
return ;
2019-06-07 15:35:22 +02:00
}
2019-05-23 17:48:07 +02:00
}
2020-03-03 20:04:32 +01:00
if ( ! n . g && ! n . selected ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
2013-09-05 16:02:48 +02:00
} ) ;
2020-03-03 20:04:32 +01:00
2019-05-23 17:48:07 +02:00
if ( mouse _mode !== RED . state . SELECTING _NODE && activeSubflow ) {
2014-02-25 00:35:11 +01:00
activeSubflow . in . forEach ( function ( n ) {
if ( ! n . selected ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
} ) ;
activeSubflow . out . forEach ( function ( n ) {
if ( ! n . selected ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
} ) ;
2019-02-02 00:44:50 +01:00
if ( activeSubflow . status ) {
if ( ! activeSubflow . status . selected ) {
activeSubflow . status . selected = true ;
activeSubflow . status . dirty = true ;
moving _set . push ( { n : activeSubflow . status } ) ;
}
}
2014-02-25 00:35:11 +01:00
}
2015-07-01 00:42:03 +02:00
2013-09-05 16:02:48 +02:00
selected _link = null ;
2019-05-23 17:48:07 +02:00
if ( mouse _mode !== RED . state . SELECTING _NODE ) {
updateSelection ( ) ;
}
2013-09-05 16:02:48 +02:00
redraw ( ) ;
}
function clearSelection ( ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "clearSelection" , mouse _mode ) ; }
2014-08-08 01:01:35 +02:00
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
2013-09-05 16:02:48 +02:00
var n = moving _set [ i ] ;
n . n . dirty = true ;
n . n . selected = false ;
}
moving _set = [ ] ;
selected _link = null ;
2020-03-03 20:04:32 +01:00
if ( activeGroup ) {
activeGroup . active = false
activeGroup . dirty = true ;
activeGroup = null ;
}
activeGroups . forEach ( function ( g ) {
g . selected = false ;
g . dirty = true ;
} )
2013-09-05 16:02:48 +02:00
}
2017-04-10 01:00:10 +02:00
var lastSelection = null ;
2013-09-05 16:02:48 +02:00
function updateSelection ( ) {
2015-03-04 14:19:13 +01:00
var selection = { } ;
2020-01-22 12:53:41 +01:00
var activeWorkspace = RED . workspaces . active ( ) ;
2015-07-01 00:42:03 +02:00
2018-10-30 23:18:16 +01:00
var workspaceSelection = RED . workspaces . selection ( ) ;
if ( workspaceSelection . length === 0 ) {
2020-03-16 12:16:18 +01:00
selection = getSelection ( ) ;
2018-10-30 23:18:16 +01:00
activeLinks = RED . nodes . filterLinks ( {
source : { z : activeWorkspace } ,
target : { z : activeWorkspace }
} ) ;
var tabOrder = RED . nodes . getWorkspaceOrder ( ) ;
var currentLinks = activeLinks ;
var addedLinkLinks = { } ;
activeFlowLinks = [ ] ;
2018-12-18 11:57:53 +01:00
var activeLinkNodeIds = Object . keys ( activeLinkNodes ) ;
activeLinkNodeIds . forEach ( function ( n ) {
activeLinkNodes [ n ] . dirty = true ;
} )
activeLinkNodes = { } ;
2018-10-30 23:18:16 +01:00
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
if ( moving _set [ i ] . n . type === "link out" || moving _set [ i ] . n . type === "link in" ) {
var linkNode = moving _set [ i ] . n ;
2018-12-18 11:57:53 +01:00
activeLinkNodes [ linkNode . id ] = linkNode ;
2018-10-30 23:18:16 +01:00
var offFlowLinks = { } ;
linkNode . links . forEach ( function ( id ) {
var target = RED . nodes . node ( id ) ;
if ( target ) {
if ( linkNode . type === "link out" ) {
if ( target . z === linkNode . z ) {
if ( ! addedLinkLinks [ linkNode . id + ":" + target . id ] ) {
activeLinks . push ( {
source : linkNode ,
sourcePort : 0 ,
target : target ,
link : true
} ) ;
addedLinkLinks [ linkNode . id + ":" + target . id ] = true ;
2018-12-18 11:57:53 +01:00
activeLinkNodes [ target . id ] = target ;
target . dirty = true ;
2018-10-30 23:18:16 +01:00
}
} else {
offFlowLinks [ target . z ] = offFlowLinks [ target . z ] || [ ] ;
offFlowLinks [ target . z ] . push ( target ) ;
2016-05-17 10:16:58 +02:00
}
} else {
2018-10-30 23:18:16 +01:00
if ( target . z === linkNode . z ) {
if ( ! addedLinkLinks [ target . id + ":" + linkNode . id ] ) {
activeLinks . push ( {
source : target ,
sourcePort : 0 ,
target : linkNode ,
link : true
} ) ;
addedLinkLinks [ target . id + ":" + linkNode . id ] = true ;
2018-12-18 11:57:53 +01:00
activeLinkNodes [ target . id ] = target ;
target . dirty = true ;
2018-10-30 23:18:16 +01:00
}
} else {
offFlowLinks [ target . z ] = offFlowLinks [ target . z ] || [ ] ;
offFlowLinks [ target . z ] . push ( target ) ;
2016-05-17 10:16:58 +02:00
}
}
}
} ) ;
2018-10-30 23:18:16 +01:00
var offFlows = Object . keys ( offFlowLinks ) ;
// offFlows.sort(function(A,B) {
// return tabOrder.indexOf(A) - tabOrder.indexOf(B);
// });
if ( offFlows . length > 0 ) {
activeFlowLinks . push ( {
refresh : Math . floor ( Math . random ( ) * 10000 ) ,
node : linkNode ,
links : offFlowLinks //offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};})
} ) ;
}
2016-05-17 10:16:58 +02:00
}
}
2018-12-18 11:57:53 +01:00
if ( activeFlowLinks . length === 0 && selected _link !== null && selected _link . link ) {
activeLinks . push ( selected _link ) ;
activeLinkNodes [ selected _link . source . id ] = selected _link . source ;
selected _link . source . dirty = true ;
activeLinkNodes [ selected _link . target . id ] = selected _link . target ;
selected _link . target . dirty = true ;
}
2018-10-30 23:18:16 +01:00
} else {
selection . flows = workspaceSelection ;
2016-05-17 10:16:58 +02:00
}
2017-06-06 11:27:03 +02:00
var selectionJSON = activeWorkspace + ":" + JSON . stringify ( selection , function ( key , value ) {
2018-10-30 23:18:16 +01:00
if ( key === 'nodes' || key === 'flows' ) {
2017-05-19 21:35:36 +02:00
return value . map ( function ( n ) { return n . id } )
} else if ( key === 'link' ) {
return value . source . id + ":" + value . sourcePort + ":" + value . target . id ;
}
return value ;
} ) ;
2017-04-10 01:00:10 +02:00
if ( selectionJSON !== lastSelection ) {
lastSelection = selectionJSON ;
RED . events . emit ( "view:selection-changed" , selection ) ;
}
2013-09-05 16:02:48 +02:00
}
2015-07-01 00:42:03 +02:00
2016-11-07 22:51:03 +01:00
function editSelection ( ) {
if ( moving _set . length > 0 ) {
var node = moving _set [ 0 ] . n ;
if ( node . type === "subflow" ) {
RED . editor . editSubflow ( activeSubflow ) ;
2020-03-26 16:27:34 +01:00
} else if ( node . type === "group" ) {
RED . editor . editGroup ( node ) ;
2016-11-07 22:51:03 +01:00
} else {
RED . editor . edit ( node ) ;
}
}
}
2013-09-05 16:02:48 +02:00
function deleteSelection ( ) {
2019-05-23 17:48:07 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
return ;
}
2018-10-27 00:17:09 +02:00
if ( portLabelHover ) {
portLabelHover . remove ( ) ;
portLabelHover = null ;
}
2018-10-30 23:18:16 +01:00
var workspaceSelection = RED . workspaces . selection ( ) ;
if ( workspaceSelection . length > 0 ) {
var workspaceCount = 0 ;
2020-01-22 12:53:41 +01:00
workspaceSelection . forEach ( function ( ws ) { if ( ws . type === 'tab' ) { workspaceCount ++ } } ) ;
2018-10-30 23:18:16 +01:00
if ( workspaceCount === RED . workspaces . count ( ) ) {
// Cannot delete all workspaces
return ;
}
var historyEvent = {
t : 'delete' ,
dirty : RED . nodes . dirty ( ) ,
nodes : [ ] ,
links : [ ] ,
2020-03-14 00:01:01 +01:00
groups : [ ] ,
2018-10-30 23:18:16 +01:00
workspaces : [ ] ,
subflows : [ ]
}
var workspaceOrder = RED . nodes . getWorkspaceOrder ( ) . slice ( 0 ) ;
for ( var i = 0 ; i < workspaceSelection . length ; i ++ ) {
var ws = workspaceSelection [ i ] ;
ws . _index = workspaceOrder . indexOf ( ws . id ) ;
RED . workspaces . remove ( ws ) ;
var subEvent ;
if ( ws . type === 'tab' ) {
historyEvent . workspaces . push ( ws ) ;
subEvent = RED . nodes . removeWorkspace ( ws . id ) ;
} else {
subEvent = RED . subflow . removeSubflow ( ws . id ) ;
historyEvent . subflows = historyEvent . subflows . concat ( subEvent . subflows ) ;
}
historyEvent . nodes = historyEvent . nodes . concat ( subEvent . nodes ) ;
historyEvent . links = historyEvent . links . concat ( subEvent . links ) ;
2020-03-14 00:01:01 +01:00
historyEvent . groups = historyEvent . groups . concat ( subEvent . groups ) ;
2018-10-30 23:18:16 +01:00
}
RED . history . push ( historyEvent ) ;
RED . nodes . dirty ( true ) ;
updateActiveNodes ( ) ;
updateSelection ( ) ;
redraw ( ) ;
} else if ( moving _set . length > 0 || selected _link != null ) {
2016-05-08 23:50:55 +02:00
var result ;
2020-06-03 15:23:26 +02:00
var node ;
2016-05-08 23:50:55 +02:00
var removedNodes = [ ] ;
var removedLinks = [ ] ;
2020-03-14 00:01:01 +01:00
var removedGroups = [ ] ;
2016-05-08 23:50:55 +02:00
var removedSubflowOutputs = [ ] ;
var removedSubflowInputs = [ ] ;
2020-01-22 12:53:41 +01:00
var removedSubflowStatus ;
2016-05-08 23:50:55 +02:00
var subflowInstances = [ ] ;
var startDirty = RED . nodes . dirty ( ) ;
var startChanged = false ;
2020-03-26 16:27:34 +01:00
var selectedGroups = [ ] ;
2016-05-08 23:50:55 +02:00
if ( moving _set . length > 0 ) {
2020-06-03 15:23:26 +02:00
2016-05-08 23:50:55 +02:00
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
2020-06-03 15:23:26 +02:00
node = moving _set [ i ] . n ;
2020-03-26 16:27:34 +01:00
if ( node . type === "group" ) {
selectedGroups . push ( node ) ;
2020-06-03 15:23:26 +02:00
}
}
// Make sure we have identified all groups about to be deleted
for ( i = 0 ; i < selectedGroups . length ; i ++ ) {
selectedGroups [ i ] . nodes . forEach ( function ( n ) {
if ( n . type === "group" && selectedGroups . indexOf ( n ) === - 1 ) {
selectedGroups . push ( n ) ;
}
} )
}
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
node = moving _set [ i ] . n ;
node . selected = false ;
if ( node . type !== "group" && node . type !== "subflow" ) {
2016-05-08 23:50:55 +02:00
if ( node . x < 0 ) {
node . x = 25
}
var removedEntities = RED . nodes . remove ( node . id ) ;
removedNodes . push ( node ) ;
removedNodes = removedNodes . concat ( removedEntities . nodes ) ;
removedLinks = removedLinks . concat ( removedEntities . links ) ;
2020-03-24 15:05:09 +01:00
if ( node . g ) {
var group = RED . nodes . group ( node . g ) ;
2020-06-03 15:23:26 +02:00
if ( selectedGroups . indexOf ( group ) === - 1 ) {
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
var index = group . nodes . indexOf ( node ) ;
group . nodes . splice ( index , 1 ) ;
RED . group . markDirty ( group ) ;
2020-03-24 15:05:09 +01:00
}
}
2016-05-08 23:50:55 +02:00
} else {
if ( node . direction === "out" ) {
removedSubflowOutputs . push ( node ) ;
} else if ( node . direction === "in" ) {
removedSubflowInputs . push ( node ) ;
2019-02-02 00:44:50 +01:00
} else if ( node . direction === "status" ) {
removedSubflowStatus = node ;
2016-05-08 23:50:55 +02:00
}
node . dirty = true ;
2014-02-25 00:35:11 +01:00
}
2016-05-08 23:50:55 +02:00
}
2020-06-03 15:23:26 +02:00
// Groups must be removed in the right order - from inner-most
// to outermost.
for ( i = selectedGroups . length - 1 ; i >= 0 ; i -- ) {
var g = selectedGroups [ i ] ;
removedGroups . push ( g ) ;
RED . nodes . removeGroup ( g ) ;
2020-03-13 12:27:13 +01:00
}
2016-05-08 23:50:55 +02:00
if ( removedSubflowOutputs . length > 0 ) {
result = RED . subflow . removeOutput ( removedSubflowOutputs ) ;
if ( result ) {
removedLinks = removedLinks . concat ( result . links ) ;
2014-11-13 00:51:42 +01:00
}
2014-08-08 01:01:35 +02:00
}
2016-05-08 23:50:55 +02:00
// Assume 0/1 inputs
if ( removedSubflowInputs . length == 1 ) {
result = RED . subflow . removeInput ( ) ;
if ( result ) {
removedLinks = removedLinks . concat ( result . links ) ;
}
2014-11-13 00:51:42 +01:00
}
2019-02-02 00:44:50 +01:00
if ( removedSubflowStatus ) {
result = RED . subflow . removeStatus ( ) ;
if ( result ) {
removedLinks = removedLinks . concat ( result . links ) ;
}
}
2016-05-08 23:50:55 +02:00
var instances = RED . subflow . refresh ( true ) ;
if ( instances ) {
subflowInstances = instances . instances ;
}
moving _set = [ ] ;
2020-03-14 00:01:01 +01:00
if ( removedNodes . length > 0 || removedSubflowOutputs . length > 0 || removedSubflowInputs . length > 0 || removedSubflowStatus || removedGroups . length > 0 ) {
2016-05-08 23:50:55 +02:00
RED . nodes . dirty ( true ) ;
2015-07-30 12:03:37 +02:00
}
2014-11-13 01:02:41 +01:00
}
2018-12-18 11:57:53 +01:00
var historyEvent ;
if ( selected _link && selected _link . link ) {
var sourceId = selected _link . source . id ;
var targetId = selected _link . target . id ;
var sourceIdIndex = selected _link . target . links . indexOf ( sourceId ) ;
var targetIdIndex = selected _link . source . links . indexOf ( targetId ) ;
historyEvent = {
t : "multi" ,
events : [
{
t : "edit" ,
node : selected _link . source ,
changed : selected _link . source . changed ,
changes : {
links : $ . extend ( true , { } , { v : selected _link . source . links } ) . v
}
} ,
{
t : "edit" ,
node : selected _link . target ,
changed : selected _link . target . changed ,
changes : {
links : $ . extend ( true , { } , { v : selected _link . target . links } ) . v
}
}
] ,
dirty : RED . nodes . dirty ( )
}
2019-09-10 12:12:07 +02:00
RED . nodes . dirty ( true ) ;
2018-12-18 11:57:53 +01:00
selected _link . source . changed = true ;
selected _link . target . changed = true ;
selected _link . target . links . splice ( sourceIdIndex , 1 ) ;
selected _link . source . links . splice ( targetIdIndex , 1 ) ;
selected _link . source . dirty = true ;
selected _link . target . dirty = true ;
} else {
if ( selected _link ) {
RED . nodes . removeLink ( selected _link ) ;
removedLinks . push ( selected _link ) ;
}
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2018-12-18 11:57:53 +01:00
historyEvent = {
t : "delete" ,
nodes : removedNodes ,
links : removedLinks ,
2020-03-14 00:01:01 +01:00
groups : removedGroups ,
2018-12-18 11:57:53 +01:00
subflowOutputs : removedSubflowOutputs ,
subflowInputs : removedSubflowInputs ,
subflow : {
2019-02-02 00:44:50 +01:00
id : activeSubflow ? activeSubflow . id : undefined ,
2018-12-18 11:57:53 +01:00
instances : subflowInstances
} ,
dirty : startDirty
} ;
2019-02-02 00:44:50 +01:00
if ( removedSubflowStatus ) {
historyEvent . subflow . status = removedSubflowStatus ;
}
2014-02-25 00:35:11 +01:00
}
2016-05-08 23:50:55 +02:00
RED . history . push ( historyEvent ) ;
2013-09-05 16:02:48 +02:00
2016-05-08 23:50:55 +02:00
selected _link = null ;
updateActiveNodes ( ) ;
updateSelection ( ) ;
redraw ( ) ;
}
2013-09-05 16:02:48 +02:00
}
function copySelection ( ) {
2019-05-23 17:48:07 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
return ;
}
2018-11-09 09:51:55 +01:00
var nodes = [ ] ;
var selection = RED . workspaces . selection ( ) ;
if ( selection . length > 0 ) {
nodes = [ ] ;
selection . forEach ( function ( n ) {
if ( n . type === 'tab' ) {
nodes . push ( n ) ;
2020-03-26 21:26:58 +01:00
nodes = nodes . concat ( RED . nodes . groups ( n . id ) ) ;
2018-11-09 09:51:55 +01:00
nodes = nodes . concat ( RED . nodes . filterNodes ( { z : n . id } ) ) ;
}
} ) ;
2020-03-09 12:14:18 +01:00
} else {
2020-03-26 16:27:34 +01:00
selection = RED . view . selection ( ) ;
if ( selection . nodes ) {
selection . nodes . forEach ( function ( n ) {
nodes . push ( n ) ;
if ( n . type === 'group' ) {
nodes = nodes . concat ( RED . group . getNodes ( n , true ) ) ;
}
} )
2020-03-09 12:14:18 +01:00
}
2018-11-09 09:51:55 +01:00
}
if ( nodes . length > 0 ) {
2013-09-05 16:02:48 +02:00
var nns = [ ] ;
2020-03-26 16:27:34 +01:00
var nodeCount = 0 ;
var groupCount = 0 ;
2018-11-09 09:51:55 +01:00
for ( var n = 0 ; n < nodes . length ; n ++ ) {
var node = nodes [ n ] ;
2015-07-08 23:12:52 +02:00
// The only time a node.type == subflow can be selected is the
2016-05-17 10:16:58 +02:00
// input/output "proxy" nodes. They cannot be copied.
2014-02-25 00:35:11 +01:00
if ( node . type != "subflow" ) {
2020-03-26 16:27:34 +01:00
if ( node . type === "group" ) {
groupCount ++ ;
} else {
nodeCount ++ ;
}
2015-07-08 23:12:52 +02:00
for ( var d in node . _def . defaults ) {
if ( node . _def . defaults . hasOwnProperty ( d ) ) {
if ( node . _def . defaults [ d ] . type ) {
var configNode = RED . nodes . node ( node [ d ] ) ;
if ( configNode && configNode . _def . exclusive ) {
nns . push ( RED . nodes . convertNode ( configNode ) ) ;
}
}
}
}
2014-02-25 00:35:11 +01:00
nns . push ( RED . nodes . convertNode ( node ) ) ;
2015-07-08 23:12:52 +02:00
//TODO: if the node has an exclusive config node, it should also be copied, to ensure it remains exclusive...
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
}
clipboard = JSON . stringify ( nns ) ;
2020-03-26 16:22:59 +01:00
if ( nodeCount > 0 ) {
RED . notify ( RED . _ ( "clipboard.nodeCopied" , { count : nodeCount } ) , { id : "clipboard" } ) ;
} else if ( groupCount > 0 ) {
RED . notify ( RED . _ ( "clipboard.groupCopied" , { count : groupCount } ) , { id : "clipboard" } ) ;
}
2013-09-05 16:02:48 +02:00
}
}
2020-03-04 22:48:38 +01:00
2014-10-09 11:05:45 +02:00
function calculateTextWidth ( str , className , offset ) {
2019-07-26 04:36:22 +02:00
var result = convertLineBreakCharacter ( str ) ;
var width = 0 ;
for ( var i = 0 ; i < result . length ; i ++ ) {
var calculateTextW = calculateTextDimensions ( result [ i ] , className , offset , 0 ) [ 0 ] ;
if ( width < calculateTextW ) {
width = calculateTextW ;
}
}
return width ;
2017-01-27 17:33:11 +01:00
}
2019-05-22 01:04:19 +02:00
var textDimensionPlaceholder = { } ;
2017-01-27 17:33:11 +01:00
function calculateTextDimensions ( str , className , offsetW , offsetH ) {
2019-05-22 01:04:19 +02:00
if ( ! textDimensionPlaceholder [ className ] ) {
textDimensionPlaceholder [ className ] = document . createElement ( "span" ) ;
textDimensionPlaceholder [ className ] . className = className ;
textDimensionPlaceholder [ className ] . style . position = "absolute" ;
textDimensionPlaceholder [ className ] . style . top = "-1000px" ;
document . getElementById ( "red-ui-editor" ) . appendChild ( textDimensionPlaceholder [ className ] ) ;
}
textDimensionPlaceholder [ className ] . textContent = ( str || "" ) ;
var w = textDimensionPlaceholder [ className ] . offsetWidth ;
var h = textDimensionPlaceholder [ className ] . offsetHeight ;
2020-03-26 16:27:34 +01:00
return [ ( offsetW || 0 ) + w , ( offsetH || 0 ) + h ] ;
2013-09-05 16:02:48 +02:00
}
2019-09-03 11:43:46 +02:00
var separateTextByLineBreak = [ ] ;
2019-07-26 04:36:22 +02:00
function convertLineBreakCharacter ( str ) {
var result = [ ] ;
2019-09-03 11:43:46 +02:00
var count = 0 ;
var result _temp = '' ;
for ( var i = 0 ; i < str . length ; i ++ ) {
if ( str . charAt ( i ) == '\\' ) {
if ( str . charAt ( i + 1 ) == '\\' ) {
result _temp += str . charAt ( i ) ;
i ++ ;
} else if ( str . charAt ( i + 1 ) == 'n' ) {
2020-05-19 18:53:20 +02:00
result . push ( result _temp . trim ( ) ) ;
2019-09-03 11:43:46 +02:00
if ( i + 1 == str . length - 1 ) {
result . push ( '' ) ;
}
result _temp = '' ;
count = i + 2 ;
i ++ ;
} else {
result _temp += str . charAt ( i ) ;
}
2019-07-26 04:36:22 +02:00
} else {
2019-09-03 11:43:46 +02:00
result _temp += str . charAt ( i ) ;
2019-07-26 04:36:22 +02:00
}
}
2019-09-03 11:43:46 +02:00
if ( count == 0 || count < str . length ) {
2020-05-19 18:53:20 +02:00
result . push ( result _temp . trim ( ) ) ;
2019-09-03 11:43:46 +02:00
}
separateTextByLineBreak = result ;
2019-07-26 04:36:22 +02:00
return result ;
}
2013-09-05 16:02:48 +02:00
function resetMouseVars ( ) {
mousedown _node = null ;
2020-03-03 20:04:32 +01:00
mousedown _group = null ;
mousedown _group _handle = null ;
2013-09-05 16:02:48 +02:00
mouseup _node = null ;
mousedown _link = null ;
mouse _mode = 0 ;
2018-01-14 00:46:16 +01:00
mousedown _port _type = null ;
2016-01-14 16:59:45 +01:00
activeSpliceLink = null ;
spliceActive = false ;
2019-05-15 14:54:29 +02:00
d3 . select ( ".red-ui-flow-link-splice" ) . classed ( "red-ui-flow-link-splice" , false ) ;
2016-01-14 16:59:45 +01:00
if ( spliceTimer ) {
clearTimeout ( spliceTimer ) ;
spliceTimer = null ;
}
2020-03-03 20:04:32 +01:00
if ( groupHoverTimer ) {
clearTimeout ( groupHoverTimer ) ;
groupHoverTimer = null ;
}
2013-09-05 16:02:48 +02:00
}
2016-11-06 01:13:02 +01:00
function disableQuickJoinEventHandler ( evt ) {
2016-11-06 01:24:01 +01:00
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
if ( evt . keyCode === 17 || evt . key === "Meta" || evt . keyCode === 91 ) {
2016-11-06 01:13:02 +01:00
resetMouseVars ( ) ;
hideDragLines ( ) ;
redraw ( ) ;
$ ( window ) . off ( 'keyup' , disableQuickJoinEventHandler ) ;
}
}
2013-09-05 16:02:48 +02:00
function portMouseDown ( d , portType , portIndex ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "portMouseDown" , mouse _mode , d ) ; }
2014-02-25 00:35:11 +01:00
//console.log(d,portType,portIndex);
2013-09-05 16:02:48 +02:00
// disable zoom
2019-05-01 23:41:20 +02:00
//eventLayer.call(d3.behavior.zoom().on("zoom"), null);
2018-06-25 14:36:58 +02:00
if ( d3 . event . button === 1 ) {
return ;
}
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2013-09-05 16:02:48 +02:00
mousedown _node = d ;
mousedown _port _type = portType ;
mousedown _port _index = portIndex || 0 ;
2016-11-06 01:13:02 +01:00
if ( mouse _mode !== RED . state . QUICK _JOINING ) {
mouse _mode = RED . state . JOINING ;
document . body . style . cursor = "crosshair" ;
if ( d3 . event . ctrlKey || d3 . event . metaKey ) {
mouse _mode = RED . state . QUICK _JOINING ;
showDragLines ( [ { node : mousedown _node , port : mousedown _port _index , portType : mousedown _port _type } ] ) ;
$ ( window ) . on ( 'keyup' , disableQuickJoinEventHandler ) ;
}
}
2016-11-09 14:17:26 +01:00
d3 . event . stopPropagation ( ) ;
2014-05-14 14:48:47 +02:00
d3 . event . preventDefault ( ) ;
2014-08-08 01:01:35 +02:00
}
2013-09-05 16:02:48 +02:00
function portMouseUp ( d , portType , portIndex ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "portMouseUp" , mouse _mode , d ) ; }
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2016-01-09 01:29:04 +01:00
var i ;
2018-01-18 00:14:18 +01:00
if ( mouse _mode === RED . state . QUICK _JOINING && drag _lines . length > 0 ) {
2018-12-18 11:57:53 +01:00
if ( drag _lines [ 0 ] . node === d ) {
// Cannot quick-join to self
return
}
if ( drag _lines [ 0 ] . virtualLink &&
(
( drag _lines [ 0 ] . node . type === 'link in' && d . type !== 'link out' ) ||
( drag _lines [ 0 ] . node . type === 'link out' && d . type !== 'link in' )
)
) {
2016-11-06 01:13:02 +01:00
return
}
}
2013-09-05 16:02:48 +02:00
document . body . style . cursor = "" ;
2016-11-06 01:13:02 +01:00
if ( mouse _mode == RED . state . JOINING || mouse _mode == RED . state . QUICK _JOINING ) {
2014-05-11 14:57:54 +02:00
if ( typeof TouchEvent != "undefined" && d3 . event instanceof TouchEvent ) {
2014-05-10 00:30:00 +02:00
RED . nodes . eachNode ( function ( n ) {
2016-01-09 01:29:04 +01:00
if ( n . z == RED . workspaces . active ( ) ) {
var hw = n . w / 2 ;
var hh = n . h / 2 ;
if ( n . x - hw < mouse _position [ 0 ] && n . x + hw > mouse _position [ 0 ] &&
n . y - hh < mouse _position [ 1 ] && n . y + hh > mouse _position [ 1 ] ) {
mouseup _node = n ;
2017-01-26 16:38:25 +01:00
portType = mouseup _node . inputs > 0 ? PORT _TYPE _INPUT : PORT _TYPE _OUTPUT ;
2016-01-09 01:29:04 +01:00
portIndex = 0 ;
2014-05-10 00:30:00 +02:00
}
2016-01-09 01:29:04 +01:00
}
2014-05-10 00:30:00 +02:00
} ) ;
} else {
mouseup _node = d ;
}
2016-01-09 01:29:04 +01:00
var addedLinks = [ ] ;
var removedLinks = [ ] ;
2018-12-18 11:57:53 +01:00
var modifiedNodes = [ ] ; // joining link nodes
var select _link = null ;
2016-01-09 01:29:04 +01:00
for ( i = 0 ; i < drag _lines . length ; i ++ ) {
if ( drag _lines [ i ] . link ) {
removedLinks . push ( drag _lines [ i ] . link )
}
}
2018-12-18 11:57:53 +01:00
var linkEditEvents = [ ] ;
2016-01-09 01:29:04 +01:00
for ( i = 0 ; i < drag _lines . length ; i ++ ) {
if ( portType != drag _lines [ i ] . portType && mouseup _node !== drag _lines [ i ] . node ) {
var drag _line = drag _lines [ i ] ;
var src , dst , src _port ;
2017-01-26 16:38:25 +01:00
if ( drag _line . portType === PORT _TYPE _OUTPUT ) {
2016-01-09 01:29:04 +01:00
src = drag _line . node ;
src _port = drag _line . port ;
dst = mouseup _node ;
2017-01-26 16:38:25 +01:00
} else if ( drag _line . portType === PORT _TYPE _INPUT ) {
2016-01-09 01:29:04 +01:00
src = mouseup _node ;
dst = drag _line . node ;
src _port = portIndex ;
}
2018-12-18 11:57:53 +01:00
var link = { source : src , sourcePort : src _port , target : dst } ;
if ( drag _line . virtualLink ) {
2019-06-17 16:37:45 +02:00
if ( /^link (in|out)$/ . test ( src . type ) && /^link (in|out)$/ . test ( dst . type ) && src . type !== dst . type ) {
2018-12-18 11:57:53 +01:00
if ( src . links . indexOf ( dst . id ) === - 1 && dst . links . indexOf ( src . id ) === - 1 ) {
var oldSrcLinks = $ . extend ( true , { } , { v : src . links } ) . v
var oldDstLinks = $ . extend ( true , { } , { v : dst . links } ) . v
src . links . push ( dst . id ) ;
dst . links . push ( src . id ) ;
src . dirty = true ;
dst . dirty = true ;
modifiedNodes . push ( src ) ;
modifiedNodes . push ( dst ) ;
link . link = true ;
activeLinks . push ( link ) ;
activeLinkNodes [ src . id ] = src ;
activeLinkNodes [ dst . id ] = dst ;
select _link = link ;
linkEditEvents . push ( {
t : 'edit' ,
node : src ,
dirty : RED . nodes . dirty ( ) ,
changed : src . changed ,
changes : {
links : oldSrcLinks
}
} ) ;
linkEditEvents . push ( {
t : 'edit' ,
node : dst ,
dirty : RED . nodes . dirty ( ) ,
changed : dst . changed ,
changes : {
links : oldDstLinks
}
} ) ;
src . changed = true ;
dst . changed = true ;
}
}
} else {
2019-03-28 11:48:48 +01:00
// This is not a virtualLink - which means it started
// on a regular node port. Need to ensure the this isn't
// connecting to a link node virual port.
if ( ! (
( d . type === "link out" && portType === PORT _TYPE _OUTPUT ) ||
( d . type === "link in" && portType === PORT _TYPE _INPUT )
) ) {
var existingLink = RED . nodes . filterLinks ( { source : src , target : dst , sourcePort : src _port } ) . length !== 0 ;
if ( ! existingLink ) {
RED . nodes . addLink ( link ) ;
addedLinks . push ( link ) ;
}
2018-12-18 11:57:53 +01:00
}
2016-01-09 01:29:04 +01:00
}
}
2013-09-05 16:02:48 +02:00
}
2018-12-18 11:57:53 +01:00
if ( addedLinks . length > 0 || removedLinks . length > 0 || modifiedNodes . length > 0 ) {
// console.log(addedLinks);
// console.log(removedLinks);
// console.log(modifiedNodes);
var historyEvent ;
if ( modifiedNodes . length > 0 ) {
historyEvent = {
t : "multi" ,
events : linkEditEvents ,
dirty : RED . nodes . dirty ( )
} ;
} else {
historyEvent = {
t : "add" ,
links : addedLinks ,
removedLinks : removedLinks ,
dirty : RED . nodes . dirty ( )
} ;
}
2015-07-30 12:03:37 +02:00
if ( activeSubflow ) {
var subflowRefresh = RED . subflow . refresh ( true ) ;
if ( subflowRefresh ) {
historyEvent . subflow = {
id : activeSubflow . id ,
changed : activeSubflow . changed ,
instances : subflowRefresh . instances
}
}
}
RED . history . push ( historyEvent ) ;
2015-03-12 12:21:05 +01:00
updateActiveNodes ( ) ;
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2013-09-05 16:02:48 +02:00
}
2016-11-06 01:13:02 +01:00
if ( mouse _mode === RED . state . QUICK _JOINING ) {
2018-12-18 11:57:53 +01:00
if ( addedLinks . length > 0 || modifiedNodes . length > 0 ) {
2016-11-06 01:13:02 +01:00
hideDragLines ( ) ;
2017-01-26 16:38:25 +01:00
if ( portType === PORT _TYPE _INPUT && d . outputs > 0 ) {
showDragLines ( [ { node : d , port : 0 , portType : PORT _TYPE _OUTPUT } ] ) ;
} else if ( portType === PORT _TYPE _OUTPUT && d . inputs > 0 ) {
showDragLines ( [ { node : d , port : 0 , portType : PORT _TYPE _INPUT } ] ) ;
2016-11-06 01:13:02 +01:00
} else {
resetMouseVars ( ) ;
}
2018-12-18 11:57:53 +01:00
selected _link = select _link ;
mousedown _link = select _link ;
if ( select _link ) {
updateSelection ( ) ;
}
2016-11-06 01:13:02 +01:00
}
redraw ( ) ;
return ;
}
2016-01-09 01:29:04 +01:00
resetMouseVars ( ) ;
hideDragLines ( ) ;
2018-12-18 11:57:53 +01:00
selected _link = select _link ;
mousedown _link = select _link ;
if ( select _link ) {
updateSelection ( ) ;
}
2013-09-05 16:02:48 +02:00
redraw ( ) ;
}
}
2017-01-26 16:38:25 +01:00
var portLabelHoverTimeout = null ;
var portLabelHover = null ;
2017-01-27 17:33:11 +01:00
function getElementPosition ( node ) {
var d3Node = d3 . select ( node ) ;
2019-05-01 23:41:20 +02:00
if ( d3Node . attr ( 'class' ) === 'red-ui-workspace-chart-event-layer' ) {
2017-01-27 17:33:11 +01:00
return [ 0 , 0 ] ;
}
var result = [ ] ;
var localPos = [ 0 , 0 ] ;
if ( node . nodeName . toLowerCase ( ) === 'g' ) {
var transform = d3Node . attr ( "transform" ) ;
if ( transform ) {
localPos = d3 . transform ( transform ) . translate ;
}
} else {
localPos = [ d3Node . attr ( "x" ) || 0 , d3Node . attr ( "y" ) || 0 ] ;
}
var parentPos = getElementPosition ( node . parentNode ) ;
return [ localPos [ 0 ] + parentPos [ 0 ] , localPos [ 1 ] + parentPos [ 1 ] ]
}
2017-01-27 19:11:25 +01:00
function getPortLabel ( node , portType , portIndex ) {
var result ;
2017-02-06 14:23:42 +01:00
var nodePortLabels = ( portType === PORT _TYPE _INPUT ) ? node . inputLabels : node . outputLabels ;
if ( nodePortLabels && nodePortLabels [ portIndex ] ) {
return nodePortLabels [ portIndex ] ;
}
2017-01-27 19:11:25 +01:00
var portLabels = ( portType === PORT _TYPE _INPUT ) ? node . _def . inputLabels : node . _def . outputLabels ;
if ( typeof portLabels === 'string' ) {
result = portLabels ;
} else if ( typeof portLabels === 'function' ) {
try {
result = portLabels . call ( node , portIndex ) ;
} catch ( err ) {
console . log ( "Definition error: " + node . type + "." + ( ( portType === PORT _TYPE _INPUT ) ? "inputLabels" : "outputLabels" ) , err ) ;
result = null ;
}
} else if ( $ . isArray ( portLabels ) ) {
result = portLabels [ portIndex ] ;
}
return result ;
}
2018-10-03 16:40:05 +02:00
function showTooltip ( x , y , content , direction ) {
2019-05-01 23:41:20 +02:00
var tooltip = eventLayer . append ( "g" )
2018-10-03 16:40:05 +02:00
. attr ( "transform" , "translate(" + x + "," + y + ")" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-port-tooltip" ) ;
2018-10-03 16:40:05 +02:00
2020-05-19 18:53:20 +02:00
// First check for a user-provided newline - "\\n"
var newContent = content . split ( /\\n/ , 1 ) [ 0 ] ;
if ( newContent . length !== content . length ) {
content = newContent + "..." ;
}
2018-10-03 16:40:05 +02:00
var lines = content . split ( "\n" ) ;
2019-05-16 15:42:21 +02:00
var labelWidth = 6 ;
var labelHeight = 12 ;
2018-10-03 16:40:05 +02:00
var labelHeights = [ ] ;
2019-05-08 20:05:30 +02:00
var lineHeight = 0 ;
lines . forEach ( function ( l , i ) {
2019-05-15 14:54:29 +02:00
var labelDimensions = calculateTextDimensions ( l || " " , "red-ui-flow-port-tooltip-label" , 8 , 0 ) ;
2019-05-16 15:42:21 +02:00
labelWidth = Math . max ( labelWidth , labelDimensions [ 0 ] + 6 ) ;
labelHeights . push ( labelDimensions [ 1 ] ) ;
2019-05-08 20:05:30 +02:00
if ( i === 0 ) {
2019-05-16 15:42:21 +02:00
lineHeight = labelDimensions [ 1 ] ;
2019-05-08 20:05:30 +02:00
}
2019-05-16 15:42:21 +02:00
labelHeight += labelDimensions [ 1 ] ;
2018-10-03 16:40:05 +02:00
} ) ;
var labelWidth1 = ( labelWidth / 2 ) - 5 - 2 ;
var labelWidth2 = labelWidth - 4 ;
var labelHeight1 = ( labelHeight / 2 ) - 5 - 2 ;
var labelHeight2 = labelHeight - 4 ;
var path ;
var lx ;
2019-05-22 01:10:55 +02:00
var ly = - labelHeight / 2 ;
2018-10-03 16:40:05 +02:00
var anchor ;
if ( direction === "left" ) {
path = "M0 0 l -5 -5 v -" + ( labelHeight1 ) + " q 0 -2 -2 -2 h -" + labelWidth + " q -2 0 -2 2 v " + ( labelHeight2 ) + " q 0 2 2 2 h " + labelWidth + " q 2 0 2 -2 v -" + ( labelHeight1 ) + " l 5 -5" ;
2019-05-16 15:42:21 +02:00
lx = - 14 ;
2018-10-03 16:40:05 +02:00
anchor = "end" ;
} else if ( direction === "right" ) {
path = "M0 0 l 5 -5 v -" + ( labelHeight1 ) + " q 0 -2 2 -2 h " + labelWidth + " q 2 0 2 2 v " + ( labelHeight2 ) + " q 0 2 -2 2 h -" + labelWidth + " q -2 0 -2 -2 v -" + ( labelHeight1 ) + " l -5 -5"
2019-05-16 15:42:21 +02:00
lx = 14 ;
2018-10-03 16:40:05 +02:00
anchor = "start" ;
} else if ( direction === "top" ) {
path = "M0 0 l 5 -5 h " + ( labelWidth1 ) + " q 2 0 2 -2 v -" + labelHeight + " q 0 -2 -2 -2 h -" + ( labelWidth2 ) + " q -2 0 -2 2 v " + labelHeight + " q 0 2 2 2 h " + ( labelWidth1 ) + " l 5 5"
2019-05-16 15:42:21 +02:00
lx = - labelWidth / 2 + 6 ;
2019-05-22 01:10:55 +02:00
ly = - labelHeight - lineHeight + 12 ;
2019-05-08 20:05:30 +02:00
anchor = "start" ;
2018-10-03 16:40:05 +02:00
}
tooltip . append ( "path" ) . attr ( "d" , path ) ;
lines . forEach ( function ( l , i ) {
ly += labelHeights [ i ] ;
2019-05-15 15:17:44 +02:00
// tooltip.append("path").attr("d","M "+(lx-10)+" "+ly+" l 20 0 m -10 -5 l 0 10 ").attr('r',2).attr("stroke","#f00").attr("stroke-width","1").attr("fill","none")
2019-05-15 14:54:29 +02:00
tooltip . append ( "svg:text" ) . attr ( "class" , "red-ui-flow-port-tooltip-label" )
2018-10-03 16:40:05 +02:00
. attr ( "x" , lx )
. attr ( "y" , ly )
. attr ( "text-anchor" , anchor )
2019-05-08 20:05:30 +02:00
. text ( l || " " )
2018-10-03 16:40:05 +02:00
} ) ;
return tooltip ;
}
2018-12-18 11:57:53 +01:00
2017-01-26 16:38:25 +01:00
function portMouseOver ( port , d , portType , portIndex ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2017-01-26 16:38:25 +01:00
clearTimeout ( portLabelHoverTimeout ) ;
2018-12-18 11:57:53 +01:00
var active = ( mouse _mode != RED . state . JOINING && mouse _mode != RED . state . QUICK _JOINING ) || // Not currently joining - all ports active
(
drag _lines . length > 0 && // Currently joining
drag _lines [ 0 ] . portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT
(
! drag _lines [ 0 ] . virtualLink || // Not a link wire
( drag _lines [ 0 ] . node . type === 'link in' && d . type === 'link out' ) ||
( drag _lines [ 0 ] . node . type === 'link out' && d . type === 'link in' )
)
)
2017-03-06 16:41:19 +01:00
if ( active && ( ( portType === PORT _TYPE _INPUT && ( ( d . _def && d . _def . inputLabels ) || d . inputLabels ) ) || ( portType === PORT _TYPE _OUTPUT && ( ( d . _def && d . _def . outputLabels ) || d . outputLabels ) ) ) ) {
2017-01-26 16:38:25 +01:00
portLabelHoverTimeout = setTimeout ( function ( ) {
2017-01-27 19:11:25 +01:00
var tooltip = getPortLabel ( d , portType , portIndex ) ;
if ( ! tooltip ) {
return ;
}
2017-01-27 17:33:11 +01:00
var pos = getElementPosition ( port . node ( ) ) ;
2017-01-26 16:38:25 +01:00
portLabelHoverTimeout = null ;
2018-10-03 16:40:05 +02:00
portLabelHover = showTooltip (
( pos [ 0 ] + ( portType === PORT _TYPE _INPUT ? - 2 : 12 ) ) ,
( pos [ 1 ] + 5 ) ,
tooltip ,
portType === PORT _TYPE _INPUT ? "left" : "right"
) ;
2017-01-26 16:38:25 +01:00
} , 500 ) ;
}
2019-05-15 14:54:29 +02:00
port . classed ( "red-ui-flow-port-hovered" , active ) ;
2017-01-26 16:38:25 +01:00
}
function portMouseOut ( port , d , portType , portIndex ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2017-01-26 16:38:25 +01:00
clearTimeout ( portLabelHoverTimeout ) ;
if ( portLabelHover ) {
portLabelHover . remove ( ) ;
portLabelHover = null ;
}
2019-05-15 14:54:29 +02:00
port . classed ( "red-ui-flow-port-hovered" , false ) ;
2017-01-26 16:38:25 +01:00
}
2013-09-05 16:02:48 +02:00
function nodeMouseUp ( d ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "nodeMouseUp" , mouse _mode , d ) ; }
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2014-07-27 23:08:27 +02:00
if ( dblClickPrimed && mousedown _node == d && clickElapsed > 0 && clickElapsed < 750 ) {
2014-02-25 00:35:11 +01:00
mouse _mode = RED . state . DEFAULT ;
if ( d . type != "subflow" ) {
RED . editor . edit ( d ) ;
} else {
RED . editor . editSubflow ( activeSubflow ) ;
}
2014-05-06 11:14:18 +02:00
clickElapsed = 0 ;
2014-05-24 02:51:51 +02:00
d3 . event . stopPropagation ( ) ;
2014-05-06 00:28:24 +02:00
return ;
}
2020-03-03 20:04:32 +01:00
if ( mouse _mode === RED . state . MOVING ) {
// Moving primed, but not active.
if ( ! groupNodeSelectPrimed && ! d . selected && d . g && RED . nodes . group ( d . g ) . selected ) {
clearSelection ( ) ;
selectGroup ( RED . nodes . group ( d . g ) , false ) ;
2020-03-26 16:27:34 +01:00
enterActiveGroup ( RED . nodes . group ( d . g ) )
2020-03-03 20:04:32 +01:00
mousedown _node . selected = true ;
moving _set . push ( { n : mousedown _node } ) ;
var mouse = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
mouse [ 0 ] += d . x - d . w / 2 ;
mouse [ 1 ] += d . y - d . h / 2 ;
prepareDrag ( mouse ) ;
2020-03-16 12:16:18 +01:00
updateSelection ( ) ;
2020-03-03 20:04:32 +01:00
return ;
}
}
groupNodeSelectPrimed = false ;
2014-02-25 00:35:11 +01:00
var direction = d . _def ? ( d . inputs > 0 ? 1 : 0 ) : ( d . direction == "in" ? 0 : 1 )
2018-12-18 22:45:33 +01:00
var wasJoining = false ;
2018-12-18 11:57:53 +01:00
if ( mouse _mode === RED . state . JOINING || mouse _mode === RED . state . QUICK _JOINING ) {
2018-12-18 22:45:33 +01:00
wasJoining = true ;
2018-12-18 11:57:53 +01:00
if ( drag _lines . length > 0 ) {
if ( drag _lines [ 0 ] . virtualLink ) {
if ( d . type === 'link in' ) {
direction = 1 ;
} else if ( d . type === 'link out' ) {
direction = 0 ;
}
}
}
}
2018-12-18 22:45:33 +01:00
2014-02-25 00:35:11 +01:00
portMouseUp ( d , direction , 0 ) ;
2018-12-18 22:45:33 +01:00
if ( wasJoining ) {
2019-05-15 14:54:29 +02:00
d3 . selectAll ( ".red-ui-flow-port-hovered" ) . classed ( "red-ui-flow-port-hovered" , false ) ;
2018-12-18 22:45:33 +01:00
}
2013-09-05 16:02:48 +02:00
}
2020-03-03 20:04:32 +01:00
function prepareDrag ( mouse ) {
mouse _mode = RED . state . MOVING ;
// Called when moving_set should be prepared to be dragged
for ( i = 0 ; i < moving _set . length ; i ++ ) {
moving _set [ i ] . ox = moving _set [ i ] . n . x ;
moving _set [ i ] . oy = moving _set [ i ] . n . y ;
moving _set [ i ] . dx = moving _set [ i ] . n . x - mouse [ 0 ] ;
moving _set [ i ] . dy = moving _set [ i ] . n . y - mouse [ 1 ] ;
}
2020-03-26 16:27:34 +01:00
2020-03-03 20:04:32 +01:00
mouse _offset = d3 . mouse ( document . body ) ;
if ( isNaN ( mouse _offset [ 0 ] ) ) {
mouse _offset = d3 . touches ( document . body ) [ 0 ] ;
}
}
2013-09-05 16:02:48 +02:00
function nodeMouseDown ( d ) {
2020-06-05 16:48:02 +02:00
if ( RED . view . DEBUG ) { console . warn ( "nodeMouseDown" , mouse _mode , d ) ; }
2015-03-02 23:55:34 +01:00
focusView ( ) ;
2018-06-25 14:36:58 +02:00
if ( d3 . event . button === 1 ) {
return ;
}
2014-05-15 23:49:07 +02:00
//var touch0 = d3.event;
//var pos = [touch0.pageX,touch0.pageY];
//RED.touch.radialMenu.show(d3.select(this),pos);
2013-09-05 16:02:48 +02:00
if ( mouse _mode == RED . state . IMPORT _DRAGGING ) {
2016-12-04 23:59:43 +01:00
RED . keyboard . remove ( "escape" ) ;
2016-03-17 12:12:45 +01:00
if ( activeSpliceLink ) {
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
var spliceLink = d3 . select ( activeSpliceLink ) . data ( ) [ 0 ] ;
RED . nodes . removeLink ( spliceLink ) ;
var link1 = {
source : spliceLink . source ,
sourcePort : spliceLink . sourcePort ,
target : moving _set [ 0 ] . n
} ;
var link2 = {
source : moving _set [ 0 ] . n ,
sourcePort : 0 ,
target : spliceLink . target
} ;
RED . nodes . addLink ( link1 ) ;
RED . nodes . addLink ( link2 ) ;
var historyEvent = RED . history . peek ( ) ;
historyEvent . links = [ link1 , link2 ] ;
historyEvent . removedLinks = [ spliceLink ] ;
updateActiveNodes ( ) ;
}
2013-09-05 16:02:48 +02:00
updateSelection ( ) ;
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2013-09-05 16:02:48 +02:00
redraw ( ) ;
resetMouseVars ( ) ;
d3 . event . stopPropagation ( ) ;
return ;
2016-11-06 01:13:02 +01:00
} else if ( mouse _mode == RED . state . QUICK _JOINING ) {
2016-11-09 14:17:26 +01:00
d3 . event . stopPropagation ( ) ;
2016-11-06 01:13:02 +01:00
return ;
2019-05-23 17:39:06 +02:00
} else if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
2019-05-24 00:38:42 +02:00
if ( selectNodesOptions . single ) {
selectNodesOptions . done ( d ) ;
return ;
}
2019-05-23 17:39:06 +02:00
if ( d . selected ) {
d . selected = false ;
for ( i = 0 ; i < moving _set . length ; i += 1 ) {
if ( moving _set [ i ] . n === d ) {
moving _set . splice ( i , 1 ) ;
break ;
}
}
} else {
if ( ! selectNodesOptions . filter || selectNodesOptions . filter ( d ) ) {
d . selected = true ;
moving _set . push ( { n : d } ) ;
}
}
d . dirty = true ;
redraw ( ) ;
// if (selectNodesOptions && selectNodesOptions.onselect) {
// selectNodesOptions.onselect(moving_set.map(function(n) { return n.n;}))
// }
return ;
2013-09-05 16:02:48 +02:00
}
2020-03-03 20:04:32 +01:00
2013-09-05 16:02:48 +02:00
mousedown _node = d ;
2020-03-03 20:04:32 +01:00
2014-05-06 00:28:24 +02:00
var now = Date . now ( ) ;
clickElapsed = now - clickTime ;
clickTime = now ;
2014-05-14 15:22:28 +02:00
2018-06-12 16:34:08 +02:00
dblClickPrimed = ( lastClickNode == mousedown _node &&
2018-09-14 12:09:56 +02:00
d3 . event . button === 0 &&
2018-06-12 16:34:08 +02:00
! d3 . event . shiftKey && ! d3 . event . metaKey && ! d3 . event . altKey && ! d3 . event . ctrlKey ) ;
2020-03-03 20:04:32 +01:00
lastClickNode = mousedown _node ;
2014-10-09 11:05:45 +02:00
2014-08-08 01:01:35 +02:00
var i ;
2014-10-09 11:05:45 +02:00
2020-03-03 20:04:32 +01:00
if ( ! d . selected && d . g /*&& !RED.nodes.group(d.g).selected*/ ) {
var nodeGroup = RED . nodes . group ( d . g ) ;
if ( nodeGroup !== activeGroup && ( d3 . event . ctrlKey || d3 . event . metaKey ) ) {
// Clicked on a node in a non-active group with ctrl pressed
// - exit active group
// - toggle the select state of the group
exitActiveGroup ( ) ;
groupNodeSelectPrimed = true ;
if ( nodeGroup . selected ) {
deselectGroup ( nodeGroup ) ;
} else {
selectGroup ( nodeGroup , true ) ;
}
} else if ( nodeGroup === activeGroup ) {
// Clicked on a node in the active group
if ( ! d3 . event . ctrlKey && ! d3 . event . metaKey ) {
// Ctrl not pressed so clear selection
deselectGroup ( nodeGroup ) ;
selectGroup ( nodeGroup , false ) ;
}
// Select this node
mousedown _node . selected = true ;
moving _set . push ( { n : mousedown _node } ) ;
} else {
// Clicked on a node in a group
// - if this group is not selected, clear current selection
// and select this group
// - if this group is not the active group, exit the active group
// and select the group
// - if this group is the active group, keep it active and
// change node selection
// Set groupNodeSelectPrimed to true as this is a (de)select of the
// group and NOT meant to trigger going into the group - see nodeMouseUp
groupNodeSelectPrimed = ! nodeGroup . selected ;
var ag = activeGroup ;
if ( ! nodeGroup . selected ) {
clearSelection ( ) ;
}
if ( ag ) {
if ( ag !== nodeGroup ) {
ag . active = false ;
ag . dirty = true ;
} else {
activeGroup = nodeGroup ;
activeGroup . active = true ;
}
} else {
dblClickPrimed = false ;
}
selectGroup ( nodeGroup , ! activeGroup ) ;
if ( activeGroup ) {
mousedown _node . selected = true ;
moving _set . push ( { n : mousedown _node } ) ;
}
}
if ( d3 . event . button != 2 ) {
var mouse = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
mouse [ 0 ] += d . x - d . w / 2 ;
mouse [ 1 ] += d . y - d . h / 2 ;
prepareDrag ( mouse ) ;
}
} else if ( d . selected && ( d3 . event . ctrlKey || d3 . event . metaKey ) ) {
2016-01-07 16:09:14 +01:00
mousedown _node . selected = false ;
2014-08-08 01:01:35 +02:00
for ( i = 0 ; i < moving _set . length ; i += 1 ) {
2016-01-07 16:09:14 +01:00
if ( moving _set [ i ] . n === mousedown _node ) {
2013-09-05 16:02:48 +02:00
moving _set . splice ( i , 1 ) ;
break ;
}
}
} else {
2020-03-03 20:04:32 +01:00
// if (d.g && !RED.nodes.group(d.g).selected) {
// selectGroup(RED.nodes.group(d.g), false);
// }
// if (!d.selected && d.g) {
// if (!RED.nodes.group(d.g).selected) {// && !RED.nodes.group(d.g).selected) {
// clearSelection();
// selectGroup(RED.nodes.group(d.g));
// d.selected = true;
// console.log(d.id,"Setting selected")
// d.gSelected = true;
// }
// } else
2013-09-05 16:02:48 +02:00
if ( d3 . event . shiftKey ) {
clearSelection ( ) ;
var cnodes = RED . nodes . getAllFlowNodes ( mousedown _node ) ;
2014-08-08 01:01:35 +02:00
for ( var n = 0 ; n < cnodes . length ; n ++ ) {
cnodes [ n ] . selected = true ;
cnodes [ n ] . dirty = true ;
moving _set . push ( { n : cnodes [ n ] } ) ;
2013-09-05 16:02:48 +02:00
}
} else if ( ! d . selected ) {
2016-01-07 16:09:14 +01:00
if ( ! d3 . event . ctrlKey && ! d3 . event . metaKey ) {
2013-09-05 16:02:48 +02:00
clearSelection ( ) ;
2020-03-03 20:04:32 +01:00
} else {
exitActiveGroup ( ) ;
2013-09-05 16:02:48 +02:00
}
mousedown _node . selected = true ;
moving _set . push ( { n : mousedown _node } ) ;
}
selected _link = null ;
if ( d3 . event . button != 2 ) {
mouse _mode = RED . state . MOVING ;
var mouse = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
mouse [ 0 ] += d . x - d . w / 2 ;
mouse [ 1 ] += d . y - d . h / 2 ;
2020-03-03 20:04:32 +01:00
prepareDrag ( mouse ) ;
2013-09-05 16:02:48 +02:00
}
}
d . dirty = true ;
updateSelection ( ) ;
redraw ( ) ;
d3 . event . stopPropagation ( ) ;
}
2013-11-19 09:48:44 +01:00
2020-03-03 20:04:32 +01:00
function groupMouseUp ( g ) {
if ( dblClickPrimed && mousedown _group == g && clickElapsed > 0 && clickElapsed < 750 ) {
mouse _mode = RED . state . DEFAULT ;
RED . editor . editGroup ( g ) ;
d3 . event . stopPropagation ( ) ;
return ;
}
}
2020-03-04 22:48:38 +01:00
2020-03-03 20:04:32 +01:00
function groupMouseDown ( g ) {
var mouse = d3 . touches ( this . parentNode ) [ 0 ] || d3 . mouse ( this . parentNode ) ;
2020-03-04 22:48:38 +01:00
// if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) {
// return
// }
2020-03-03 20:04:32 +01:00
focusView ( ) ;
if ( d3 . event . button === 1 ) {
return ;
}
if ( mouse _mode == RED . state . IMPORT _DRAGGING ) {
RED . keyboard . remove ( "escape" ) ;
} else if ( mouse _mode == RED . state . QUICK _JOINING ) {
d3 . event . stopPropagation ( ) ;
return ;
} else if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
mousedown _group = g ;
var now = Date . now ( ) ;
clickElapsed = now - clickTime ;
clickTime = now ;
dblClickPrimed = (
lastClickNode == g &&
d3 . event . button === 0 &&
! d3 . event . shiftKey && ! d3 . event . metaKey && ! d3 . event . altKey && ! d3 . event . ctrlKey
) ;
lastClickNode = g ;
if ( g . selected && ( d3 . event . ctrlKey || d3 . event . metaKey ) ) {
if ( g === activeGroup ) {
exitActiveGroup ( ) ;
}
deselectGroup ( g ) ;
d3 . event . stopPropagation ( ) ;
} else {
if ( ! g . selected ) {
if ( ! d3 . event . ctrlKey && ! d3 . event . metaKey ) {
clearSelection ( ) ;
2013-09-05 16:02:48 +02:00
}
2020-03-24 15:05:09 +01:00
if ( activeGroup ) {
if ( ! RED . group . contains ( activeGroup , g ) ) {
// Clicked on a group that is outside the activeGroup
exitActiveGroup ( ) ;
} else {
}
2013-09-05 16:02:48 +02:00
}
2020-03-03 20:04:32 +01:00
selectGroup ( g , true ) ; //!wasSelected);
} else {
exitActiveGroup ( ) ;
}
if ( d3 . event . button != 2 ) {
var d = g . nodes [ 0 ] ;
prepareDrag ( mouse ) ;
2020-03-04 22:48:38 +01:00
mousedown _group . dx = mousedown _group . x - mouse [ 0 ] ;
mousedown _group . dy = mousedown _group . y - mouse [ 1 ] ;
2013-09-05 16:02:48 +02:00
}
}
2020-03-03 20:04:32 +01:00
2013-09-05 16:02:48 +02:00
updateSelection ( ) ;
redraw ( ) ;
d3 . event . stopPropagation ( ) ;
}
2013-11-19 09:48:44 +01:00
2020-03-03 20:04:32 +01:00
function selectGroup ( g , includeNodes ) {
if ( ! g . selected ) {
g . selected = true ;
g . dirty = true ;
}
2020-03-26 16:27:34 +01:00
moving _set . push ( { n : g } ) ;
2020-03-03 20:04:32 +01:00
if ( includeNodes ) {
var currentSet = new Set ( moving _set . map ( function ( n ) { return n . n } ) ) ;
2020-03-04 22:48:38 +01:00
var allNodes = RED . group . getNodes ( g , true ) ;
allNodes . forEach ( function ( n ) {
2020-03-03 20:04:32 +01:00
if ( ! currentSet . has ( n ) ) {
moving _set . push ( { n : n } )
// n.selected = true;
}
n . dirty = true ;
} )
}
}
2020-03-26 16:27:34 +01:00
function enterActiveGroup ( group ) {
if ( activeGroup ) {
exitActiveGroup ( ) ;
}
group . active = true ;
group . dirty = true ;
activeGroup = group ;
for ( var i = moving _set . length - 1 ; i >= 0 ; i -= 1 ) {
if ( moving _set [ i ] . n === group ) {
moving _set . splice ( i , 1 ) ;
break ;
}
}
}
2020-03-03 20:04:32 +01:00
function exitActiveGroup ( ) {
if ( activeGroup ) {
activeGroup . active = false ;
activeGroup . dirty = true ;
deselectGroup ( activeGroup ) ;
selectGroup ( activeGroup , true ) ;
activeGroup = null ;
}
}
function deselectGroup ( g ) {
if ( g . selected ) {
g . selected = false ;
g . dirty = true ;
}
var nodeSet = new Set ( g . nodes ) ;
2020-03-26 16:27:34 +01:00
nodeSet . add ( g ) ;
2020-03-03 20:04:32 +01:00
for ( var i = moving _set . length - 1 ; i >= 0 ; i -= 1 ) {
2020-03-26 16:27:34 +01:00
if ( nodeSet . has ( moving _set [ i ] . n ) || moving _set [ i ] . n === g ) {
2020-03-03 20:04:32 +01:00
moving _set [ i ] . n . selected = false ;
moving _set [ i ] . n . dirty = true ;
moving _set . splice ( i , 1 ) ;
}
}
}
function getGroupAt ( x , y ) {
2020-03-04 22:48:38 +01:00
var candidateGroups = { } ;
2020-03-03 20:04:32 +01:00
for ( var i = 0 ; i < activeGroups . length ; i ++ ) {
var g = activeGroups [ i ] ;
2020-03-04 22:48:38 +01:00
if ( x >= g . x && x <= g . x + g . w && y >= g . y && y <= g . y + g . h ) {
candidateGroups [ g . id ] = g ;
2020-03-03 20:04:32 +01:00
}
}
2020-03-04 22:48:38 +01:00
var ids = Object . keys ( candidateGroups ) ;
if ( ids . length > 1 ) {
ids . forEach ( function ( id ) {
if ( candidateGroups [ id ] && candidateGroups [ id ] . g ) {
delete candidateGroups [ candidateGroups [ id ] . g ]
}
} )
ids = Object . keys ( candidateGroups ) ;
}
if ( ids . length === 0 ) {
return null ;
} else {
2020-03-23 22:30:09 +01:00
return candidateGroups [ ids [ ids . length - 1 ] ]
2020-03-03 20:04:32 +01:00
}
}
2017-04-24 00:20:50 +02:00
function isButtonEnabled ( d ) {
var buttonEnabled = true ;
2018-11-08 18:04:36 +01:00
var ws = RED . nodes . workspace ( RED . workspaces . active ( ) ) ;
2019-06-17 23:46:34 +02:00
if ( ws && ! ws . disabled && ! d . d ) {
2018-11-08 18:04:36 +01:00
if ( d . _def . button . hasOwnProperty ( 'enabled' ) ) {
if ( typeof d . _def . button . enabled === "function" ) {
buttonEnabled = d . _def . button . enabled . call ( d ) ;
} else {
buttonEnabled = d . _def . button . enabled ;
}
2017-04-24 00:20:50 +02:00
}
2018-11-08 18:04:36 +01:00
} else {
buttonEnabled = false ;
2017-04-24 00:20:50 +02:00
}
return buttonEnabled ;
}
2013-10-28 17:45:31 +01:00
function nodeButtonClicked ( d ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
2020-04-27 12:03:43 +02:00
if ( d3 . event ) {
d3 . event . stopPropagation ( ) ;
}
2019-05-23 17:39:06 +02:00
return ;
}
2018-11-08 18:04:36 +01:00
var activeWorkspace = RED . workspaces . active ( ) ;
var ws = RED . nodes . workspace ( activeWorkspace ) ;
2019-06-17 23:46:34 +02:00
if ( ws && ! ws . disabled && ! d . d ) {
2015-03-12 14:58:53 +01:00
if ( d . _def . button . toggle ) {
d [ d . _def . button . toggle ] = ! d [ d . _def . button . toggle ] ;
d . dirty = true ;
}
if ( d . _def . button . onclick ) {
2016-03-01 22:58:57 +01:00
try {
d . _def . button . onclick . call ( d ) ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".onclick" , err ) ;
}
2015-03-12 14:58:53 +01:00
}
if ( d . dirty ) {
redraw ( ) ;
}
2015-03-19 12:19:44 +01:00
} else {
2018-11-08 18:04:36 +01:00
if ( activeSubflow ) {
RED . notify ( RED . _ ( "notification.warning" , { message : RED . _ ( "notification.warnings.nodeActionDisabledSubflow" ) } ) , "warning" ) ;
} else {
RED . notify ( RED . _ ( "notification.warning" , { message : RED . _ ( "notification.warnings.nodeActionDisabled" ) } ) , "warning" ) ;
}
2013-10-28 17:45:31 +01:00
}
2020-04-27 12:03:43 +02:00
if ( d3 . event ) {
d3 . event . preventDefault ( ) ;
}
2013-10-28 17:45:31 +01:00
}
2013-11-19 09:48:44 +01:00
2014-05-15 23:49:07 +02:00
function showTouchMenu ( obj , pos ) {
var mdn = mousedown _node ;
var options = [ ] ;
2015-11-06 17:46:07 +01:00
options . push ( { name : "delete" , disabled : ( moving _set . length === 0 && selected _link === null ) , onselect : function ( ) { deleteSelection ( ) ; } } ) ;
2014-08-08 01:01:35 +02:00
options . push ( { name : "cut" , disabled : ( moving _set . length === 0 ) , onselect : function ( ) { copySelection ( ) ; deleteSelection ( ) ; } } ) ;
options . push ( { name : "copy" , disabled : ( moving _set . length === 0 ) , onselect : function ( ) { copySelection ( ) ; } } ) ;
2016-09-23 23:02:12 +02:00
options . push ( { name : "paste" , disabled : ( clipboard . length === 0 ) , onselect : function ( ) { importNodes ( clipboard , false , true ) ; } } ) ;
2014-05-15 23:49:07 +02:00
options . push ( { name : "edit" , disabled : ( moving _set . length != 1 ) , onselect : function ( ) { RED . editor . edit ( mdn ) ; } } ) ;
options . push ( { name : "select" , onselect : function ( ) { selectAll ( ) ; } } ) ;
2014-08-08 01:01:35 +02:00
options . push ( { name : "undo" , disabled : ( RED . history . depth ( ) === 0 ) , onselect : function ( ) { RED . history . pop ( ) ; } } ) ;
2020-06-05 16:48:02 +02:00
options . push ( { name : "add" , onselect : function ( ) {
chartPos = chart . offset ( ) ;
showQuickAddDialog ( [ pos [ 0 ] - chartPos . left + chart . scrollLeft ( ) , pos [ 1 ] - chartPos . top + chart . scrollTop ( ) ] , undefined , undefined , true )
} } ) ;
2014-10-09 11:05:45 +02:00
2014-05-15 23:49:07 +02:00
RED . touch . radialMenu . show ( obj , pos , options ) ;
resetMouseVars ( ) ;
}
2018-09-26 02:16:15 +02:00
2018-09-28 10:07:55 +02:00
function createIconAttributes ( iconUrl , icon _group , d ) {
var fontAwesomeUnicode = null ;
2018-09-26 02:16:15 +02:00
if ( iconUrl . indexOf ( "font-awesome/" ) === 0 ) {
var iconName = iconUrl . substr ( 13 ) ;
var fontAwesomeUnicode = RED . nodes . fontAwesome . getIconUnicode ( iconName ) ;
2018-09-28 10:07:55 +02:00
if ( ! fontAwesomeUnicode ) {
2018-09-26 02:16:15 +02:00
var iconPath = RED . utils . getDefaultNodeIcon ( d . _def , d ) ;
2018-09-28 10:07:55 +02:00
iconUrl = RED . settings . apiRootUrl + "icons/" + iconPath . module + "/" + iconPath . file ;
2018-09-26 02:16:15 +02:00
}
}
2018-09-28 10:07:55 +02:00
if ( fontAwesomeUnicode ) {
// Since Node-RED workspace uses SVG, i tag cannot be used for font-awesome icon.
// On SVG, use text tag as an alternative.
icon _group . append ( "text" )
. attr ( "xlink:href" , iconUrl )
. attr ( "class" , "fa-lg" )
. attr ( "x" , 15 )
. text ( fontAwesomeUnicode ) ;
} else {
var icon = icon _group . append ( "image" )
. attr ( "xlink:href" , iconUrl )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-icon" )
2018-09-28 10:07:55 +02:00
. attr ( "x" , 0 )
. attr ( "width" , "30" )
2019-06-21 13:36:20 +02:00
. attr ( "height" , "30" )
. style ( "display" , "none" ) ;
2018-09-26 02:16:15 +02:00
var img = new Image ( ) ;
img . src = iconUrl ;
img . onload = function ( ) {
2019-10-02 10:39:38 +02:00
if ( ! iconUrl . match ( /\.svg$/ ) ) {
var largestEdge = Math . max ( img . width , img . height ) ;
var scaleFactor = 1 ;
if ( largestEdge > 30 ) {
scaleFactor = 30 / largestEdge ;
}
var width = img . width * scaleFactor ;
var height = img . height * scaleFactor ;
icon . attr ( "width" , width ) ;
icon . attr ( "height" , height ) ;
icon . attr ( "x" , 15 - width / 2 ) ;
}
2018-09-26 02:16:15 +02:00
icon . attr ( "xlink:href" , iconUrl ) ;
icon . style ( "display" , null ) ;
//if ("right" == d._def.align) {
// icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);});
// icon_shade.attr("x",function(d){return d.w-30});
// icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);});
//}
}
}
}
2013-09-05 16:02:48 +02:00
function redraw ( ) {
2020-03-23 22:30:52 +01:00
requestAnimationFrame ( _redraw ) ;
}
function _redraw ( ) {
2019-05-01 23:41:20 +02:00
eventLayer . attr ( "transform" , "scale(" + scaleFactor + ")" ) ;
2013-09-05 16:02:48 +02:00
outer . attr ( "width" , space _width * scaleFactor ) . attr ( "height" , space _height * scaleFactor ) ;
2015-03-12 14:26:31 +01:00
// Don't bother redrawing nodes if we're drawing links
2018-12-18 11:57:53 +01:00
if ( showAllLinkPorts !== - 1 || mouse _mode != RED . state . JOINING ) {
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
var dirtyNodes = { } ;
2015-07-01 00:42:03 +02:00
2014-02-25 00:35:11 +01:00
if ( activeSubflow ) {
2019-05-15 14:54:29 +02:00
var subflowOutputs = nodeLayer . selectAll ( ".red-ui-flow-subflow-port-output" ) . data ( activeSubflow . out , function ( d , i ) { return d . id ; } ) ;
2014-02-25 00:35:11 +01:00
subflowOutputs . exit ( ) . remove ( ) ;
2019-05-15 14:54:29 +02:00
var outGroup = subflowOutputs . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "red-ui-flow-node red-ui-flow-subflow-port-output" ) . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - 20 ) + "," + ( d . y - 20 ) + ")" } ) ;
2014-02-25 00:35:11 +01:00
outGroup . each ( function ( d , i ) {
d . w = 40 ;
d . h = 40 ;
} ) ;
2019-05-15 14:54:29 +02:00
outGroup . append ( "rect" ) . attr ( "class" , "red-ui-flow-subflow-port" ) . attr ( "rx" , 8 ) . attr ( "ry" , 8 ) . attr ( "width" , 40 ) . attr ( "height" , 40 )
2015-07-01 00:42:03 +02:00
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
2014-02-25 00:35:11 +01:00
. on ( "mouseup" , nodeMouseUp )
. on ( "mousedown" , nodeMouseDown )
. on ( "touchstart" , function ( d ) {
var obj = d3 . select ( this ) ;
var touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
startTouchDistance = 0 ;
touchStartTime = setTimeout ( function ( ) {
showTouchMenu ( obj , pos ) ;
} , touchLongPressTimeout ) ;
2015-07-01 00:42:03 +02:00
nodeMouseDown . call ( this , d )
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-02-25 00:35:11 +01:00
} )
. on ( "touchend" , function ( d ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-02-25 00:35:11 +01:00
} ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
outGroup . append ( "g" ) . attr ( 'transform' , 'translate(-5,15)' ) . append ( "rect" ) . attr ( "class" , "red-ui-flow-port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
2017-01-26 16:38:25 +01:00
. on ( "mousedown" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseup" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchend" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseover" , function ( d ) { portMouseOver ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } )
. on ( "mouseout" , function ( d ) { portMouseOut ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } ) ;
2014-02-25 00:35:11 +01:00
2019-09-10 15:31:17 +02:00
outGroup . append ( "svg:text" ) . attr ( "class" , "red-ui-flow-port-label" ) . attr ( "x" , 20 ) . attr ( "y" , 12 ) . style ( "font-size" , "10px" ) . text ( "output" ) ;
outGroup . append ( "svg:text" ) . attr ( "class" , "red-ui-flow-port-label red-ui-flow-port-index" ) . attr ( "x" , 20 ) . attr ( "y" , 28 ) . text ( function ( d , i ) { return i + 1 } ) ;
2014-02-25 00:35:11 +01:00
2019-05-15 14:54:29 +02:00
var subflowInputs = nodeLayer . selectAll ( ".red-ui-flow-subflow-port-input" ) . data ( activeSubflow . in , function ( d , i ) { return d . id ; } ) ;
2014-02-25 00:35:11 +01:00
subflowInputs . exit ( ) . remove ( ) ;
2019-05-15 14:54:29 +02:00
var inGroup = subflowInputs . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "red-ui-flow-node red-ui-flow-subflow-port-input" ) . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - 20 ) + "," + ( d . y - 20 ) + ")" } ) ;
2014-02-25 00:35:11 +01:00
inGroup . each ( function ( d , i ) {
d . w = 40 ;
d . h = 40 ;
} ) ;
2019-05-15 14:54:29 +02:00
inGroup . append ( "rect" ) . attr ( "class" , "red-ui-flow-subflow-port" ) . attr ( "rx" , 8 ) . attr ( "ry" , 8 ) . attr ( "width" , 40 ) . attr ( "height" , 40 )
2015-07-01 00:42:03 +02:00
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
2014-02-25 00:35:11 +01:00
. on ( "mouseup" , nodeMouseUp )
. on ( "mousedown" , nodeMouseDown )
. on ( "touchstart" , function ( d ) {
var obj = d3 . select ( this ) ;
var touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
startTouchDistance = 0 ;
touchStartTime = setTimeout ( function ( ) {
showTouchMenu ( obj , pos ) ;
} , touchLongPressTimeout ) ;
2015-07-01 00:42:03 +02:00
nodeMouseDown . call ( this , d )
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-02-25 00:35:11 +01:00
} )
. on ( "touchend" , function ( d ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-02-25 00:35:11 +01:00
} ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
inGroup . append ( "g" ) . attr ( 'transform' , 'translate(35,15)' ) . append ( "rect" ) . attr ( "class" , "red-ui-flow-port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
2017-01-26 16:38:25 +01:00
. on ( "mousedown" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _OUTPUT , i ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _OUTPUT , i ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseup" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _OUTPUT , i ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchend" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _OUTPUT , i ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseover" , function ( d ) { portMouseOver ( d3 . select ( this ) , d , PORT _TYPE _OUTPUT , 0 ) ; } )
. on ( "mouseout" , function ( d ) { portMouseOut ( d3 . select ( this ) , d , PORT _TYPE _OUTPUT , 0 ) ; } ) ;
2019-05-15 14:54:29 +02:00
inGroup . append ( "svg:text" ) . attr ( "class" , "red-ui-flow-port-label" ) . attr ( "x" , 18 ) . attr ( "y" , 20 ) . style ( "font-size" , "10px" ) . text ( "input" ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
var subflowStatus = nodeLayer . selectAll ( ".red-ui-flow-subflow-port-status" ) . data ( activeSubflow . status ? [ activeSubflow . status ] : [ ] , function ( d , i ) { return d . id ; } ) ;
2019-02-02 00:44:50 +01:00
subflowStatus . exit ( ) . remove ( ) ;
2019-05-15 14:54:29 +02:00
var statusGroup = subflowStatus . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "red-ui-flow-node red-ui-flow-subflow-port-status" ) . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - 20 ) + "," + ( d . y - 20 ) + ")" } ) ;
2019-02-02 00:44:50 +01:00
statusGroup . each ( function ( d , i ) {
d . w = 40 ;
d . h = 40 ;
} ) ;
2019-05-15 14:54:29 +02:00
statusGroup . append ( "rect" ) . attr ( "class" , "red-ui-flow-subflow-port" ) . attr ( "rx" , 8 ) . attr ( "ry" , 8 ) . attr ( "width" , 40 ) . attr ( "height" , 40 )
2019-02-02 00:44:50 +01:00
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
. on ( "mouseup" , nodeMouseUp )
. on ( "mousedown" , nodeMouseDown )
. on ( "touchstart" , function ( d ) {
var obj = d3 . select ( this ) ;
var touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
startTouchDistance = 0 ;
touchStartTime = setTimeout ( function ( ) {
showTouchMenu ( obj , pos ) ;
} , touchLongPressTimeout ) ;
nodeMouseDown . call ( this , d )
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2019-02-02 00:44:50 +01:00
} )
. on ( "touchend" , function ( d ) {
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2019-02-02 00:44:50 +01:00
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
} ) ;
2019-05-15 14:54:29 +02:00
statusGroup . append ( "g" ) . attr ( 'transform' , 'translate(-5,15)' ) . append ( "rect" ) . attr ( "class" , "red-ui-flow-port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
2019-02-02 00:44:50 +01:00
. on ( "mousedown" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , function ( d , i ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2019-02-02 00:44:50 +01:00
. on ( "mouseup" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchend" , function ( d , i ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2019-02-02 00:44:50 +01:00
. on ( "mouseover" , function ( d ) { portMouseOver ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } )
. on ( "mouseout" , function ( d ) { portMouseOut ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } ) ;
2019-05-15 14:54:29 +02:00
statusGroup . append ( "svg:text" ) . attr ( "class" , "red-ui-flow-port-label" ) . attr ( "x" , 22 ) . attr ( "y" , 20 ) . style ( "font-size" , "10px" ) . text ( "status" ) ;
2019-02-02 00:44:50 +01:00
2014-11-13 00:51:42 +01:00
subflowOutputs . each ( function ( d , i ) {
2014-02-25 00:35:11 +01:00
if ( d . dirty ) {
var output = d3 . select ( this ) ;
2019-05-23 17:39:06 +02:00
output . classed ( "red-ui-flow-node-selected" , function ( d ) { return d . selected ; } )
2019-05-15 14:54:29 +02:00
output . selectAll ( ".red-ui-flow-port-index" ) . text ( function ( d ) { return d . i + 1 } ) ;
2014-02-25 00:35:11 +01:00
output . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ; } ) ;
2015-03-12 14:26:31 +01:00
dirtyNodes [ d . id ] = d ;
2014-02-25 00:35:11 +01:00
d . dirty = false ;
}
} ) ;
2014-11-13 00:51:42 +01:00
subflowInputs . each ( function ( d , i ) {
2014-02-25 00:35:11 +01:00
if ( d . dirty ) {
var input = d3 . select ( this ) ;
2019-05-23 17:39:06 +02:00
input . classed ( "red-ui-flow-node-selected" , function ( d ) { return d . selected ; } )
2014-02-25 00:35:11 +01:00
input . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ; } ) ;
2015-03-12 14:26:31 +01:00
dirtyNodes [ d . id ] = d ;
2014-02-25 00:35:11 +01:00
d . dirty = false ;
}
} ) ;
2019-02-02 00:44:50 +01:00
subflowStatus . each ( function ( d , i ) {
if ( d . dirty ) {
var output = d3 . select ( this ) ;
2019-05-23 17:39:06 +02:00
output . classed ( "red-ui-flow-node-selected" , function ( d ) { return d . selected ; } )
2019-05-15 14:54:29 +02:00
output . selectAll ( ".red-ui-flow-port-index" ) . text ( function ( d ) { return d . i + 1 } ) ;
2019-02-02 00:44:50 +01:00
output . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ; } ) ;
dirtyNodes [ d . id ] = d ;
d . dirty = false ;
}
} ) ;
2014-02-25 00:35:11 +01:00
} else {
2019-05-15 14:54:29 +02:00
nodeLayer . selectAll ( ".red-ui-flow-subflow-port-output" ) . remove ( ) ;
nodeLayer . selectAll ( ".red-ui-flow-subflow-port-input" ) . remove ( ) ;
nodeLayer . selectAll ( ".red-ui-flow-subflow-port-status" ) . remove ( ) ;
2014-02-25 00:35:11 +01:00
}
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
var node = nodeLayer . selectAll ( ".red-ui-flow-node-group" ) . data ( activeNodes , function ( d ) { return d . id } ) ;
2013-09-05 16:02:48 +02:00
node . exit ( ) . remove ( ) ;
2016-03-22 14:58:24 +01:00
var nodeEnter = node . enter ( ) . insert ( "svg:g" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node red-ui-flow-node-group" )
2020-03-23 22:30:52 +01:00
. classed ( "red-ui-flow-subflow" , activeSubflow != null ) ;
2016-03-22 14:58:24 +01:00
2013-09-05 16:02:48 +02:00
nodeEnter . each ( function ( d , i ) {
var node = d3 . select ( this ) ;
2018-12-18 11:57:53 +01:00
var isLink = ( d . type === "link in" || d . type === "link out" )
var hideLabel = d . hasOwnProperty ( 'l' ) ? ! d . l : isLink ;
2014-05-11 00:33:02 +02:00
node . attr ( "id" , d . id ) ;
2019-09-03 11:43:46 +02:00
var labelWidth = calculateTextWidth ( RED . utils . getNodeLabel ( d ) , "red-ui-flow-node-label" , 50 ) ;
2019-05-23 17:39:06 +02:00
if ( d . resize || d . w === undefined ) {
2019-05-22 01:04:19 +02:00
if ( hideLabel ) {
2019-09-03 11:43:46 +02:00
d . w = node _height ;
2019-05-22 01:04:19 +02:00
} else {
2019-09-03 11:43:46 +02:00
d . w = Math . max ( node _width , 20 * ( Math . ceil ( ( labelWidth + ( d . _def . inputs > 0 ? 7 : 0 ) ) / 20 ) ) ) ;
2019-05-22 01:04:19 +02:00
}
2016-05-17 10:16:58 +02:00
}
2019-07-26 04:36:22 +02:00
if ( hideLabel ) {
2019-09-03 11:43:46 +02:00
d . h = Math . max ( node _height , ( d . outputs || 0 ) * 15 ) ;
2019-07-26 04:36:22 +02:00
} else {
2019-09-03 11:43:46 +02:00
d . h = Math . max ( 6 + 24 * separateTextByLineBreak . length , ( d . outputs || 0 ) * 15 , 30 ) ;
2019-07-26 04:36:22 +02:00
}
2019-05-15 14:54:29 +02:00
// if (d._def.badge) {
// var badge = node.append("svg:g").attr("class","node_badge_group");
// var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15);
// badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr("text-anchor","end").text(d._def.badge());
// if (d._def.onbadgeclick) {
// badgeRect.attr("cursor","pointer")
// .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();});
// }
// }
2014-05-14 15:22:28 +02:00
2013-09-05 16:02:48 +02:00
if ( d . _def . button ) {
2016-05-17 10:16:58 +02:00
var nodeButtonGroup = node . append ( "svg:g" )
2013-10-28 17:45:31 +01:00
. attr ( "transform" , function ( d ) { return "translate(" + ( ( d . _def . align == "right" ) ? 94 : - 25 ) + ",2)" ; } )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-button" ) ;
2016-05-17 10:16:58 +02:00
nodeButtonGroup . append ( "rect" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-button-background" )
2015-07-12 00:43:45 +02:00
. attr ( "rx" , 5 )
. attr ( "ry" , 5 )
2013-09-05 16:02:48 +02:00
. attr ( "width" , 32 )
2019-09-03 11:43:46 +02:00
. attr ( "height" , node _height - 4 ) ;
2016-05-17 10:16:58 +02:00
nodeButtonGroup . append ( "rect" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-button-button" )
2015-07-14 19:55:53 +02:00
. attr ( "x" , function ( d ) { return d . _def . align == "right" ? 11 : 5 } )
2013-10-28 17:45:31 +01:00
. attr ( "y" , 4 )
2015-07-12 00:43:45 +02:00
. attr ( "rx" , 4 )
. attr ( "ry" , 4 )
2013-09-05 16:02:48 +02:00
. attr ( "width" , 16 )
2019-09-03 11:43:46 +02:00
. attr ( "height" , node _height - 12 )
2018-07-27 23:05:28 +02:00
. attr ( "fill" , function ( d ) { return RED . utils . getNodeColor ( d . type , d . _def ) ; /*d._def.color;*/ } )
2013-09-05 16:02:48 +02:00
. attr ( "cursor" , "pointer" )
2017-04-24 00:20:50 +02:00
. on ( "mousedown" , function ( d ) { if ( ! lasso && isButtonEnabled ( d ) ) { focusView ( ) ; d3 . select ( this ) . attr ( "fill-opacity" , 0.2 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseup" , function ( d ) { if ( ! lasso && isButtonEnabled ( d ) ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseover" , function ( d ) { if ( ! lasso && isButtonEnabled ( d ) ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; } } )
. on ( "mouseout" , function ( d ) { if ( ! lasso && isButtonEnabled ( d ) ) {
2013-10-28 17:45:31 +01:00
var op = 1 ;
if ( d . _def . button . toggle ) {
op = d [ d . _def . button . toggle ] ? 1 : 0.2 ;
}
d3 . select ( this ) . attr ( "fill-opacity" , op ) ;
} } )
. on ( "click" , nodeButtonClicked )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , function ( d ) { nodeButtonClicked . call ( this , d ) ; d3 . event . preventDefault ( ) ; } )
2013-09-05 16:02:48 +02:00
}
var mainRect = node . append ( "rect" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node" )
2020-03-23 22:30:52 +01:00
. classed ( "red-ui-flow-node-unknown" , d . type == "unknown" )
2015-07-12 00:43:45 +02:00
. attr ( "rx" , 5 )
. attr ( "ry" , 5 )
2020-03-23 22:30:52 +01:00
. attr ( "fill" , RED . utils . getNodeColor ( d . type , d . _def ) )
2014-05-15 23:49:07 +02:00
. on ( "mouseup" , nodeMouseUp )
2013-09-05 16:02:48 +02:00
. on ( "mousedown" , nodeMouseDown )
2014-05-15 23:49:07 +02:00
. on ( "touchstart" , function ( d ) {
var obj = d3 . select ( this ) ;
var touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
startTouchDistance = 0 ;
touchStartTime = setTimeout ( function ( ) {
showTouchMenu ( obj , pos ) ;
} , touchLongPressTimeout ) ;
2014-10-09 11:05:45 +02:00
nodeMouseDown . call ( this , d )
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-05-15 23:49:07 +02:00
} )
. on ( "touchend" , function ( d ) {
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2014-05-15 23:49:07 +02:00
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
} )
2013-09-05 16:02:48 +02:00
. on ( "mouseover" , function ( d ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === 0 || mouse _mode === RED . state . SELECTING _NODE ) {
if ( mouse _mode === RED . state . SELECTING _NODE && selectNodesOptions && selectNodesOptions . filter ) {
if ( selectNodesOptions . filter ( d ) ) {
node . classed ( "red-ui-flow-node-hovered" , true ) ;
}
} else {
node . classed ( "red-ui-flow-node-hovered" , true ) ;
}
2018-10-03 16:40:05 +02:00
clearTimeout ( portLabelHoverTimeout ) ;
2018-10-08 17:25:11 +02:00
if ( d . hasOwnProperty ( 'l' ) ? ! d . l : ( d . type === "link in" || d . type === "link out" ) ) {
2018-10-03 16:40:05 +02:00
portLabelHoverTimeout = setTimeout ( function ( ) {
var tooltip ;
if ( d . _def . label ) {
tooltip = d . _def . label ;
try {
tooltip = ( typeof tooltip === "function" ? tooltip . call ( d ) : tooltip ) || "" ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".label" , err ) ;
tooltip = d . type ;
}
}
if ( tooltip !== "" ) {
var pos = getElementPosition ( node . node ( ) ) ;
portLabelHoverTimeout = null ;
portLabelHover = showTooltip (
( pos [ 0 ] + d . w / 2 ) ,
( pos [ 1 ] - 1 ) ,
tooltip ,
"top"
) ;
}
} , 500 ) ;
}
2018-12-18 22:45:33 +01:00
} else if ( mouse _mode === RED . state . JOINING || mouse _mode === RED . state . QUICK _JOINING ) {
if ( drag _lines . length > 0 ) {
var selectClass ;
var portType ;
if ( ( drag _lines [ 0 ] . virtualLink && drag _lines [ 0 ] . portType === PORT _TYPE _INPUT ) || drag _lines [ 0 ] . portType === PORT _TYPE _OUTPUT ) {
2019-05-15 14:54:29 +02:00
selectClass = ".red-ui-flow-port-input .red-ui-flow-port" ;
2018-12-18 22:45:33 +01:00
portType = PORT _TYPE _INPUT ;
} else {
2019-05-15 14:54:29 +02:00
selectClass = ".red-ui-flow-port-output .red-ui-flow-port" ;
2018-12-18 22:45:33 +01:00
portType = PORT _TYPE _OUTPUT ;
}
portMouseOver ( d3 . select ( this . parentNode ) . selectAll ( selectClass ) , d , portType , 0 ) ;
}
2016-11-07 22:25:09 +01:00
}
2013-09-05 16:02:48 +02:00
} )
. on ( "mouseout" , function ( d ) {
2019-05-23 17:39:06 +02:00
node . classed ( "red-ui-flow-node-hovered" , false ) ;
2018-10-03 16:40:05 +02:00
clearTimeout ( portLabelHoverTimeout ) ;
if ( portLabelHover ) {
portLabelHover . remove ( ) ;
portLabelHover = null ;
}
2018-12-18 22:45:33 +01:00
if ( mouse _mode === RED . state . JOINING || mouse _mode === RED . state . QUICK _JOINING ) {
if ( drag _lines . length > 0 ) {
var selectClass ;
var portType ;
if ( ( drag _lines [ 0 ] . virtualLink && drag _lines [ 0 ] . portType === PORT _TYPE _INPUT ) || drag _lines [ 0 ] . portType === PORT _TYPE _OUTPUT ) {
2019-05-15 14:54:29 +02:00
selectClass = ".red-ui-flow-port-input .red-ui-flow-port" ;
2018-12-18 22:45:33 +01:00
portType = PORT _TYPE _INPUT ;
} else {
2019-05-15 14:54:29 +02:00
selectClass = ".red-ui-flow-port-output .red-ui-flow-port" ;
2018-12-18 22:45:33 +01:00
portType = PORT _TYPE _OUTPUT ;
}
portMouseOut ( d3 . select ( this . parentNode ) . selectAll ( selectClass ) , d , portType , 0 ) ;
}
}
2013-09-05 16:02:48 +02:00
} ) ;
//node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none");
//node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none");
if ( d . _def . icon ) {
2017-01-18 14:06:22 +01:00
var icon _url = RED . utils . getNodeIcon ( d . _def , d ) ;
2014-06-04 23:43:44 +02:00
var icon _group = node . append ( "g" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-icon-group" )
2014-06-04 23:43:44 +02:00
. attr ( "x" , 0 ) . attr ( "y" , 0 ) ;
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
var icon _shade = icon _group . append ( "rect" )
. attr ( "x" , 0 ) . attr ( "y" , 0 )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-icon-shade" )
2014-06-04 23:43:44 +02:00
. attr ( "width" , "30" )
2020-03-23 22:30:52 +01:00
. attr ( "height" , Math . min ( 50 , d . h - 4 ) ) ;
2014-10-09 11:05:45 +02:00
2018-09-28 10:07:55 +02:00
createIconAttributes ( icon _url , icon _group , d ) ;
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
var icon _shade _border = icon _group . append ( "path" )
2020-03-23 22:30:52 +01:00
. attr ( "d" , "M 30 1 l 0 " + ( d . h - 2 ) )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-icon-shade-border" ) ;
2014-06-04 23:43:44 +02:00
if ( "right" == d . _def . align ) {
2019-05-15 14:54:29 +02:00
icon _group . attr ( "class" , "red-ui-flow-node-icon-group red-ui-flow-node-icon-group-" + d . _def . align ) ;
2020-03-23 22:30:52 +01:00
icon _shade _border . attr ( "d" , "M 0 1 l 0 " + ( d . h - 2 ) )
2019-05-15 14:54:29 +02:00
//icon.attr("class","red-ui-flow-node-icon red-ui-flow-node-icon-"+d._def.align);
//icon.attr("class","red-ui-flow-node-icon-shade red-ui-flow-node-icon-shade-"+d._def.align);
//icon.attr("class","red-ui-flow-node-icon-shade-border red-ui-flow-node-icon-shade-border-"+d._def.align);
2013-09-05 16:02:48 +02:00
}
2015-07-01 00:42:03 +02:00
2014-02-25 00:35:11 +01:00
//if (d.inputs > 0 && d._def.align == null) {
2014-06-04 23:43:44 +02:00
// icon_shade.attr("width",35);
// icon.attr("transform","translate(5,0)");
// icon_shade_border.attr("transform","translate(5,0)");
//}
//if (d._def.outputs > 0 && "right" == d._def.align) {
// icon_shade.attr("width",35); //icon.attr("x",5);
//}
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
//icon.style("pointer-events","none");
icon _group . style ( "pointer-events" , "none" ) ;
2013-09-05 16:02:48 +02:00
}
2019-09-03 11:43:46 +02:00
var labelLineNumber = ( separateTextByLineBreak . length == 0 ) ? 1 : separateTextByLineBreak . length ;
2019-09-27 12:17:17 +02:00
var labelId = d . id . replace ( "." , "-" ) ;
2019-09-03 11:43:46 +02:00
for ( var i = 0 ; i < labelLineNumber ; i ++ ) {
var text = node . append ( "svg:text" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-label" )
2019-09-27 12:17:17 +02:00
. attr ( "id" , "red-ui-flow-node-label-" + labelId + "-" + i )
2018-10-08 17:25:11 +02:00
. attr ( "x" , 38 )
2019-09-03 11:43:46 +02:00
. attr ( "dy" , ".3px" )
2018-10-08 17:25:11 +02:00
. attr ( "text-anchor" , "start" )
2019-05-08 14:26:48 +02:00
. classed ( "hide" , hideLabel ) ;
2018-10-08 17:25:11 +02:00
2019-09-03 11:43:46 +02:00
if ( d . _def . align ) {
text . attr ( "class" , "red-ui-flow-node-label red-ui-flow-node-label-" + d . _def . align ) ;
if ( d . _def . align === "right" ) {
2020-03-23 22:30:52 +01:00
text . attr ( "text-anchor" , "end" ) ;
2019-09-03 11:43:46 +02:00
}
2015-10-15 15:26:43 +02:00
}
2018-10-08 17:25:11 +02:00
}
2014-05-14 15:22:28 +02:00
2019-05-15 14:54:29 +02:00
var status = node . append ( "svg:g" ) . attr ( "class" , "red-ui-flow-node-status-group" ) . style ( "display" , "none" ) ;
var statusRect = status . append ( "rect" ) . attr ( "class" , "red-ui-flow-node-status" )
2018-10-08 17:25:11 +02:00
. attr ( "x" , 6 ) . attr ( "y" , 1 ) . attr ( "width" , 9 ) . attr ( "height" , 9 )
. attr ( "rx" , 2 ) . attr ( "ry" , 2 ) . attr ( "stroke-width" , "3" ) ;
var statusLabel = status . append ( "svg:text" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-node-status-label" )
2019-02-09 22:27:54 +01:00
. attr ( "x" , 20 ) . attr ( "y" , 10 ) ;
2014-05-14 15:22:28 +02:00
2019-05-15 14:54:29 +02:00
node . append ( "g" ) . attr ( "class" , "red-ui-flow-node-changed hide" ) . attr ( "transform" , "translate(20, -2)" ) . append ( "circle" ) . attr ( "r" , 5 ) ;
var nodeErrorButton = node . append ( "g" ) . attr ( "class" , "red-ui-flow-node-error hide" ) . attr ( "transform" , "translate(0, -2)" ) . append ( "path" ) . attr ( "d" , "M -5,4 l 10,0 -5,-8 z" ) ;
2019-05-16 15:42:21 +02:00
nodeErrorButton . on ( "mouseenter" , function ( ) {
if ( d . validationErrors && d . validationErrors . length > 0 ) {
clearTimeout ( portLabelHoverTimeout ) ;
portLabelHoverTimeout = setTimeout ( function ( ) {
var pos = getElementPosition ( nodeErrorButton . node ( ) ) ;
portLabelHoverTimeout = null ;
portLabelHover = showTooltip (
( pos [ 0 ] ) ,
( pos [ 1 ] ) ,
RED . _ ( "editor.errors.invalidProperties" ) + "\n - " + d . validationErrors . join ( "\n - " ) ,
"top"
) ;
} , 500 ) ;
}
} ) . on ( "mouseleave" , function ( ) {
clearTimeout ( portLabelHoverTimeout ) ;
if ( portLabelHover ) {
portLabelHover . remove ( ) ;
portLabelHover = null ;
}
} ) ;
2013-09-05 16:02:48 +02:00
} ) ;
node . each ( function ( d , i ) {
if ( d . dirty ) {
2018-12-18 11:57:53 +01:00
var isLink = ( d . type === "link in" || d . type === "link out" )
var hideLabel = d . hasOwnProperty ( 'l' ) ? ! d . l : isLink ;
2015-03-12 14:26:31 +01:00
dirtyNodes [ d . id ] = d ;
2013-11-15 18:46:57 +01:00
//if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
2019-09-03 11:43:46 +02:00
var labelLineNumber = ( d . h - 6 ) / 24 ;
var labelWidth = calculateTextWidth ( RED . utils . getNodeLabel ( d ) , "red-ui-flow-node-label" , 50 ) ;
2018-12-18 11:57:53 +01:00
if ( d . resize ) {
2016-01-07 15:39:01 +01:00
var ow = d . w ;
2018-12-18 11:57:53 +01:00
if ( hideLabel ) {
2019-09-03 11:43:46 +02:00
d . w = node _height ;
2018-10-08 17:25:11 +02:00
} else {
2019-09-03 11:43:46 +02:00
d . w = Math . max ( node _width , 20 * ( Math . ceil ( ( labelWidth + ( d . _def . inputs > 0 ? 7 : 0 ) ) / 20 ) ) ) ;
2018-10-08 17:25:11 +02:00
}
2019-05-15 14:54:29 +02:00
// d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) );
2016-01-07 15:39:01 +01:00
d . x += ( d . w - ow ) / 2 ;
2014-02-25 00:35:11 +01:00
d . resize = false ;
2013-09-05 16:02:48 +02:00
}
2019-07-26 04:36:22 +02:00
if ( hideLabel ) {
2019-09-03 11:43:46 +02:00
d . h = Math . max ( node _height , ( d . outputs || 0 ) * 15 ) ;
2019-07-26 04:36:22 +02:00
} else {
2019-09-03 11:43:46 +02:00
d . h = Math . max ( 6 + 24 * separateTextByLineBreak . length , ( d . outputs || 0 ) * 15 , 30 ) ;
2019-07-26 04:36:22 +02:00
}
2013-09-05 16:02:48 +02:00
var thisNode = d3 . select ( this ) ;
2020-03-23 22:30:52 +01:00
thisNode . classed ( "red-ui-flow-node-disabled" , d . d === true ) ;
thisNode . classed ( "red-ui-flow-subflow" , activeSubflow != null )
2019-01-26 21:49:22 +01:00
2014-03-22 14:47:47 +01:00
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
2020-03-23 22:30:52 +01:00
thisNode . attr ( "transform" , "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ) ;
2015-06-03 11:05:31 +02:00
if ( mouse _mode != RED . state . MOVING _ACTIVE ) {
2020-03-23 22:30:52 +01:00
thisNode . classed ( "red-ui-flow-node-selected" , d . selected )
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node" )
2020-03-23 22:30:52 +01:00
. attr ( "width" , d . w )
. attr ( "height" , d . h )
. classed ( "red-ui-flow-node-highlighted" , d . highlighted )
2015-06-03 11:05:31 +02:00
;
2019-09-03 11:43:46 +02:00
var l = "" ;
if ( d . _def . label ) {
l = d . _def . label ;
try {
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
l = RED . text . bidi . enforceTextDirectionWithUCC ( l ) ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".label" , err ) ;
l = d . type ;
}
}
var sa = convertLineBreakCharacter ( l ) ;
var sn = sa . length ;
var st = "" ;
var yp = d . h / 2 - ( sn / 2 ) * 24 + 16
2019-09-27 12:17:17 +02:00
var labelId = d . id . replace ( "." , "-" ) ;
2019-09-03 11:43:46 +02:00
if ( labelLineNumber < sn ) {
for ( var i = labelLineNumber ; i < sn ; i ++ ) {
var text = node . append ( "svg:text" )
. attr ( "class" , "red-ui-flow-node-label" )
2019-09-27 12:17:17 +02:00
. attr ( "id" , "red-ui-flow-node-label-" + labelId + "-" + i )
2019-09-03 11:43:46 +02:00
. attr ( "x" , 38 )
. attr ( "dy" , ".3px" )
. attr ( "text-anchor" , "start" )
. classed ( "hide" , hideLabel ) ;
if ( d . _def . align ) {
text . attr ( "class" , "red-ui-flow-node-label red-ui-flow-node-label-" + d . _def . align ) ;
if ( d . _def . align === "right" ) {
text . attr ( "text-anchor" , "end" ) ;
}
}
}
} else if ( 0 < sn && sn < labelLineNumber ) {
for ( var i = sn ; i < labelLineNumber ; i ++ ) {
2019-09-27 12:17:17 +02:00
thisNode . select ( "#red-ui-flow-node-label-" + labelId + "-" + i ) . remove ( ) ;
2019-09-03 11:43:46 +02:00
}
}
for ( var ic = 0 ; ic < sn ; ic ++ ) {
var yn = yp + ic * 24 ;
2019-09-27 12:17:17 +02:00
thisNode . select ( "#red-ui-flow-node-label-" + labelId + "-" + ic )
2020-03-23 22:30:52 +01:00
. text ( separateTextByLineBreak [ ic ] )
. attr ( "y" , yn )
2019-09-03 11:43:46 +02:00
. attr ( "class" , function ( d ) {
var s = "" ;
if ( d . _def . labelStyle ) {
s = d . _def . labelStyle ;
try {
s = ( typeof s === "function" ? s . call ( d ) : s ) || "" ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".labelStyle" , err ) ;
s = "" ;
}
s = " " + s ;
}
return "red-ui-flow-node-label" + ( d . _def . align ? " red-ui-flow-node-label-" + d . _def . align : "" ) + s ;
} ) . classed ( "hide" , hideLabel ) ;
}
2019-03-05 13:21:23 +01:00
if ( ( ! d . _def . align && d . inputs !== 0 && d . outputs === 0 ) || "right" === d . _def . align ) {
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-icon-group" ) . classed ( "red-ui-flow-node-icon-group-right" , true ) ;
thisNode . selectAll ( ".red-ui-flow-node-label" ) . classed ( "red-ui-flow-node-label-right" , true ) . attr ( "text-anchor" , "end" ) ;
2019-03-05 13:21:23 +01:00
} else {
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-icon-group" ) . classed ( "red-ui-flow-node-icon-group-right" , false ) ;
thisNode . selectAll ( ".red-ui-flow-node-label" ) . classed ( "red-ui-flow-node-label-right" , false ) . attr ( "text-anchor" , "start" ) ;
2019-03-05 13:21:23 +01:00
}
2019-09-03 11:43:46 +02:00
var alignX ;
2020-03-23 22:30:52 +01:00
thisNode . selectAll ( ".red-ui-flow-node-icon-group" ) . attr ( "transform" , "translate(0, 0)" ) ;
2019-09-03 11:43:46 +02:00
thisNode . selectAll ( ".red-ui-flow-node-label" ) . attr ( "x" , function ( d ) { alignX = 38 ; return 38 ; } ) ;
2020-03-23 22:30:52 +01:00
thisNode . selectAll ( ".red-ui-flow-node-icon-group-right" ) . attr ( "transform" , "translate(" + ( d . w - 30 ) + ",0)" ) ;
2019-09-03 11:43:46 +02:00
thisNode . selectAll ( ".red-ui-flow-node-label-right" ) . attr ( "x" , function ( d ) { alignX = d . w - 38 ; return d . w - 38 } ) ;
2019-05-15 14:54:29 +02:00
//thisNode.selectAll(".red-ui-flow-node-icon-right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
//thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;});
//thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
var inputPorts = thisNode . selectAll ( ".red-ui-flow-port-input" ) ;
2019-08-15 14:29:08 +02:00
if ( ( ! isLink || ( showAllLinkPorts === - 1 && ! activeLinkNodes [ d . id ] ) ) && d . inputs === 0 && ! inputPorts . empty ( ) ) {
2015-06-03 11:05:31 +02:00
inputPorts . remove ( ) ;
2018-12-18 11:57:53 +01:00
} else if ( ( ( isLink && ( showAllLinkPorts === PORT _TYPE _INPUT || activeLinkNodes [ d . id ] ) ) || d . inputs === 1 ) && inputPorts . empty ( ) ) {
2019-05-15 14:54:29 +02:00
var inputGroup = thisNode . append ( "g" ) . attr ( "class" , "red-ui-flow-port-input" ) ;
2018-12-18 11:57:53 +01:00
var inputGroupPorts ;
if ( d . type === "link in" ) {
inputGroupPorts = inputGroup . append ( "circle" )
. attr ( "cx" , - 1 ) . attr ( "cy" , 5 )
. attr ( "r" , 5 )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-port red-ui-flow-link-port" )
2018-12-18 11:57:53 +01:00
} else {
2019-05-15 14:54:29 +02:00
inputGroupPorts = inputGroup . append ( "rect" ) . attr ( "class" , "red-ui-flow-port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
2018-12-18 11:57:53 +01:00
}
inputGroupPorts . on ( "mousedown" , function ( d ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , function ( d ) { portMouseDown ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseup" , function ( d ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; } )
2020-06-05 16:48:02 +02:00
. on ( "touchend" , function ( d ) { portMouseUp ( d , PORT _TYPE _INPUT , 0 ) ; d3 . event . preventDefault ( ) ; } )
2017-01-26 16:38:25 +01:00
. on ( "mouseover" , function ( d ) { portMouseOver ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } )
. on ( "mouseout" , function ( d ) { portMouseOut ( d3 . select ( this ) , d , PORT _TYPE _INPUT , 0 ) ; } ) ;
2015-06-03 11:05:31 +02:00
}
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
var numOutputs = d . outputs ;
2018-12-18 11:57:53 +01:00
if ( isLink && d . type === "link out" ) {
if ( showAllLinkPorts === PORT _TYPE _OUTPUT || activeLinkNodes [ d . id ] ) {
d . ports = [ 0 ] ;
numOutputs = 1 ;
} else {
d . ports = [ ] ;
numOutputs = 0 ;
}
}
2015-06-03 11:05:31 +02:00
var y = ( d . h / 2 ) - ( ( numOutputs - 1 ) / 2 ) * 13 ;
d . ports = d . ports || d3 . range ( numOutputs ) ;
2019-05-15 14:54:29 +02:00
d . _ports = thisNode . selectAll ( ".red-ui-flow-port-output" ) . data ( d . ports ) ;
var output _group = d . _ports . enter ( ) . append ( "g" ) . attr ( "class" , "red-ui-flow-port-output" ) ;
2018-12-18 11:57:53 +01:00
var output _group _ports ;
if ( d . type === "link out" ) {
output _group _ports = output _group . append ( "circle" )
. attr ( "cx" , 11 ) . attr ( "cy" , 5 )
. attr ( "r" , 5 )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-port red-ui-flow-link-port" )
2018-12-18 11:57:53 +01:00
} else {
output _group _ports = output _group . append ( "rect" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-port" )
2018-12-18 11:57:53 +01:00
. attr ( "rx" , 3 ) . attr ( "ry" , 3 )
. attr ( "width" , 10 )
. attr ( "height" , 10 )
}
2015-07-01 00:42:03 +02:00
2018-12-18 11:57:53 +01:00
output _group _ports . on ( "mousedown" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseDown ( node , PORT _TYPE _OUTPUT , i ) ; } } ) ( ) )
2020-06-05 16:48:02 +02:00
. on ( "touchstart" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseDown ( node , PORT _TYPE _OUTPUT , i ) ; d3 . event . preventDefault ( ) ; } } ) ( ) )
2017-01-26 16:38:25 +01:00
. on ( "mouseup" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseUp ( node , PORT _TYPE _OUTPUT , i ) ; } } ) ( ) )
2020-06-05 16:48:02 +02:00
. on ( "touchend" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseUp ( node , PORT _TYPE _OUTPUT , i ) ; d3 . event . preventDefault ( ) ; } } ) ( ) )
2017-01-27 17:33:11 +01:00
. on ( "mouseover" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseOver ( d3 . select ( this ) , node , PORT _TYPE _OUTPUT , i ) ; } } ) ( ) )
. on ( "mouseout" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseOut ( d3 . select ( this ) , node , PORT _TYPE _OUTPUT , i ) ; } } ) ( ) ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
d . _ports . exit ( ) . remove ( ) ;
if ( d . _ports ) {
numOutputs = d . outputs || 1 ;
y = ( d . h / 2 ) - ( ( numOutputs - 1 ) / 2 ) * 13 ;
var x = d . w - 5 ;
d . _ports . each ( function ( d , i ) {
var port = d3 . select ( this ) ;
port . attr ( "transform" , function ( d ) { return "translate(" + x + "," + ( ( y + 13 * i ) - 5 ) + ")" ; } ) ;
} ) ;
}
if ( d . _def . icon ) {
2019-05-15 14:54:29 +02:00
var icon = thisNode . select ( ".red-ui-flow-node-icon" ) ;
2018-09-26 02:16:15 +02:00
var faIcon = thisNode . select ( ".fa-lg" ) ;
var current _url ;
2018-09-28 10:07:55 +02:00
if ( ! icon . empty ( ) ) {
2018-09-26 02:16:15 +02:00
current _url = icon . attr ( "xlink:href" ) ;
} else {
current _url = faIcon . attr ( "xlink:href" ) ;
}
2017-01-18 14:06:22 +01:00
var new _url = RED . utils . getNodeIcon ( d . _def , d ) ;
if ( new _url !== current _url ) {
2018-09-28 10:07:55 +02:00
if ( ! icon . empty ( ) ) {
icon . remove ( ) ;
} else {
faIcon . remove ( ) ;
2015-06-03 11:05:31 +02:00
}
2019-05-15 14:54:29 +02:00
var iconGroup = thisNode . select ( ".red-ui-flow-node-icon-group" ) ;
2018-09-28 10:07:55 +02:00
createIconAttributes ( new _url , iconGroup , d ) ;
2015-06-03 11:05:31 +02:00
}
}
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-changed" )
2020-03-23 22:30:52 +01:00
. attr ( "transform" , "translate(" + ( d . w - 10 ) + ", -2)" )
. classed ( "hide" , ! ( d . changed || d . moved ) ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-error" )
2020-03-23 22:30:52 +01:00
. attr ( "transform" , "translate(" + ( d . w - 10 - ( ( d . changed || d . moved ) ? 14 : 0 ) ) + ", -2)" )
. classed ( "hide" , d . valid ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-port-input" ) . each ( function ( d , i ) {
2018-10-01 13:59:06 +02:00
var port = d3 . select ( this ) ;
port . attr ( "transform" , function ( d ) { return "translate(-5," + ( ( d . h / 2 ) - 5 ) + ")" ; } )
2013-09-05 16:02:48 +02:00
} ) ;
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-icon" ) . attr ( "y" , function ( d ) { return ( d . h - d3 . select ( this ) . attr ( "height" ) ) / 2 ; } ) ;
2020-03-23 22:30:52 +01:00
thisNode . selectAll ( ".red-ui-flow-node-icon-shade" ) . attr ( "height" , d . h ) ;
thisNode . selectAll ( ".red-ui-flow-node-icon-shade-border" ) . attr ( "d" ,
"M " + ( ( ( ! d . _def . align && d . inputs !== 0 && d . outputs === 0 ) || "right" === d . _def . align ) ? 0 : 30 ) + " 1 l 0 " + ( d . h - 2 )
) ;
thisNode . selectAll ( ".fa-lg" ) . attr ( "y" , ( d . h + 13 ) / 2 ) ;
thisNode . selectAll ( ".red-ui-flow-node-button" ) . attr ( "opacity" , function ( d2 ) { return ! isButtonEnabled ( d2 ) ? 0.4 : 1 } ) ;
thisNode . selectAll ( ".red-ui-flow-node-button-button" ) . attr ( "cursor" , function ( d2 ) { return isButtonEnabled ( d2 ) ? "" : "pointer" } ) ;
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-button" ) . attr ( "transform" , function ( d ) {
2018-10-29 21:55:35 +01:00
var x = d . _def . align == "right" ? d . w - 6 : - 25 ;
2018-10-01 13:59:06 +02:00
if ( d . _def . button . toggle && ! d [ d . _def . button . toggle ] ) {
2018-10-29 21:55:35 +01:00
x = x - ( d . _def . align == "right" ? 8 : - 8 ) ;
2018-10-01 13:59:06 +02:00
}
return "translate(" + x + ",2)" ;
2015-06-03 11:05:31 +02:00
} ) ;
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-button rect" ) . attr ( "fill-opacity" , function ( d ) {
2018-10-01 13:59:06 +02:00
if ( d . _def . button . toggle ) {
return d [ d . _def . button . toggle ] ? 1 : 0.2 ;
}
return 1 ;
2015-06-03 11:05:31 +02:00
} ) ;
2015-07-01 00:42:03 +02:00
2018-10-01 13:59:06 +02:00
if ( d . _def . button && ( typeof d . _def . button . visible === "function" ) ) { // is defined and a function...
if ( d . _def . button . visible . call ( d ) === false ) {
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-button" ) . style ( "display" , "none" ) ;
2018-10-01 13:59:06 +02:00
}
else {
2019-05-15 14:54:29 +02:00
thisNode . selectAll ( ".red-ui-flow-node-button" ) . style ( "display" , "inherit" ) ;
2018-10-01 13:59:06 +02:00
}
}
2019-05-15 14:54:29 +02:00
// thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
// thisNode.selectAll("text.node_badge_label").text(function(d,i) {
// if (d._def.badge) {
// if (typeof d._def.badge == "function") {
// try {
// return d._def.badge.call(d);
// } catch(err) {
// console.log("Definition error: "+d.type+".badge",err);
// return "";
// }
// } else {
// return d._def.badge;
// }
// }
// return "";
// });
2015-06-02 23:07:45 +02:00
}
2013-09-05 16:02:48 +02:00
2019-05-16 15:42:41 +02:00
if ( d . dirtyStatus ) {
if ( ! showStatus || ! d . status ) {
thisNode . selectAll ( ".red-ui-flow-node-status-group" ) . style ( "display" , "none" ) ;
2014-05-08 15:15:54 +02:00
} else {
2019-05-16 15:42:41 +02:00
thisNode . selectAll ( ".red-ui-flow-node-status-group" ) . style ( "display" , "inline" ) ;
var fill = status _colours [ d . status . fill ] ; // Only allow our colours for now
if ( d . status . shape == null && fill == null ) {
thisNode . selectAll ( ".red-ui-flow-node-status" ) . style ( "display" , "none" ) ;
thisNode . selectAll ( ".red-ui-flow-node-status-group" ) . attr ( "transform" , "translate(-14," + ( d . h + 3 ) + ")" ) ;
} else {
thisNode . selectAll ( ".red-ui-flow-node-status-group" ) . attr ( "transform" , "translate(3," + ( d . h + 3 ) + ")" ) ;
var statusClass = "red-ui-flow-node-status-" + ( d . status . shape || "dot" ) + "-" + d . status . fill ;
2019-05-27 22:06:53 +02:00
thisNode . selectAll ( ".red-ui-flow-node-status" ) . style ( "display" , "inline" ) . attr ( "class" , "red-ui-flow-node-status " + statusClass ) ;
2019-05-16 15:42:41 +02:00
}
2019-08-06 17:10:33 +02:00
if ( d . status . hasOwnProperty ( 'text' ) ) {
2019-05-16 15:42:41 +02:00
thisNode . selectAll ( ".red-ui-flow-node-status-label" ) . text ( d . status . text ) ;
} else {
thisNode . selectAll ( ".red-ui-flow-node-status-label" ) . text ( "" ) ;
}
2014-05-08 15:15:54 +02:00
}
2019-05-16 15:42:41 +02:00
delete d . dirtyStatus ;
2014-05-08 15:15:54 +02:00
}
2014-05-14 15:22:28 +02:00
2013-09-05 16:02:48 +02:00
d . dirty = false ;
2020-03-03 20:04:32 +01:00
if ( d . g ) {
if ( ! dirtyGroups [ d . g ] ) {
2020-03-04 22:48:38 +01:00
var gg = d . g ;
while ( gg && ! dirtyGroups [ gg ] ) {
dirtyGroups [ gg ] = RED . nodes . group ( gg ) ;
gg = dirtyGroups [ gg ] . g ;
}
2020-03-03 20:04:32 +01:00
}
}
2013-09-05 16:02:48 +02:00
}
} ) ;
2016-05-17 10:16:58 +02:00
2019-05-15 14:54:29 +02:00
var link = linkLayer . selectAll ( ".red-ui-flow-link" ) . data (
2015-03-12 14:26:31 +01:00
activeLinks ,
function ( d ) {
return d . source . id + ":" + d . sourcePort + ":" + d . target . id + ":" + d . target . i ;
2013-09-05 16:02:48 +02:00
}
2015-03-12 14:26:31 +01:00
) ;
2019-05-15 14:54:29 +02:00
var linkEnter = link . enter ( ) . insert ( "g" , ".red-ui-flow-node" ) . attr ( "class" , "red-ui-flow-link" ) ;
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
linkEnter . each ( function ( d , i ) {
var l = d3 . select ( this ) ;
d . added = true ;
2019-05-15 14:54:29 +02:00
l . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-link-background red-ui-flow-link-path" )
. classed ( "red-ui-flow-link-link" , function ( d ) { return d . link } )
2015-03-12 14:26:31 +01:00
. on ( "mousedown" , function ( d ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2015-03-12 14:26:31 +01:00
mousedown _link = d ;
clearSelection ( ) ;
selected _link = mousedown _link ;
updateSelection ( ) ;
redraw ( ) ;
focusView ( ) ;
d3 . event . stopPropagation ( ) ;
2019-08-13 11:31:21 +02:00
if ( d3 . event . metaKey || d3 . event . ctrlKey ) {
l . classed ( "red-ui-flow-link-splice" , true ) ;
2020-03-03 20:04:32 +01:00
var point = d3 . mouse ( this ) ;
var clickedGroup = getGroupAt ( point [ 0 ] , point [ 1 ] ) ;
showQuickAddDialog ( point , selected _link , clickedGroup ) ;
2019-08-13 11:31:21 +02:00
}
2015-03-12 14:26:31 +01:00
} )
. on ( "touchstart" , function ( d ) {
2019-05-23 17:39:06 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
d3 . event . stopPropagation ( ) ;
return ;
}
2015-03-12 14:26:31 +01:00
mousedown _link = d ;
clearSelection ( ) ;
selected _link = mousedown _link ;
updateSelection ( ) ;
redraw ( ) ;
focusView ( ) ;
d3 . event . stopPropagation ( ) ;
2015-11-06 17:46:07 +01:00
var obj = d3 . select ( document . body ) ;
var touch0 = d3 . event . touches . item ( 0 ) ;
var pos = [ touch0 . pageX , touch0 . pageY ] ;
touchStartTime = setTimeout ( function ( ) {
touchStartTime = null ;
showTouchMenu ( obj , pos ) ;
} , touchLongPressTimeout ) ;
2020-06-05 16:48:02 +02:00
d3 . event . preventDefault ( ) ;
2016-01-08 23:34:10 +01:00
} )
2019-05-15 14:54:29 +02:00
l . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-link-outline red-ui-flow-link-path" ) ;
l . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-link-line red-ui-flow-link-path" )
. classed ( "red-ui-flow-link-link" , function ( d ) { return d . link } )
. classed ( "red-ui-flow-subflow-link" , function ( d ) { return ! d . link && activeSubflow } ) ;
2015-03-12 14:26:31 +01:00
} ) ;
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
link . exit ( ) . remove ( ) ;
2019-05-15 14:54:29 +02:00
var links = linkLayer . selectAll ( ".red-ui-flow-link-path" ) ;
2015-03-12 14:26:31 +01:00
links . each ( function ( d ) {
var link = d3 . select ( this ) ;
if ( d . added || d === selected _link || d . selected || dirtyNodes [ d . source . id ] || dirtyNodes [ d . target . id ] ) {
2019-05-15 14:54:29 +02:00
if ( /red-ui-flow-link-line/ . test ( link . attr ( 'class' ) ) ) {
link . classed ( "red-ui-flow-subflow-link" , function ( d ) { return ! d . link && activeSubflow } ) ;
2019-01-26 21:49:22 +01:00
}
2015-03-12 14:26:31 +01:00
link . attr ( "d" , function ( d ) {
var numOutputs = d . source . outputs || 1 ;
var sourcePort = d . sourcePort || 0 ;
var y = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
d . x1 = d . source . x + d . source . w / 2 ;
d . y1 = d . source . y + y ;
d . x2 = d . target . x - d . target . w / 2 ;
d . y2 = d . target . y ;
2015-07-01 00:42:03 +02:00
2018-06-25 14:18:00 +02:00
// return "M "+d.x1+" "+d.y1+
// " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
// (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
// d.x2+" "+d.y2;
2019-01-26 21:49:22 +01:00
var path = generateLinkPath ( d . x1 , d . y1 , d . x2 , d . y2 , 1 ) ;
if ( /NaN/ . test ( path ) ) {
return ""
}
return path ;
2015-03-12 14:26:31 +01:00
} ) ;
2019-06-14 23:12:47 +02:00
link . classed ( "red-ui-flow-node-disabled" , function ( d ) { return d . source . d || d . target . d ; } ) ;
2013-09-28 22:15:32 +02:00
}
2015-03-12 14:26:31 +01:00
} )
2015-07-01 00:42:03 +02:00
2019-05-15 14:54:29 +02:00
link . classed ( "red-ui-flow-link-selected" , function ( d ) { return d === selected _link || d . selected ; } ) ;
link . classed ( "red-ui-flow-link-unknown" , function ( d ) {
2015-03-12 14:26:31 +01:00
delete d . added ;
2015-03-12 14:35:39 +01:00
return d . target . type == "unknown" || d . source . type == "unknown"
2015-03-12 14:26:31 +01:00
} ) ;
2019-05-15 14:54:29 +02:00
var offLinks = linkLayer . selectAll ( ".red-ui-flow-link-off-flow" ) . data (
2016-05-17 10:16:58 +02:00
activeFlowLinks ,
function ( d ) {
return d . node . id + ":" + d . refresh
}
) ;
2019-05-15 14:54:29 +02:00
var offLinksEnter = offLinks . enter ( ) . insert ( "g" , ".red-ui-flow-node" ) . attr ( "class" , "red-ui-flow-link-off-flow" ) ;
2016-05-17 10:16:58 +02:00
offLinksEnter . each ( function ( d , i ) {
var g = d3 . select ( this ) ;
var s = 1 ;
var labelAnchor = "start" ;
if ( d . node . type === "link in" ) {
s = - 1 ;
labelAnchor = "end" ;
}
var stemLength = s * 30 ;
var branchLength = s * 20 ;
2019-05-15 14:54:29 +02:00
var l = g . append ( "svg:path" ) . attr ( "class" , "red-ui-flow-link-link" ) . attr ( "d" , "M 0 0 h " + stemLength ) ;
2016-05-17 10:16:58 +02:00
var links = d . links ;
var flows = Object . keys ( links ) ;
var tabOrder = RED . nodes . getWorkspaceOrder ( ) ;
flows . sort ( function ( A , B ) {
return tabOrder . indexOf ( A ) - tabOrder . indexOf ( B ) ;
} ) ;
var linkWidth = 10 ;
var h = node _height ;
var y = - ( flows . length - 1 ) * h / 2 ;
2019-05-15 14:54:29 +02:00
var linkGroups = g . selectAll ( ".red-ui-flow-link-group" ) . data ( flows ) ;
var enterLinkGroups = linkGroups . enter ( ) . append ( "g" ) . attr ( "class" , "red-ui-flow-link-group" )
. on ( 'mouseover' , function ( ) { if ( mouse _mode !== 0 ) { return } d3 . select ( this ) . classed ( 'red-ui-flow-link-group-active' , true ) } )
. on ( 'mouseout' , function ( ) { if ( mouse _mode !== 0 ) { return } d3 . select ( this ) . classed ( 'red-ui-flow-link-group-active' , false ) } )
2016-05-17 10:16:58 +02:00
. on ( 'mousedown' , function ( ) { d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } )
. on ( 'mouseup' , function ( f ) {
2018-12-18 11:57:53 +01:00
if ( mouse _mode !== 0 ) {
return
}
2016-05-17 10:16:58 +02:00
d3 . event . stopPropagation ( ) ;
var targets = d . links [ f ] ;
RED . workspaces . show ( f ) ;
targets . forEach ( function ( n ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
} ) ;
updateSelection ( ) ;
redraw ( ) ;
} ) ;
enterLinkGroups . each ( function ( f ) {
var linkG = d3 . select ( this ) ;
2019-05-15 14:54:29 +02:00
linkG . append ( "svg:path" )
. attr ( "class" , "red-ui-flow-link-link" )
2016-05-17 10:16:58 +02:00
. attr ( "d" ,
"M " + stemLength + " 0 " +
"C " + ( stemLength + ( 1.7 * branchLength ) ) + " " + 0 +
" " + ( stemLength + ( 0.1 * branchLength ) ) + " " + y + " " +
( stemLength + branchLength * 1.5 ) + " " + y + " "
) ;
linkG . append ( "svg:path" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-link-port" )
2016-05-17 10:16:58 +02:00
. attr ( "d" ,
"M " + ( stemLength + branchLength * 1.5 + s * ( linkWidth + 7 ) ) + " " + ( y - 12 ) + " " +
"h " + ( - s * linkWidth ) + " " +
"a 3 3 45 0 " + ( s === 1 ? "0" : "1" ) + " " + ( s * - 3 ) + " 3 " +
"v 18 " +
"a 3 3 45 0 " + ( s === 1 ? "0" : "1" ) + " " + ( s * 3 ) + " 3 " +
"h " + ( s * linkWidth )
) ;
linkG . append ( "svg:path" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-link-port" )
2016-05-17 10:16:58 +02:00
. attr ( "d" ,
"M " + ( stemLength + branchLength * 1.5 + s * ( linkWidth + 10 ) ) + " " + ( y - 12 ) + " " +
"h " + ( s * ( linkWidth * 3 ) ) + " " +
"M " + ( stemLength + branchLength * 1.5 + s * ( linkWidth + 10 ) ) + " " + ( y + 12 ) + " " +
"h " + ( s * ( linkWidth * 3 ) )
) . style ( "stroke-dasharray" , "12 3 8 4 3" ) ;
2019-05-15 14:54:29 +02:00
linkG . append ( "rect" ) . attr ( "class" , "red-ui-flow-port red-ui-flow-link-port" )
2016-05-17 10:16:58 +02:00
. attr ( "x" , stemLength + branchLength * 1.5 - 4 + ( s * 4 ) )
. attr ( "y" , y - 4 )
. attr ( "rx" , 2 )
. attr ( "ry" , 2 )
. attr ( "width" , 8 )
. attr ( "height" , 8 ) ;
linkG . append ( "rect" )
. attr ( "x" , stemLength + branchLength * 1.5 - ( s === - 1 ? node _width : 0 ) )
. attr ( "y" , y - 12 )
. attr ( "width" , node _width )
. attr ( "height" , 24 )
. style ( "stroke" , "none" )
. style ( "fill" , "transparent" )
var tab = RED . nodes . workspace ( f ) ;
var label ;
if ( tab ) {
label = tab . label || tab . id ;
}
linkG . append ( "svg:text" )
2019-05-15 14:54:29 +02:00
. attr ( "class" , "red-ui-flow-port-label" )
2016-05-17 10:16:58 +02:00
. attr ( "x" , stemLength + branchLength * 1.5 + ( s * 15 ) )
. attr ( "y" , y + 1 )
. style ( "font-size" , "10px" )
. style ( "text-anchor" , labelAnchor )
. text ( label ) ;
y += h ;
} ) ;
linkGroups . exit ( ) . remove ( ) ;
} ) ;
offLinks . exit ( ) . remove ( ) ;
2019-05-15 14:54:29 +02:00
offLinks = linkLayer . selectAll ( ".red-ui-flow-link-off-flow" ) ;
2016-05-17 10:16:58 +02:00
offLinks . each ( function ( d ) {
var s = 1 ;
if ( d . node . type === "link in" ) {
s = - 1 ;
}
var link = d3 . select ( this ) ;
link . attr ( "transform" , function ( d ) { return "translate(" + ( d . node . x + ( s * d . node . w / 2 ) ) + "," + ( d . node . y ) + ")" ; } ) ;
} )
2020-03-03 20:04:32 +01:00
var group = groupLayer . selectAll ( ".red-ui-flow-group" ) . data ( activeGroups , function ( d ) { return d . id } ) ;
2020-04-01 15:10:35 +02:00
group . exit ( ) . each ( function ( d , i ) {
document . getElementById ( "group_select_" + d . id ) . remove ( )
} ) . remove ( ) ;
var groupEnter = group . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "red-ui-flow-group" )
2020-03-24 15:05:09 +01:00
var addedGroups = false ;
2020-03-03 20:04:32 +01:00
groupEnter . each ( function ( d , i ) {
2020-03-24 15:05:09 +01:00
addedGroups = true ;
2020-03-03 20:04:32 +01:00
var g = d3 . select ( this ) ;
2020-03-23 15:51:18 +01:00
g . attr ( "id" , d . id ) ;
2020-04-14 23:39:42 +02:00
var groupBorderRadius = 4 ;
2020-03-03 20:04:32 +01:00
2020-04-01 15:10:35 +02:00
var selectGroup = groupSelectLayer . append ( 'g' ) . attr ( "class" , "red-ui-flow-group" ) . attr ( "id" , "group_select_" + d . id ) ;
selectGroup . append ( 'rect' ) . classed ( "red-ui-flow-group-outline-select" , true )
2020-04-14 23:39:42 +02:00
. attr ( 'rx' , groupBorderRadius ) . attr ( 'ry' , groupBorderRadius )
2020-04-01 15:10:35 +02:00
. attr ( "x" , - 4 )
. attr ( "y" , - 4 )
. style ( "stroke" , "rgba(255,255,255,0.8)" )
. style ( "stroke-width" , 6 )
selectGroup . append ( 'rect' ) . classed ( "red-ui-flow-group-outline-select" , true )
2020-04-14 23:39:42 +02:00
. attr ( 'rx' , groupBorderRadius ) . attr ( 'ry' , groupBorderRadius )
2020-03-20 21:00:03 +01:00
. attr ( "x" , - 4 )
2020-04-01 15:10:35 +02:00
. attr ( "y" , - 4 )
2020-05-12 11:59:41 +02:00
selectGroup . on ( "mousedown" , function ( ) { groupMouseDown . call ( g [ 0 ] [ 0 ] , d ) } ) ;
selectGroup . on ( "mouseup" , function ( ) { groupMouseUp . call ( g [ 0 ] [ 0 ] , d ) } ) ;
2020-04-01 15:10:35 +02:00
g . append ( 'rect' ) . classed ( "red-ui-flow-group-outline" , true ) . attr ( 'rx' , 0.5 ) . attr ( 'ry' , 0.5 ) ;
2020-03-04 22:48:38 +01:00
2020-03-03 20:04:32 +01:00
g . append ( 'rect' ) . classed ( "red-ui-flow-group-body" , true )
2020-04-14 23:39:42 +02:00
. attr ( 'rx' , groupBorderRadius ) . attr ( 'ry' , groupBorderRadius ) . style ( {
2020-03-03 20:04:32 +01:00
"fill" : d . fill || "none" ,
"stroke" : d . stroke || "none" ,
} )
g . on ( "mousedown" , groupMouseDown ) . on ( "mouseup" , groupMouseUp )
2020-05-16 13:56:21 +02:00
g . append ( 'svg:text' ) . attr ( "class" , "red-ui-flow-group-label" ) ;
2020-03-04 22:48:38 +01:00
d . dirty = true ;
2020-03-03 20:04:32 +01:00
} ) ;
2020-03-24 15:05:09 +01:00
if ( addedGroups ) {
group . sort ( function ( a , b ) {
2020-03-26 23:51:06 +01:00
if ( a . _root === b . _root ) {
return a . _depth - b . _depth ;
} else {
return a . _root . localeCompare ( b . _root ) ;
}
2020-03-24 15:05:09 +01:00
} )
}
2020-03-09 16:10:54 +01:00
group [ 0 ] . reverse ( ) ;
2020-03-03 20:04:32 +01:00
group . each ( function ( d , i ) {
2020-03-26 16:27:34 +01:00
if ( d . resize ) {
d . minWidth = 0 ;
delete d . resize ;
}
2020-03-03 20:04:32 +01:00
if ( d . dirty || dirtyGroups [ d . id ] ) {
var g = d3 . select ( this ) ;
2020-03-26 16:27:34 +01:00
if ( d . nodes . length > 0 ) {
var minX = Number . POSITIVE _INFINITY ;
var minY = Number . POSITIVE _INFINITY ;
var maxX = 0 ;
var maxY = 0 ;
d . nodes . forEach ( function ( n ) {
if ( n . type !== "group" ) {
minX = Math . min ( minX , n . x - n . w / 2 - 25 - ( ( n . _def . button && n . _def . align !== "right" ) ? 20 : 0 ) ) ;
minY = Math . min ( minY , n . y - n . h / 2 - 25 ) ;
maxX = Math . max ( maxX , n . x + n . w / 2 + 25 + ( ( n . _def . button && n . _def . align == "right" ) ? 20 : 0 ) ) ;
maxY = Math . max ( maxY , n . y + n . h / 2 + 25 ) ;
} else {
minX = Math . min ( minX , n . x - 25 )
minY = Math . min ( minY , n . y - 25 )
maxX = Math . max ( maxX , n . x + n . w + 25 )
maxY = Math . max ( maxY , n . y + n . h + 25 )
}
} ) ;
d . x = minX ;
d . y = minY ;
d . w = maxX - minX ;
d . h = maxY - minY ;
} else {
d . w = 40 ;
d . h = 40 ;
}
if ( ! d . minWidth ) {
if ( d . style . label && d . name ) {
d . minWidth = calculateTextWidth ( d . name || "" , "red-ui-flow-group-label" , 8 ) ;
2020-05-16 13:56:21 +02:00
d . labels = separateTextByLineBreak ;
2020-03-04 22:48:38 +01:00
} else {
2020-03-26 16:27:34 +01:00
d . minWidth = 40 ;
2020-03-04 22:48:38 +01:00
}
2020-03-26 16:27:34 +01:00
}
d . w = Math . max ( d . minWidth , d . w ) ;
2020-05-16 13:56:21 +02:00
if ( d . style . label && d . labels ) {
2020-05-19 18:53:20 +02:00
var h = ( d . labels . length - 1 ) * 16 ;
2020-05-16 13:56:21 +02:00
var labelPos = d . style [ "label-position" ] || "nw" ;
d . h += h ;
if ( labelPos [ 0 ] === "n" ) {
d . y -= h ;
}
}
2020-03-03 20:04:32 +01:00
2020-03-23 15:51:18 +01:00
g . attr ( "transform" , "translate(" + d . x + "," + d . y + ")" )
2020-03-03 20:04:32 +01:00
g . selectAll ( ".red-ui-flow-group-outline" )
2020-03-04 22:48:38 +01:00
. attr ( "width" , d . w )
. attr ( "height" , d . h )
2020-03-20 21:00:03 +01:00
2020-04-01 15:10:35 +02:00
var selectGroup = document . getElementById ( "group_select_" + d . id ) ;
selectGroup . setAttribute ( "transform" , "translate(" + d . x + "," + d . y + ")" ) ;
if ( d . hovered ) {
selectGroup . classList . add ( "red-ui-flow-group-hovered" )
} else {
selectGroup . classList . remove ( "red-ui-flow-group-hovered" )
}
var selectGroupRect = selectGroup . children [ 0 ] ;
selectGroupRect . setAttribute ( "width" , d . w + 8 )
selectGroupRect . setAttribute ( "height" , d . h + 8 )
2020-05-12 11:59:41 +02:00
selectGroupRect . style . strokeOpacity = ( d . selected || d . highlighted ) ? 0.8 : 0 ;
2020-04-01 15:10:35 +02:00
selectGroupRect . style . strokeDasharray = ( d . active ) ? "10 4" : "" ;
selectGroupRect = selectGroup . children [ 1 ] ;
selectGroupRect . setAttribute ( "width" , d . w + 8 )
selectGroupRect . setAttribute ( "height" , d . h + 8 )
2020-05-12 11:59:41 +02:00
selectGroupRect . style . strokeOpacity = ( d . selected || d . highlighted ) ? 0.8 : 0 ;
2020-04-01 15:10:35 +02:00
selectGroupRect . style . strokeDasharray = ( d . active ) ? "10 4" : "" ;
2020-03-20 21:00:03 +01:00
2020-05-12 11:59:41 +02:00
if ( d . highlighted ) {
selectGroup . classList . add ( "red-ui-flow-node-highlighted" ) ;
} else {
selectGroup . classList . remove ( "red-ui-flow-node-highlighted" ) ;
}
2020-03-03 20:04:32 +01:00
g . selectAll ( ".red-ui-flow-group-body" )
2020-03-04 22:48:38 +01:00
. attr ( "width" , d . w )
. attr ( "height" , d . h )
2020-03-26 16:27:34 +01:00
. style ( "stroke" , d . style . stroke || "" )
. style ( "stroke-opacity" , d . style . hasOwnProperty ( 'stroke-opacity' ) ? d . style [ 'stroke-opacity' ] : "" )
. style ( "fill" , d . style . fill || "" )
. style ( "fill-opacity" , d . style . hasOwnProperty ( 'fill-opacity' ) ? d . style [ 'fill-opacity' ] : "" )
2020-03-20 21:00:03 +01:00
var label = g . selectAll ( ".red-ui-flow-group-label" ) ;
label . classed ( "hide" , ! ! ! d . style . label )
if ( d . style . label ) {
var labelPos = d . style [ "label-position" ] || "nw" ;
var labelX = 0 ;
var labelY = 0 ;
if ( labelPos [ 0 ] === 'n' ) {
labelY = 0 + 15 ; // Allow for font-height
} else {
2020-05-19 18:53:20 +02:00
labelY = d . h - 5 - ( d . labels . length - 1 ) * 16 ;
2020-03-20 21:00:03 +01:00
}
if ( labelPos [ 1 ] === 'w' ) {
labelX = 5 ;
labelAnchor = "start"
} else if ( labelPos [ 1 ] === 'e' ) {
labelX = d . w - 5 ;
labelAnchor = "end"
} else {
labelX = d . w / 2 ;
labelAnchor = "middle"
}
2020-05-16 13:56:21 +02:00
label
. style ( "fill" , d . style . hasOwnProperty ( 'color' ) ? d . style . color : "#999" )
. attr ( "transform" , "translate(" + labelX + "," + labelY + ")" )
. attr ( "text-anchor" , labelAnchor ) ;
2020-05-17 04:44:23 +02:00
if ( d . labels ) {
var ypos = 0 ;
g . selectAll ( ".red-ui-flow-group-label-text" ) . remove ( ) ;
d . labels . forEach ( function ( name ) {
label . append ( "tspan" )
. classed ( "red-ui-flow-group-label-text" , true )
. text ( name )
. attr ( "x" , 0 )
. attr ( "y" , ypos ) ;
2020-05-19 18:53:20 +02:00
ypos += 16 ;
2020-05-17 04:44:23 +02:00
} ) ;
}
2020-03-20 21:00:03 +01:00
}
2020-03-03 20:04:32 +01:00
delete dirtyGroups [ d . id ] ;
delete d . dirty ;
}
} )
2015-03-12 14:35:39 +01:00
} else {
// JOINING - unselect any selected links
2019-05-15 14:54:29 +02:00
linkLayer . selectAll ( ".red-ui-flow-link-selected" ) . data (
2015-03-12 14:35:39 +01:00
activeLinks ,
function ( d ) {
return d . source . id + ":" + d . sourcePort + ":" + d . target . id + ":" + d . target . i ;
}
2019-05-15 14:54:29 +02:00
) . classed ( "red-ui-flow-link-selected" , false ) ;
2015-03-12 14:26:31 +01:00
}
2018-06-09 00:32:17 +02:00
RED . view . navigator . refresh ( ) ;
2013-09-05 16:02:48 +02:00
if ( d3 . event ) {
d3 . event . preventDefault ( ) ;
}
}
2015-03-02 23:55:34 +01:00
function focusView ( ) {
2016-07-06 14:28:51 +02:00
try {
// Workaround for browser unexpectedly scrolling iframe into full
// view - record the parent scroll position and restore it after
// setting the focus
var scrollX = window . parent . window . scrollX ;
var scrollY = window . parent . window . scrollY ;
2019-05-01 23:41:20 +02:00
chart . trigger ( "focus" ) ;
2016-07-06 14:28:51 +02:00
window . parent . window . scrollTo ( scrollX , scrollY ) ;
} catch ( err ) {
// In case we're iframed into a page of a different origin, just focus
// the view following the inevitable DOMException
2019-05-01 23:41:20 +02:00
chart . trigger ( "focus" ) ;
2016-07-06 14:28:51 +02:00
}
2015-03-02 23:55:34 +01:00
}
2013-09-05 16:02:48 +02:00
/ * *
* Imports a new collection of nodes from a JSON String .
* - all get new IDs assigned
2016-05-17 10:16:58 +02:00
* - all "selected"
* - attached to mouse for placing - "IMPORT_DRAGGING"
2013-09-05 16:02:48 +02:00
* /
2016-09-23 23:02:12 +02:00
function importNodes ( newNodesStr , addNewFlow , touchImport ) {
2019-05-23 17:48:07 +02:00
if ( mouse _mode === RED . state . SELECTING _NODE ) {
return ;
}
2013-09-05 16:02:48 +02:00
try {
2015-07-30 12:03:37 +02:00
var activeSubflowChanged ;
if ( activeSubflow ) {
activeSubflowChanged = activeSubflow . changed ;
}
2016-09-23 23:02:12 +02:00
var result = RED . nodes . import ( newNodesStr , true , addNewFlow ) ;
2013-09-05 16:02:48 +02:00
if ( result ) {
var new _nodes = result [ 0 ] ;
var new _links = result [ 1 ] ;
2020-03-13 12:27:13 +01:00
var new _groups = result [ 2 ] ;
var new _workspaces = result [ 3 ] ;
var new _subflows = result [ 4 ] ;
var new _default _workspace = result [ 5 ] ;
2016-09-23 23:02:12 +02:00
if ( addNewFlow && new _default _workspace ) {
RED . workspaces . show ( new _default _workspace . id ) ;
}
2016-05-17 10:16:58 +02:00
var new _ms = new _nodes . filter ( function ( n ) { return n . hasOwnProperty ( "x" ) && n . hasOwnProperty ( "y" ) && n . z == RED . workspaces . active ( ) } ) . map ( function ( n ) { return { n : n } ; } ) ;
2020-03-26 21:26:58 +01:00
new _ms = new _ms . concat ( new _groups . filter ( function ( g ) { return g . z === RED . workspaces . active ( ) } ) . map ( function ( g ) { return { n : g } } ) )
2018-10-16 12:36:46 +02:00
var new _node _ids = new _nodes . map ( function ( n ) { n . changed = true ; return n . id ; } ) ;
2014-09-08 11:53:18 +02:00
// TODO: pick a more sensible root node
if ( new _ms . length > 0 ) {
2020-03-26 16:27:34 +01:00
2014-10-09 11:05:45 +02:00
2014-09-08 11:53:18 +02:00
if ( mouse _position == null ) {
mouse _position = [ 0 , 0 ] ;
}
2014-10-09 11:05:45 +02:00
2020-03-26 16:27:34 +01:00
var dx = mouse _position [ 0 ] ;
var dy = mouse _position [ 1 ] ;
if ( new _ms . length > 0 ) {
var root _node = new _ms [ 0 ] . n ;
dx = root _node . x ;
dy = root _node . y ;
}
2014-09-08 11:53:18 +02:00
var minX = 0 ;
var minY = 0 ;
var i ;
2020-03-26 16:27:34 +01:00
var node , group ;
2014-10-09 11:05:45 +02:00
2014-09-08 11:53:18 +02:00
for ( i = 0 ; i < new _ms . length ; i ++ ) {
node = new _ms [ i ] ;
node . n . selected = true ;
node . n . changed = true ;
2017-04-24 00:20:36 +02:00
node . n . moved = true ;
2014-09-08 11:53:18 +02:00
node . n . x -= dx - mouse _position [ 0 ] ;
node . n . y -= dy - mouse _position [ 1 ] ;
2020-04-02 12:57:46 +02:00
node . n . w = node _width ;
node . n . h = node _height ;
node . n . resize = true ;
2014-09-08 11:53:18 +02:00
node . dx = node . n . x - mouse _position [ 0 ] ;
node . dy = node . n . y - mouse _position [ 1 ] ;
2020-03-26 16:27:34 +01:00
if ( node . n . type === "group" ) {
minX = Math . min ( node . n . x - 5 , minX ) ;
minY = Math . min ( node . n . y - 5 , minY ) ;
} else {
minX = Math . min ( node . n . x - node _width / 2 - 5 , minX ) ;
minY = Math . min ( node . n . y - node _height / 2 - 5 , minY ) ;
}
2014-09-08 11:53:18 +02:00
}
for ( i = 0 ; i < new _ms . length ; i ++ ) {
node = new _ms [ i ] ;
node . n . x -= minX ;
node . n . y -= minY ;
node . dx -= minX ;
node . dy -= minY ;
2016-05-18 17:48:16 +02:00
if ( node . n . _def . onadd ) {
try {
node . n . _def . onadd . call ( node . n ) ;
} catch ( err ) {
2017-07-05 00:40:37 +02:00
console . log ( "Definition error: " + node . n . type + ".onadd:" , err ) ;
2016-05-18 17:48:16 +02:00
}
}
2014-09-08 11:53:18 +02:00
}
if ( ! touchImport ) {
mouse _mode = RED . state . IMPORT _DRAGGING ;
2016-03-17 12:12:45 +01:00
spliceActive = false ;
if ( new _ms . length === 1 ) {
node = new _ms [ 0 ] ;
2016-08-26 14:34:29 +02:00
spliceActive = node . n . hasOwnProperty ( "_def" ) &&
2020-03-29 21:33:15 +02:00
( ( node . n . hasOwnProperty ( "inputs" ) && node . n . inputs > 0 ) || ( ! node . n . hasOwnProperty ( "inputs" ) && node . n . _def . inputs > 0 ) ) &&
( ( node . n . hasOwnProperty ( "outputs" ) && node . n . outputs > 0 ) || ( ! node . n . hasOwnProperty ( "outputs" ) && node . n . _def . outputs > 0 ) )
2016-03-17 12:12:45 +01:00
}
2014-09-08 11:53:18 +02:00
}
2016-12-04 23:59:43 +01:00
RED . keyboard . add ( "*" , "escape" , function ( ) {
RED . keyboard . remove ( "escape" ) ;
2014-09-08 11:53:18 +02:00
clearSelection ( ) ;
RED . history . pop ( ) ;
mouse _mode = 0 ;
} ) ;
clearSelection ( ) ;
moving _set = new _ms ;
2014-05-15 23:49:07 +02:00
}
2013-09-05 16:02:48 +02:00
2015-07-30 12:03:37 +02:00
var historyEvent = {
2016-05-17 10:16:58 +02:00
t : "add" ,
2014-02-25 00:35:11 +01:00
nodes : new _node _ids ,
links : new _links ,
2020-03-13 12:27:13 +01:00
groups : new _groups ,
2014-02-25 00:35:11 +01:00
workspaces : new _workspaces ,
subflows : new _subflows ,
2015-03-15 22:54:36 +01:00
dirty : RED . nodes . dirty ( )
2015-07-30 12:03:37 +02:00
} ;
2016-09-30 15:15:04 +02:00
if ( new _ms . length === 0 ) {
RED . nodes . dirty ( true ) ;
}
2015-07-30 12:03:37 +02:00
if ( activeSubflow ) {
var subflowRefresh = RED . subflow . refresh ( true ) ;
if ( subflowRefresh ) {
historyEvent . subflow = {
id : activeSubflow . id ,
changed : activeSubflowChanged ,
instances : subflowRefresh . instances
}
}
}
RED . history . push ( historyEvent ) ;
2013-09-05 16:02:48 +02:00
2015-03-12 12:21:05 +01:00
updateActiveNodes ( ) ;
2013-09-05 16:02:48 +02:00
redraw ( ) ;
2018-10-24 12:07:48 +02:00
var counts = [ ] ;
var newNodeCount = 0 ;
var newConfigNodeCount = 0 ;
new _nodes . forEach ( function ( n ) {
if ( n . hasOwnProperty ( "x" ) && n . hasOwnProperty ( "y" ) ) {
newNodeCount ++ ;
} else {
newConfigNodeCount ++ ;
}
} )
2020-03-26 16:22:59 +01:00
var newGroupCount = new _groups . length ;
2018-10-24 12:07:48 +02:00
if ( new _workspaces . length > 0 ) {
counts . push ( RED . _ ( "clipboard.flow" , { count : new _workspaces . length } ) ) ;
}
if ( newNodeCount > 0 ) {
counts . push ( RED . _ ( "clipboard.node" , { count : newNodeCount } ) ) ;
}
2020-03-26 16:22:59 +01:00
if ( newGroupCount > 0 ) {
counts . push ( RED . _ ( "clipboard.group" , { count : newGroupCount } ) ) ;
}
2018-10-24 12:07:48 +02:00
if ( newConfigNodeCount > 0 ) {
counts . push ( RED . _ ( "clipboard.configNode" , { count : newNodeCount } ) ) ;
}
if ( new _subflows . length > 0 ) {
counts . push ( RED . _ ( "clipboard.subflow" , { count : new _subflows . length } ) ) ;
}
if ( counts . length > 0 ) {
var countList = "<ul><li>" + counts . join ( "</li><li>" ) + "</li></ul>" ;
2019-01-09 15:02:46 +01:00
RED . notify ( "<p>" + RED . _ ( "clipboard.nodesImported" ) + "</p>" + countList , { id : "clipboard" } ) ;
2018-10-24 12:07:48 +02:00
}
2013-09-05 16:02:48 +02:00
}
} catch ( error ) {
2014-02-25 00:35:11 +01:00
if ( error . code != "NODE_RED" ) {
console . log ( error . stack ) ;
2015-07-13 17:28:23 +02:00
RED . notify ( RED . _ ( "notification.error" , { message : error . toString ( ) } ) , "error" ) ;
2014-02-25 00:35:11 +01:00
} else {
2015-07-13 17:28:23 +02:00
RED . notify ( RED . _ ( "notification.error" , { message : error . message } ) , "error" ) ;
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
}
}
2016-12-04 23:59:43 +01:00
function toggleShowGrid ( state ) {
if ( state ) {
2019-05-01 23:41:20 +02:00
gridLayer . style ( "visibility" , "visible" ) ;
2016-12-04 23:59:43 +01:00
} else {
2019-05-01 23:41:20 +02:00
gridLayer . style ( "visibility" , "hidden" ) ;
2016-12-04 23:59:43 +01:00
}
}
function toggleSnapGrid ( state ) {
snapGrid = state ;
redraw ( ) ;
}
function toggleStatus ( s ) {
showStatus = s ;
2019-05-16 15:42:41 +02:00
RED . nodes . eachNode ( function ( n ) { n . dirtyStatus = true ; n . dirty = true ; } ) ;
2016-12-04 23:59:43 +01:00
//TODO: subscribe/unsubscribe here
redraw ( ) ;
}
2019-06-17 23:46:34 +02:00
function setSelectedNodeState ( isDisabled ) {
if ( mouse _mode === RED . state . SELECTING _NODE ) {
return ;
}
var workspaceSelection = RED . workspaces . selection ( ) ;
var changed = false ;
if ( workspaceSelection . length > 0 ) {
// TODO: toggle workspace state
} else if ( moving _set . length > 0 ) {
var historyEvents = [ ] ;
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
var node = moving _set [ i ] . n ;
2020-03-26 16:27:34 +01:00
if ( node . type !== "group" && node . type !== "subflow" ) {
if ( isDisabled != node . d ) {
historyEvents . push ( {
t : "edit" ,
node : node ,
changed : node . changed ,
changes : {
d : node . d
}
} ) ;
if ( isDisabled ) {
node . d = true ;
} else {
delete node . d ;
2019-06-17 23:46:34 +02:00
}
2020-03-26 16:27:34 +01:00
node . dirty = true ;
node . changed = true ;
2020-04-20 22:30:03 +02:00
RED . events . emit ( "nodes:change" , node ) ;
2019-06-17 23:46:34 +02:00
}
}
}
if ( historyEvents . length > 0 ) {
RED . history . push ( {
t : "multi" ,
events : historyEvents ,
dirty : RED . nodes . dirty ( )
} )
RED . nodes . dirty ( true )
}
}
RED . view . redraw ( ) ;
}
2020-03-16 12:16:18 +01:00
function getSelection ( ) {
var selection = { } ;
2016-12-04 23:59:43 +01:00
2020-03-16 12:16:18 +01:00
var allNodes = new Set ( ) ;
if ( moving _set . length > 0 ) {
moving _set . forEach ( function ( n ) {
2020-03-26 16:27:34 +01:00
if ( n . n . type !== 'group' ) {
allNodes . add ( n . n ) ;
}
2020-03-16 12:16:18 +01:00
} ) ;
}
2020-03-26 16:27:34 +01:00
var selectedGroups = activeGroups . filter ( function ( g ) { return g . selected && ! g . active } ) ;
2020-03-16 12:16:18 +01:00
if ( selectedGroups . length > 0 ) {
if ( selectedGroups . length === 1 && selectedGroups [ 0 ] . active ) {
// Let nodes be nodes
} else {
selectedGroups . forEach ( function ( g ) {
var groupNodes = RED . group . getNodes ( g , true ) ;
groupNodes . forEach ( function ( n ) {
allNodes . delete ( n ) ;
} ) ;
allNodes . add ( g ) ;
} ) ;
}
}
if ( allNodes . size > 0 ) {
selection . nodes = Array . from ( allNodes ) ;
}
if ( selected _link != null ) {
selection . link = selected _link ;
}
return selection ;
}
2013-09-05 16:02:48 +02:00
return {
2014-11-11 11:15:02 +01:00
init : init ,
2013-09-05 16:02:48 +02:00
state : function ( state ) {
if ( state == null ) {
return mouse _mode
} else {
mouse _mode = state ;
}
} ,
2015-07-01 00:42:03 +02:00
2020-03-16 23:51:54 +01:00
updateActive : updateActiveNodes ,
2020-03-31 16:58:51 +02:00
redraw : function ( updateActive , syncRedraw ) {
2015-03-12 12:21:05 +01:00
if ( updateActive ) {
updateActiveNodes ( ) ;
2016-05-17 10:16:58 +02:00
updateSelection ( ) ;
2015-03-12 12:21:05 +01:00
}
2020-03-31 16:58:51 +02:00
if ( syncRedraw ) {
_redraw ( ) ;
} else {
redraw ( ) ;
}
2014-02-25 00:35:11 +01:00
} ,
2015-03-02 23:55:34 +01:00
focus : focusView ,
2013-10-23 11:44:08 +02:00
importNodes : importNodes ,
2014-10-09 11:05:45 +02:00
calculateTextWidth : calculateTextWidth ,
2015-03-04 14:19:13 +01:00
select : function ( selection ) {
2015-03-12 01:08:47 +01:00
if ( typeof selection !== "undefined" ) {
clearSelection ( ) ;
if ( typeof selection == "string" ) {
var selectedNode = RED . nodes . node ( selection ) ;
if ( selectedNode ) {
selectedNode . selected = true ;
selectedNode . dirty = true ;
moving _set = [ { n : selectedNode } ] ;
}
2020-03-03 20:04:32 +01:00
} else if ( selection ) {
2020-03-05 11:43:28 +01:00
if ( selection . nodes ) {
2020-03-03 20:04:32 +01:00
updateActiveNodes ( ) ;
2020-03-05 11:43:28 +01:00
moving _set = [ ] ;
// TODO: this selection group span groups
// - if all in one group -> activate the group
// - if in multiple groups (or group/no-group)
// -> select the first 'set' of things in the same group/no-group
selection . nodes . forEach ( function ( n ) {
if ( n . type !== "group" ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
} else {
selectGroup ( n , true ) ;
}
2020-03-03 20:04:32 +01:00
} )
}
2015-03-04 14:19:13 +01:00
}
}
updateSelection ( ) ;
2020-03-03 20:04:32 +01:00
redraw ( true ) ;
2016-01-07 15:39:01 +01:00
} ,
2020-03-16 12:16:18 +01:00
selection : getSelection ,
2020-03-04 22:48:38 +01:00
2016-01-08 23:34:10 +01:00
scale : function ( ) {
return scaleFactor ;
2016-01-10 23:25:20 +01:00
} ,
getLinksAtPoint : function ( x , y ) {
var result = [ ] ;
2019-05-15 14:54:29 +02:00
var links = outer . selectAll ( ".red-ui-flow-link-background" ) [ 0 ] ;
2016-01-10 23:25:20 +01:00
for ( var i = 0 ; i < links . length ; i ++ ) {
var bb = links [ i ] . getBBox ( ) ;
if ( x >= bb . x && y >= bb . y && x <= bb . x + bb . width && y <= bb . y + bb . height ) {
result . push ( links [ i ] )
}
}
return result ;
2016-09-30 00:46:29 +02:00
} ,
2020-03-14 00:01:01 +01:00
getGroupAtPoint : getGroupAt ,
2020-03-23 15:51:18 +01:00
getActiveGroup : function ( ) { return activeGroup } ,
2020-05-14 23:08:25 +02:00
reveal : function ( id , triggerHighlight ) {
2016-09-30 00:46:29 +02:00
if ( RED . nodes . workspace ( id ) || RED . nodes . subflow ( id ) ) {
RED . workspaces . show ( id ) ;
} else {
2020-05-12 11:59:41 +02:00
var node = RED . nodes . node ( id ) || RED . nodes . group ( id ) ;
2020-04-27 12:03:43 +02:00
if ( node ) {
2020-05-12 11:59:41 +02:00
if ( node . z && ( node . type === "group" || node . _def . category !== 'config' ) ) {
2020-04-27 12:03:43 +02:00
node . dirty = true ;
RED . workspaces . show ( node . z ) ;
var screenSize = [ chart . width ( ) / scaleFactor , chart . height ( ) / scaleFactor ] ;
var scrollPos = [ chart . scrollLeft ( ) / scaleFactor , chart . scrollTop ( ) / scaleFactor ] ;
2020-05-14 23:08:25 +02:00
var cx = node . x ;
var cy = node . y ;
if ( node . type === "group" ) {
cx += node . w / 2 ;
cy += node . h / 2 ;
}
if ( cx < scrollPos [ 0 ] || cy < scrollPos [ 1 ] || cx > screenSize [ 0 ] + scrollPos [ 0 ] || cy > screenSize [ 1 ] + scrollPos [ 1 ] ) {
var deltaX = '-=' + ( ( ( scrollPos [ 0 ] - cx ) + screenSize [ 0 ] / 2 ) * scaleFactor ) ;
var deltaY = '-=' + ( ( ( scrollPos [ 1 ] - cy ) + screenSize [ 1 ] / 2 ) * scaleFactor ) ;
2020-04-27 12:03:43 +02:00
chart . animate ( {
scrollLeft : deltaX ,
scrollTop : deltaY
} , 200 ) ;
}
2020-05-14 23:08:25 +02:00
if ( triggerHighlight !== false ) {
node . highlighted = true ;
if ( ! node . _flashing ) {
node . _flashing = true ;
var flash = 22 ;
var flashFunc = function ( ) {
flash -- ;
node . dirty = true ;
if ( flash >= 0 ) {
node . highlighted = ! node . highlighted ;
setTimeout ( flashFunc , 100 ) ;
} else {
node . highlighted = false ;
delete node . _flashing ;
}
RED . view . redraw ( ) ;
2020-04-27 12:03:43 +02:00
}
2020-05-14 23:08:25 +02:00
flashFunc ( ) ;
2016-10-24 17:53:09 +02:00
}
2016-09-30 00:46:29 +02:00
}
2020-04-27 12:03:43 +02:00
} else if ( node . _def . category === 'config' ) {
RED . sidebar . config . show ( id ) ;
2016-09-30 00:46:29 +02:00
}
}
}
2017-04-28 21:49:01 +02:00
} ,
gridSize : function ( v ) {
if ( v === undefined ) {
return gridSize ;
} else {
2017-12-04 12:42:44 +01:00
gridSize = Math . max ( 5 , v ) ;
2017-04-28 21:49:01 +02:00
updateGrid ( ) ;
}
2018-06-09 00:32:17 +02:00
} ,
getActiveNodes : function ( ) {
return activeNodes ;
2019-05-23 17:39:06 +02:00
} ,
selectNodes : function ( options ) {
$ ( "#red-ui-workspace-tabs-shade" ) . show ( ) ;
$ ( "#red-ui-palette-shade" ) . show ( ) ;
$ ( "#red-ui-sidebar-shade" ) . show ( ) ;
$ ( "#red-ui-header-shade" ) . show ( ) ;
$ ( "#red-ui-workspace" ) . addClass ( "red-ui-workspace-select-mode" ) ;
mouse _mode = RED . state . SELECTING _NODE ;
clearSelection ( ) ;
if ( options . selected ) {
options . selected . forEach ( function ( id ) {
var n = RED . nodes . node ( id ) ;
if ( n ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
} )
}
redraw ( ) ;
selectNodesOptions = options || { } ;
var closeNotification = function ( ) {
clearSelection ( ) ;
$ ( "#red-ui-workspace-tabs-shade" ) . hide ( ) ;
$ ( "#red-ui-palette-shade" ) . hide ( ) ;
$ ( "#red-ui-sidebar-shade" ) . hide ( ) ;
$ ( "#red-ui-header-shade" ) . hide ( ) ;
$ ( "#red-ui-workspace" ) . removeClass ( "red-ui-workspace-select-mode" ) ;
resetMouseVars ( ) ;
notification . close ( ) ;
}
2019-05-24 00:38:42 +02:00
selectNodesOptions . done = function ( selection ) {
closeNotification ( ) ;
if ( selectNodesOptions . onselect ) {
selectNodesOptions . onselect ( selection ) ;
}
}
var buttons = [ {
text : RED . _ ( "common.label.cancel" ) ,
click : function ( e ) {
closeNotification ( ) ;
if ( selectNodesOptions . oncancel ) {
selectNodesOptions . oncancel ( ) ;
}
}
} ] ;
if ( ! selectNodesOptions . single ) {
buttons . push ( {
text : RED . _ ( "common.label.done" ) ,
class : "primary" ,
click : function ( e ) {
var selection = moving _set . map ( function ( n ) { return n . n ; } ) ;
2019-05-24 11:26:00 +02:00
selectNodesOptions . done ( selection ) ;
2019-05-24 00:38:42 +02:00
}
} ) ;
}
2019-05-23 17:39:06 +02:00
var notification = RED . notify ( selectNodesOptions . prompt || RED . _ ( "workspace.selectNodes" ) , {
modal : false ,
fixed : true ,
type : "compact" ,
2019-05-24 00:38:42 +02:00
buttons : buttons
2019-05-23 17:39:06 +02:00
} )
2019-11-13 11:06:25 +01:00
} ,
scroll : function ( x , y ) {
chart . scrollLeft ( chart . scrollLeft ( ) + x ) ;
chart . scrollTop ( chart . scrollTop ( ) + y )
2020-04-27 12:03:43 +02:00
} ,
clickNodeButton : function ( n ) {
if ( n . _def . button ) {
nodeButtonClicked ( n ) ;
}
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
} ;
2014-08-08 01:01:35 +02:00
} ) ( ) ;