2013-09-05 16:02:48 +02:00
/ * *
2016-01-13 00:08:13 +01:00
* Copyright 2013 , 2016 IBM Corp .
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
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 ;
2014-02-25 00:35:11 +01:00
var activeSubflow = null ;
2015-03-12 12:21:05 +01:00
var activeNodes = [ ] ;
var activeLinks = [ ] ;
2015-07-01 00:42:03 +02:00
2013-09-05 16:02:48 +02:00
var selected _link = null ,
mousedown _link = null ,
mousedown _node = null ,
mousedown _port _type = null ,
mousedown _port _index = 0 ,
mouseup _node = null ,
mouse _offset = [ 0 , 0 ] ,
mouse _position = null ,
mouse _mode = 0 ,
moving _set = [ ] ,
lasso = null ,
2014-05-11 00:33:02 +02:00
showStatus = false ,
2014-07-27 23:08:27 +02:00
lastClickNode = null ,
dblClickPrimed = null ,
2014-05-06 00:28:24 +02:00
clickTime = 0 ,
2014-05-15 23:49:07 +02:00
clickElapsed = 0 ;
2014-05-13 00:57:14 +02:00
2013-09-05 16:02:48 +02:00
var clipboard = "" ;
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
2013-09-05 16:02:48 +02:00
var outer = d3 . select ( "#chart" )
. append ( "svg:svg" )
. attr ( "width" , space _width )
. attr ( "height" , space _height )
2015-03-02 23:55:34 +01:00
. attr ( "tabindex" , 1 )
2013-09-05 16:02:48 +02:00
. attr ( "pointer-events" , "all" )
2015-03-02 23:55:34 +01:00
. style ( "cursor" , "crosshair" )
. on ( "mousedown" , function ( ) {
2015-06-17 23:52:04 +02:00
$ ( this ) . focus ( ) ;
2015-03-02 23:55:34 +01:00
} ) ;
2015-07-01 00:42:03 +02:00
2015-03-02 23:55:34 +01:00
var vis = outer
2014-05-13 00:57:14 +02:00
. append ( 'svg:g' )
. on ( "dblclick.zoom" , null )
. append ( 'svg:g' )
. on ( "mousemove" , canvasMouseMove )
. on ( "mousedown" , canvasMouseDown )
. on ( "mouseup" , canvasMouseUp )
2014-05-13 01:42:24 +02:00
. on ( "touchend" , function ( ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
2014-05-15 23:49:07 +02:00
if ( RED . touch . radialMenu . active ( ) ) {
return ;
}
2014-05-13 01:42:24 +02:00
if ( lasso ) {
outer _background . attr ( "fill" , "#fff" ) ;
}
canvasMouseUp . call ( this ) ;
} )
2014-05-13 00:57:14 +02:00
. on ( "touchcancel" , canvasMouseUp )
2014-05-15 23:49:07 +02:00
. on ( "touchstart" , function ( ) {
2014-08-08 01:01:35 +02:00
var touch0 ;
2014-05-13 00:57:14 +02:00
if ( d3 . event . touches . length > 1 ) {
2014-05-15 23:56:12 +02:00
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
2014-05-13 00:57:14 +02:00
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 ) ;
var a = touch0 [ 'pageY' ] - touch1 [ 'pageY' ] ;
var b = touch0 [ 'pageX' ] - touch1 [ 'pageX' ] ;
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 = [
touch1 [ 'pageX' ] + ( b / 2 ) ,
touch1 [ 'pageY' ] + ( a / 2 )
]
startTouchDistance = Math . sqrt ( ( a * a ) + ( b * b ) ) ;
} else {
2014-05-15 23:49:07 +02:00
var obj = d3 . select ( document . body ) ;
2014-08-08 01:01:35 +02:00
touch0 = d3 . event . touches . item ( 0 ) ;
2014-05-15 23:49:07 +02:00
var pos = [ touch0 . pageX , touch0 . pageY ] ;
2014-05-13 01:42:24 +02:00
startTouchCenter = [ touch0 . pageX , touch0 . pageY ] ;
2014-05-13 00:57:14 +02:00
startTouchDistance = 0 ;
2014-05-13 01:42:24 +02:00
var point = d3 . touches ( this ) [ 0 ] ;
touchStartTime = setTimeout ( function ( ) {
2014-05-15 23:49:07 +02:00
touchStartTime = null ;
showTouchMenu ( obj , pos ) ;
//lasso = vis.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)
// .attr("class","lasso");
//outer_background.attr("fill","#e3e3f3");
} , touchLongPressTimeout ) ;
2014-05-13 00:57:14 +02:00
}
} )
. on ( "touchmove" , function ( ) {
2014-05-15 23:49:07 +02:00
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . preventDefault ( ) ;
return ;
}
2014-08-08 01:01:35 +02:00
var touch0 ;
2014-05-13 00:57:14 +02:00
if ( d3 . event . touches . length < 2 ) {
2014-05-13 01:42:24 +02:00
if ( touchStartTime ) {
2014-08-08 01:01:35 +02:00
touch0 = d3 . event . touches . item ( 0 ) ;
2014-05-13 01:42:24 +02:00
var dx = ( touch0 . pageX - startTouchCenter [ 0 ] ) ;
var dy = ( touch0 . pageY - startTouchCenter [ 1 ] ) ;
2014-05-15 23:49:07 +02:00
var d = Math . abs ( dx * dx + dy * dy ) ;
if ( d > 64 ) {
2014-05-13 01:42:24 +02:00
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
}
} else if ( lasso ) {
d3 . event . preventDefault ( ) ;
}
2014-05-13 00:57:14 +02:00
canvasMouseMove . call ( this ) ;
} else {
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 ) ;
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 )
] ;
if ( ! isNaN ( moveTouchDistance ) ) {
oldScaleFactor = scaleFactor ;
scaleFactor = Math . min ( 2 , Math . max ( 0.3 , scaleFactor + ( Math . floor ( ( ( moveTouchDistance * 100 ) - ( startTouchDistance * 100 ) ) ) / 10000 ) ) ) ;
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])
] ;
startTouchDistance = moveTouchDistance ;
moveTouchCenter = touchCenter ;
2014-05-14 15:22:28 +02:00
2014-05-13 00:57:14 +02:00
$ ( "#chart" ) . scrollLeft ( scrollPos [ 0 ] + deltaTouchCenter [ 0 ] ) ;
$ ( "#chart" ) . scrollTop ( scrollPos [ 1 ] + deltaTouchCenter [ 1 ] ) ;
redraw ( ) ;
}
}
2014-05-12 15:19:54 +02:00
} ) ;
2013-09-05 16:02:48 +02:00
var outer _background = vis . append ( 'svg:rect' )
. attr ( 'width' , space _width )
. attr ( 'height' , space _height )
. attr ( 'fill' , '#fff' ) ;
2016-01-07 15:39:01 +01:00
var gridScale = d3 . scale . linear ( ) . range ( [ 0 , space _width ] ) . domain ( [ 0 , space _width ] ) ;
var grid = vis . append ( 'g' ) ;
grid . selectAll ( "line.horizontal" ) . data ( gridScale . ticks ( space _width / gridSize ) ) . enter ( )
. append ( "line" )
. attr (
{
"class" : "horizontal" ,
"x1" : 0 ,
"x2" : space _width ,
"y1" : function ( d ) { return gridScale ( d ) ; } ,
"y2" : function ( d ) { return gridScale ( d ) ; } ,
"fill" : "none" ,
"shape-rendering" : "crispEdges" ,
"stroke" : "#eee" ,
"stroke-width" : "1px"
} ) ;
grid . selectAll ( "line.vertical" ) . data ( gridScale . ticks ( space _width / gridSize ) ) . enter ( )
. append ( "line" )
. attr (
{
"class" : "vertical" ,
"y1" : 0 ,
"y2" : space _width ,
"x1" : function ( d ) { return gridScale ( d ) ; } ,
"x2" : function ( d ) { return gridScale ( d ) ; } ,
"fill" : "none" ,
"shape-rendering" : "crispEdges" ,
"stroke" : "#eee" ,
"stroke-width" : "1px"
} ) ;
grid . style ( "visibility" , "hidden" ) ;
2014-04-03 01:05:16 +02:00
2016-01-09 01:29:04 +01:00
var dragGroup = vis . append ( 'g' ) ;
var drag _lines = [ ] ;
function showDragLines ( nodes ) {
for ( var i = 0 ; i < nodes . length ; i ++ ) {
var node = nodes [ i ] ;
node . el = dragGroup . append ( "svg:path" ) . attr ( "class" , "drag_line" ) ;
drag _lines . push ( node ) ;
}
}
function hideDragLines ( ) {
while ( drag _lines . length ) {
( drag _lines . pop ( ) ) . el . remove ( ) ;
}
}
2013-09-05 16:02:48 +02:00
2015-03-12 12:21:05 +01:00
function updateActiveNodes ( ) {
2015-03-16 00:31:38 +01:00
var activeWorkspace = RED . workspaces . active ( ) ;
2015-07-01 00:42:03 +02:00
2015-03-16 00:31:38 +01:00
activeNodes = RED . nodes . filterNodes ( { z : activeWorkspace } ) ;
2015-07-01 00:42:03 +02:00
2015-03-16 00:31:38 +01:00
activeLinks = RED . nodes . filterLinks ( {
source : { z : activeWorkspace } ,
target : { z : activeWorkspace }
2015-03-12 12:21:05 +01:00
} ) ;
}
2015-03-13 00:38:37 +01:00
function init ( ) {
2015-07-10 20:49:31 +02:00
RED . events . on ( "workspace:change" , function ( event ) {
2014-02-25 00:42:24 +01:00
var chart = $ ( "#chart" ) ;
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 ;
}
clearSelection ( ) ;
RED . nodes . eachNode ( function ( n ) {
2015-03-12 12:21:05 +01:00
n . dirty = 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
2015-03-14 23:53:31 +01:00
$ ( '#btn-zoom-out' ) . click ( function ( ) { zoomOut ( ) ; } ) ;
$ ( '#btn-zoom-zero' ) . click ( function ( ) { zoomZero ( ) ; } ) ;
$ ( '#btn-zoom-in' ) . click ( function ( ) { zoomIn ( ) ; } ) ;
$ ( "#chart" ) . on ( 'DOMMouseScroll mousewheel' , function ( evt ) {
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
$ ( "#chart" ) . droppable ( {
accept : ".palette_node" ,
drop : function ( event , ui ) {
d3 . event = event ;
var selected _tool = ui . draggable [ 0 ] . type ;
var m = /^subflow:(.+)$/ . exec ( selected _tool ) ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
if ( activeSubflow && m ) {
var subflowId = m [ 1 ] ;
if ( subflowId === activeSubflow . id ) {
2015-07-01 00:42:03 +02:00
RED . notify ( RED . _ ( "notification.error" , { message : RED . _ ( "notification.errors.cannotAddSubflowToItself" ) } ) , "error" ) ;
2015-03-14 23:53:31 +01:00
return ;
}
if ( RED . nodes . subflowContains ( m [ 1 ] , activeSubflow . id ) ) {
2015-07-01 00:42:03 +02:00
RED . notify ( RED . _ ( "notification.error" , { message : RED . _ ( "notification.errors.cannotAddCircularReference" ) } ) , "error" ) ;
2015-03-14 23:53:31 +01:00
return ;
}
2016-01-07 15:39:01 +01:00
}
2016-01-08 15:42:05 +01:00
var nn = { id : ( 1 + Math . random ( ) * 4294967295 ) . toString ( 16 ) , z : RED . workspaces . active ( ) } ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
nn . type = selected _tool ;
nn . _def = RED . nodes . getType ( nn . type ) ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
if ( ! m ) {
nn . inputs = nn . _def . inputs || 0 ;
nn . outputs = nn . _def . outputs ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
for ( var d in nn . _def . defaults ) {
if ( nn . _def . defaults . hasOwnProperty ( d ) ) {
nn [ d ] = nn . _def . defaults [ d ] . value ;
}
}
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
if ( nn . _def . onadd ) {
2016-03-01 22:58:57 +01:00
try {
nn . _def . onadd . call ( nn ) ;
} catch ( err ) {
console . log ( "onadd:" , err ) ;
}
2015-03-14 23:53:31 +01:00
}
} else {
var subflow = RED . nodes . subflow ( m [ 1 ] ) ;
nn . inputs = subflow . in . length ;
nn . outputs = subflow . out . length ;
}
2015-07-01 00:42:03 +02:00
2015-03-20 22:20:04 +01:00
nn . changed = true ;
2016-01-08 15:42:05 +01:00
nn . w = node _width ;
2015-03-14 23:53:31 +01:00
nn . h = Math . max ( node _height , ( nn . outputs || 0 ) * 15 ) ;
2016-01-08 15:42:05 +01:00
2015-07-30 12:03:37 +02: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
}
}
}
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-01-08 23:34:10 +01:00
var spliceLink = $ ( ui . helper ) . data ( 'splice' ) ;
if ( spliceLink ) {
2016-01-14 16:59:45 +01:00
// TODO: DRY - canvasMouseUp
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
2015-07-30 12:03:37 +02:00
RED . history . push ( historyEvent ) ;
2015-03-14 23:53:31 +01:00
RED . nodes . add ( nn ) ;
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)
clearSelection ( ) ;
nn . selected = true ;
moving _set . push ( { n : nn } ) ;
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 ) ;
}
}
} ) ;
2015-07-01 00:42:03 +02:00
2015-03-14 23:53:31 +01:00
RED . keyboard . add ( /* z */ 90 , { ctrl : true } , function ( ) { RED . history . pop ( ) ; } ) ;
RED . keyboard . add ( /* a */ 65 , { ctrl : true } , function ( ) { selectAll ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* = */ 187 , { ctrl : true } , function ( ) { zoomIn ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* - */ 189 , { ctrl : true } , function ( ) { zoomOut ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* 0 */ 48 , { ctrl : true } , function ( ) { zoomZero ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* v */ 86 , { ctrl : true } , function ( ) { importNodes ( clipboard ) ; d3 . event . preventDefault ( ) ; } ) ;
2013-10-28 21:28:44 +01:00
}
2013-11-19 09:48:44 +01:00
2013-09-05 16:02:48 +02:00
function canvasMouseDown ( ) {
if ( ! mousedown _node && ! mousedown _link ) {
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 ;
}
2014-10-09 11:05:45 +02:00
2014-05-15 23:49:07 +02:00
if ( ! touchStartTime ) {
var point = d3 . mouse ( this ) ;
2014-05-09 00:00:11 +02:00
lasso = vis . append ( 'rect' )
. attr ( "ox" , point [ 0 ] )
. attr ( "oy" , point [ 1 ] )
2015-09-23 23:49:48 +02:00
. attr ( "rx" , 1 )
. attr ( "ry" , 1 )
2014-05-09 00:00:11 +02:00
. attr ( "x" , point [ 0 ] )
. attr ( "y" , point [ 1 ] )
. attr ( "width" , 0 )
. attr ( "height" , 0 )
. attr ( "class" , "lasso" ) ;
d3 . event . preventDefault ( ) ;
2013-10-28 11:01:12 +01:00
}
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 ;
2013-09-05 16:02:48 +02:00
mouse _position = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
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);
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 ;
}
2014-08-08 01:01:35 +02:00
if ( mouse _mode != RED . state . IMPORT _DRAGGING && ! mousedown _node && selected _link == null ) {
return ;
}
2013-09-05 16:02:48 +02:00
2014-08-08 01:01:35 +02:00
var mousePos ;
2013-09-05 16:02:48 +02:00
if ( mouse _mode == RED . state . JOINING ) {
// update drag line
2016-01-09 01:29:04 +01:00
if ( drag _lines . length === 0 ) {
if ( d3 . event . shiftKey ) {
// Get all the wires we need to detach.
var links = [ ] ;
var filter ;
if ( mousedown _port _type === 0 ) {
filter = {
source : mousedown _node ,
sourcePort : mousedown _port _index
}
} else {
filter = {
target : mousedown _node
}
}
var existingLinks = RED . nodes . filterLinks ( filter ) ;
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 ,
node : ( mousedown _port _type === 0 ) ? link . target : link . source ,
port : ( mousedown _port _type === 0 ) ? 0 : link . sourcePort ,
portType : ( mousedown _port _type === 0 ) ? 1 : 0
} )
}
showDragLines ( links ) ;
mouse _mode = 0 ;
updateActiveNodes ( ) ;
redraw ( ) ;
mouse _mode = RED . state . JOINING ;
} else {
showDragLines ( [ { node : mousedown _node , port : mousedown _port _index , portType : mousedown _port _type } ] ) ;
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 ] ;
var numOutputs = ( drag _line . portType === 0 ) ? ( drag _line . node . outputs || 1 ) : 1 ;
var sourcePort = drag _line . port ;
var portY = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
var sc = ( drag _line . portType === 0 ) ? 1 : - 1 ;
var dy = mousePos [ 1 ] - ( drag _line . node . y + portY ) ;
var dx = mousePos [ 0 ] - ( drag _line . node . x + sc * drag _line . node . w / 2 ) ;
var delta = Math . sqrt ( dy * dy + dx * dx ) ;
var scale = lineCurveScale ;
var scaleY = 0 ;
if ( delta < node _width ) {
scale = 0.75 - 0.75 * ( ( node _width - delta ) / node _width ) ;
}
if ( dx * sc < 0 ) {
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 ) ) ;
}
}
2013-10-28 11:01:12 +01:00
2016-01-09 01:29:04 +01:00
drag _line . el . attr ( "d" ,
"M " + ( drag _line . node . x + sc * drag _line . node . w / 2 ) + " " + ( drag _line . node . y + portY ) +
" C " + ( drag _line . node . x + sc * ( drag _line . node . w / 2 + node _width * scale ) ) + " " + ( drag _line . node . y + portY + scaleY * node _height ) + " " +
( mousePos [ 0 ] - sc * ( scale ) * node _width ) + " " + ( mousePos [ 1 ] - scaleY * node _height ) + " " +
mousePos [ 0 ] + " " + mousePos [ 1 ]
) ;
}
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 ] ;
spliceActive = node . n . _def . inputs > 0 &&
node . n . _def . outputs > 0 &&
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 ;
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 ;
2013-12-28 21:03:43 +01:00
minX = Math . min ( node . n . x - node . n . w / 2 - 5 , minX ) ;
minY = Math . min ( node . n . y - node . n . h / 2 - 5 , minY ) ;
}
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-01-13 10:16:24 +01:00
if ( snapGrid != d3 . event . shiftKey && moving _set . length > 0 ) {
2016-01-07 15:39:01 +01:00
var gridOffset = [ 0 , 0 ] ;
2014-08-08 01:01:35 +02:00
node = moving _set [ 0 ] ;
2016-01-07 15:39:01 +01:00
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
}
2016-01-14 16:59:45 +01:00
if ( mouse _mode == RED . state . MOVING _ACTIVE && moving _set . length === 1 ) {
node = moving _set [ 0 ] ;
if ( spliceActive ) {
if ( ! spliceTimer ) {
spliceTimer = setTimeout ( function ( ) {
var nodes = [ ] ;
var bestDistance = Infinity ;
var bestLink = null ;
var mouseX = mousePos [ 0 ] ;
var mouseY = mousePos [ 1 ] ;
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 {
// Firefox doesn't do getIntersectionList and that
// makes us sad
nodes = RED . view . getLinksAtPoint ( mouseX , mouseY ) ;
}
for ( var i = 0 ; i < nodes . length ; i ++ ) {
if ( d3 . select ( nodes [ i ] ) . classed ( 'link_background' ) ) {
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 ) {
d3 . select ( activeSpliceLink . parentNode ) . classed ( 'link_splice' , false ) ;
}
if ( bestLink ) {
d3 . select ( bestLink . parentNode ) . classed ( 'link_splice' , true )
} else {
d3 . select ( '.link_splice' ) . classed ( 'link_splice' , false ) ;
}
activeSpliceLink = bestLink ;
spliceTimer = null ;
} , 100 ) ;
}
}
}
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 ( ) {
2016-01-09 01:31:05 +01:00
var i ;
2016-01-14 16:59:45 +01:00
var historyEvent ;
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 )
}
}
2016-01-14 16:59:45 +01:00
historyEvent = {
2016-01-09 01:29:04 +01:00
t : 'delete' ,
links : removedLinks ,
dirty : RED . nodes . dirty ( )
} ;
RED . history . push ( historyEvent ) ;
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" ) ) ;
if ( ! d3 . event . ctrlKey ) {
clearSelection ( ) ;
}
RED . nodes . eachNode ( function ( n ) {
2015-03-13 00:38:37 +01:00
if ( n . z == RED . workspaces . active ( ) && ! n . selected ) {
2013-09-05 16:02:48 +02:00
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 } ) ;
}
}
} ) ;
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 } ) ;
}
} ) ;
}
2013-09-05 16:02:48 +02:00
updateSelection ( ) ;
lasso . remove ( ) ;
lasso = null ;
2016-01-07 16:09:14 +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 ) {
var ns = [ ] ;
2014-08-08 01:01:35 +02:00
for ( var j = 0 ; j < moving _set . length ; j ++ ) {
ns . push ( { n : moving _set [ j ] . n , ox : moving _set [ j ] . ox , oy : moving _set [ j ] . oy } ) ;
2013-09-05 16:02:48 +02:00
}
2016-01-14 16:59:45 +01:00
historyEvent = { t : 'move' , nodes : ns , dirty : RED . nodes . dirty ( ) } ;
if ( activeSpliceLink ) {
// TODO: DRY - droppable
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 ( ) ;
}
2016-01-08 12:08:48 +01:00
RED . nodes . dirty ( true ) ;
2016-01-14 16:59:45 +01:00
RED . history . push ( historyEvent ) ;
2013-09-05 16:02:48 +02:00
}
}
2014-04-16 14:39:16 +02:00
if ( mouse _mode == RED . state . MOVING || mouse _mode == RED . state . MOVING _ACTIVE ) {
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 ) {
RED . keyboard . remove ( /* ESCAPE */ 27 ) ;
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 ) {
scaleFactor += 0.1 ;
redraw ( ) ;
}
}
function zoomOut ( ) {
if ( scaleFactor > 0.3 ) {
scaleFactor -= 0.1 ;
redraw ( ) ;
}
}
function zoomZero ( ) {
scaleFactor = 1 ;
redraw ( ) ;
}
function selectAll ( ) {
RED . nodes . eachNode ( function ( n ) {
2015-03-13 00:38:37 +01:00
if ( n . z == RED . workspaces . active ( ) ) {
2013-09-05 16:02:48 +02:00
if ( ! n . selected ) {
n . selected = true ;
n . dirty = true ;
moving _set . push ( { n : n } ) ;
}
2013-10-21 00:11:38 +02:00
}
2013-09-05 16:02:48 +02:00
} ) ;
2014-02-25 00:35:11 +01:00
if ( activeSubflow ) {
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 } ) ;
}
} ) ;
}
2015-07-01 00:42:03 +02:00
2013-09-05 16:02:48 +02:00
selected _link = null ;
updateSelection ( ) ;
redraw ( ) ;
}
function clearSelection ( ) {
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 ;
}
function updateSelection ( ) {
2014-08-08 01:01:35 +02:00
if ( moving _set . length === 0 && selected _link == null ) {
2013-09-05 16:02:48 +02:00
RED . keyboard . remove ( /* backspace */ 8 ) ;
RED . keyboard . remove ( /* delete */ 46 ) ;
RED . keyboard . remove ( /* c */ 67 ) ;
2014-04-03 01:05:16 +02:00
RED . keyboard . remove ( /* x */ 88 ) ;
2013-09-05 16:02:48 +02:00
} else {
RED . keyboard . add ( /* backspace */ 8 , function ( ) { deleteSelection ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* delete */ 46 , function ( ) { deleteSelection ( ) ; d3 . event . preventDefault ( ) ; } ) ;
2014-10-29 09:49:07 +01:00
RED . keyboard . add ( /* c */ 67 , { ctrl : true } , function ( ) { copySelection ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* x */ 88 , { ctrl : true } , function ( ) { copySelection ( ) ; deleteSelection ( ) ; d3 . event . preventDefault ( ) ; } ) ;
2013-09-05 16:02:48 +02:00
}
2014-08-08 01:01:35 +02:00
if ( moving _set . length === 0 ) {
2014-04-16 14:39:16 +02:00
RED . keyboard . remove ( /* up */ 38 ) ;
RED . keyboard . remove ( /* down */ 40 ) ;
RED . keyboard . remove ( /* left */ 37 ) ;
RED . keyboard . remove ( /* right*/ 39 ) ;
} else {
2014-08-08 01:01:35 +02:00
RED . keyboard . add ( /* up */ 38 , function ( ) { if ( d3 . event . shiftKey ) { moveSelection ( 0 , - 20 ) } else { moveSelection ( 0 , - 1 ) ; } d3 . event . preventDefault ( ) ; } , endKeyboardMove ) ;
RED . keyboard . add ( /* down */ 40 , function ( ) { if ( d3 . event . shiftKey ) { moveSelection ( 0 , 20 ) } else { moveSelection ( 0 , 1 ) ; } d3 . event . preventDefault ( ) ; } , endKeyboardMove ) ;
RED . keyboard . add ( /* left */ 37 , function ( ) { if ( d3 . event . shiftKey ) { moveSelection ( - 20 , 0 ) } else { moveSelection ( - 1 , 0 ) ; } d3 . event . preventDefault ( ) ; } , endKeyboardMove ) ;
RED . keyboard . add ( /* right*/ 39 , function ( ) { if ( d3 . event . shiftKey ) { moveSelection ( 20 , 0 ) } else { moveSelection ( 1 , 0 ) ; } d3 . event . preventDefault ( ) ; } , endKeyboardMove ) ;
2014-04-16 14:39:16 +02:00
}
2015-03-12 01:08:47 +01:00
2015-03-04 14:19:13 +01:00
var selection = { } ;
2015-07-01 00:42:03 +02:00
2015-03-04 14:19:13 +01:00
if ( moving _set . length > 0 ) {
selection . nodes = moving _set . map ( function ( n ) { return n . n ; } ) ;
}
if ( selected _link != null ) {
selection . link = selected _link ;
}
2015-07-10 20:49:31 +02:00
RED . events . emit ( "view:selection-changed" , selection ) ;
2013-09-05 16:02:48 +02:00
}
2015-07-01 00:42:03 +02:00
2014-04-16 14:39:16 +02:00
function endKeyboardMove ( ) {
var ns = [ ] ;
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
ns . push ( { n : moving _set [ i ] . n , ox : moving _set [ i ] . ox , oy : moving _set [ i ] . oy } ) ;
delete moving _set [ i ] . ox ;
delete moving _set [ i ] . oy ;
}
2015-03-15 22:54:36 +01:00
RED . history . push ( { t : 'move' , nodes : ns , dirty : RED . nodes . dirty ( ) } ) ;
2016-01-13 00:08:13 +01:00
RED . nodes . dirty ( true ) ;
2014-04-16 14:39:16 +02:00
}
function moveSelection ( dx , dy ) {
var minX = 0 ;
var minY = 0 ;
2014-08-08 01:01:35 +02:00
var node ;
2014-10-09 11:05:45 +02:00
2014-04-16 14:39:16 +02:00
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
2014-08-08 01:01:35 +02:00
node = moving _set [ i ] ;
2014-04-16 14:39:16 +02:00
if ( node . ox == null && node . oy == null ) {
node . ox = node . n . x ;
node . oy = node . n . y ;
}
node . n . x += dx ;
node . n . y += dy ;
node . n . dirty = true ;
minX = Math . min ( node . n . x - node . n . w / 2 - 5 , minX ) ;
minY = Math . min ( node . n . y - node . n . h / 2 - 5 , minY ) ;
}
2014-05-14 15:22:28 +02:00
2014-08-08 01:01:35 +02:00
if ( minX !== 0 || minY !== 0 ) {
2014-04-16 14:39:16 +02:00
for ( var n = 0 ; n < moving _set . length ; n ++ ) {
2014-08-08 01:01:35 +02:00
node = moving _set [ n ] ;
2014-04-16 14:39:16 +02:00
node . n . x -= minX ;
node . n . y -= minY ;
}
}
2014-05-14 15:22:28 +02:00
2014-04-16 14:39:16 +02:00
redraw ( ) ;
}
2013-09-05 16:02:48 +02:00
function deleteSelection ( ) {
2015-08-13 23:18:47 +02:00
var result ;
2013-09-05 16:02:48 +02:00
var removedNodes = [ ] ;
var removedLinks = [ ] ;
2014-11-13 00:51:42 +01:00
var removedSubflowOutputs = [ ] ;
2014-11-13 01:02:41 +01:00
var removedSubflowInputs = [ ] ;
2015-07-30 12:03:37 +02:00
var subflowInstances = [ ] ;
2015-07-01 00:42:03 +02:00
2015-03-15 22:54:36 +01:00
var startDirty = RED . nodes . dirty ( ) ;
2015-07-30 12:03:37 +02:00
var startChanged = false ;
2013-09-05 16:02:48 +02:00
if ( moving _set . length > 0 ) {
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 node = moving _set [ i ] . n ;
node . selected = false ;
2014-02-25 00:35:11 +01:00
if ( node . type != "subflow" ) {
if ( node . x < 0 ) {
node . x = 25
}
2015-07-08 23:12:52 +02:00
var removedEntities = RED . nodes . remove ( node . id ) ;
2014-02-25 00:35:11 +01:00
removedNodes . push ( node ) ;
2015-07-08 23:12:52 +02:00
removedNodes = removedNodes . concat ( removedEntities . nodes ) ;
removedLinks = removedLinks . concat ( removedEntities . links ) ;
2014-02-25 00:35:11 +01:00
} else {
2014-11-13 00:51:42 +01:00
if ( node . direction === "out" ) {
removedSubflowOutputs . push ( node ) ;
2014-11-13 01:02:41 +01:00
} else if ( node . direction === "in" ) {
removedSubflowInputs . push ( node ) ;
2014-11-13 00:51:42 +01:00
}
2014-02-25 00:35:11 +01:00
node . dirty = true ;
2014-08-08 01:01:35 +02:00
}
2013-09-05 16:02:48 +02:00
}
2014-11-13 17:00:46 +01:00
if ( removedSubflowOutputs . length > 0 ) {
2015-08-13 23:18:47 +02:00
result = RED . subflow . removeOutput ( removedSubflowOutputs ) ;
2015-07-30 12:03:37 +02:00
if ( result ) {
removedLinks = removedLinks . concat ( result . links ) ;
2014-11-13 00:51:42 +01:00
}
}
2014-11-13 01:02:41 +01:00
// Assume 0/1 inputs
if ( removedSubflowInputs . length == 1 ) {
2015-08-13 23:18:47 +02:00
result = RED . subflow . removeInput ( ) ;
2015-07-30 12:03:37 +02:00
if ( result ) {
removedLinks = removedLinks . concat ( result . links ) ;
}
2014-11-13 01:02:41 +01:00
}
2015-07-30 12:03:37 +02:00
var instances = RED . subflow . refresh ( true ) ;
if ( instances ) {
subflowInstances = instances . instances ;
2014-11-13 17:00:46 +01:00
}
2013-09-05 16:02:48 +02:00
moving _set = [ ] ;
2015-03-15 22:54:36 +01:00
if ( removedNodes . length > 0 || removedSubflowOutputs . length > 0 || removedSubflowInputs . length > 0 ) {
RED . nodes . dirty ( true ) ;
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
}
if ( selected _link ) {
RED . nodes . removeLink ( selected _link ) ;
removedLinks . push ( selected _link ) ;
2015-03-15 22:54:36 +01:00
RED . nodes . dirty ( true ) ;
2013-09-05 16:02:48 +02:00
}
2015-07-30 12:03:37 +02:00
var historyEvent = {
t : 'delete' ,
nodes : removedNodes ,
links : removedLinks ,
subflowOutputs : removedSubflowOutputs ,
subflowInputs : removedSubflowInputs ,
subflow : {
instances : subflowInstances
} ,
dirty : startDirty
} ;
RED . history . push ( historyEvent ) ;
2013-09-05 16:02:48 +02:00
selected _link = null ;
2015-03-12 12:21:05 +01:00
updateActiveNodes ( ) ;
2013-09-05 16:02:48 +02:00
updateSelection ( ) ;
redraw ( ) ;
}
function copySelection ( ) {
if ( moving _set . length > 0 ) {
var nns = [ ] ;
2014-08-08 01:01:35 +02:00
for ( var n = 0 ; n < moving _set . length ; n ++ ) {
2013-09-05 16:02:48 +02:00
var node = moving _set [ n ] . n ;
2015-07-08 23:12:52 +02:00
// The only time a node.type == subflow can be selected is the
// input/output 'proxy' nodes. They cannot be copied.
2014-02-25 00:35:11 +01:00
if ( node . type != "subflow" ) {
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 ) ;
2015-07-01 00:42:03 +02:00
RED . notify ( RED . _ ( "clipboard.nodeCopied" , { count : nns . length } ) ) ;
2013-09-05 16:02:48 +02:00
}
}
2014-02-27 17:47:28 +01:00
2014-10-09 11:05:45 +02:00
function calculateTextWidth ( str , className , offset ) {
2013-09-05 16:02:48 +02:00
var sp = document . createElement ( "span" ) ;
2014-10-09 11:05:45 +02:00
sp . className = className ;
2013-09-05 16:02:48 +02:00
sp . style . position = "absolute" ;
sp . style . top = "-1000px" ;
sp . innerHTML = ( str || "" ) . replace ( /&/g , "&" ) . replace ( /</g , "<" ) . replace ( />/g , ">" ) ;
document . body . appendChild ( sp ) ;
var w = sp . offsetWidth ;
document . body . removeChild ( sp ) ;
2014-10-09 11:05:45 +02:00
return offset + w ;
2013-09-05 16:02:48 +02:00
}
function resetMouseVars ( ) {
mousedown _node = null ;
mouseup _node = null ;
mousedown _link = null ;
mouse _mode = 0 ;
mousedown _port _type = 0 ;
2016-01-14 16:59:45 +01:00
activeSpliceLink = null ;
spliceActive = false ;
d3 . select ( '.link_splice' ) . classed ( 'link_splice' , false ) ;
if ( spliceTimer ) {
clearTimeout ( spliceTimer ) ;
spliceTimer = null ;
}
2013-09-05 16:02:48 +02:00
}
function portMouseDown ( d , portType , portIndex ) {
2014-02-25 00:35:11 +01:00
//console.log(d,portType,portIndex);
2013-09-05 16:02:48 +02:00
// disable zoom
2014-05-12 00:55:11 +02:00
//vis.call(d3.behavior.zoom().on("zoom"), null);
2013-09-05 16:02:48 +02:00
mousedown _node = d ;
selected _link = null ;
mouse _mode = RED . state . JOINING ;
mousedown _port _type = portType ;
mousedown _port _index = portIndex || 0 ;
document . body . style . cursor = "crosshair" ;
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 ) {
2016-01-09 01:29:04 +01:00
var i ;
2013-09-05 16:02:48 +02:00
document . body . style . cursor = "" ;
2016-01-09 01:29:04 +01:00
if ( mouse _mode == RED . state . JOINING && drag _lines . length > 0 ) {
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 ;
portType = mouseup _node . inputs > 0 ? 1 : 0 ;
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 = [ ] ;
for ( i = 0 ; i < drag _lines . length ; i ++ ) {
if ( drag _lines [ i ] . link ) {
removedLinks . push ( drag _lines [ i ] . link )
}
}
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 ;
if ( drag _line . portType === 0 ) {
src = drag _line . node ;
src _port = drag _line . port ;
dst = mouseup _node ;
} else if ( drag _line . portType == 1 ) {
src = mouseup _node ;
dst = drag _line . node ;
src _port = portIndex ;
}
var existingLink = RED . nodes . filterLinks ( { source : src , target : dst , sourcePort : src _port } ) . length !== 0 ;
if ( ! existingLink ) {
var link = { source : src , sourcePort : src _port , target : dst } ;
RED . nodes . addLink ( link ) ;
addedLinks . push ( link ) ;
}
}
2013-09-05 16:02:48 +02:00
}
2016-01-09 01:29:04 +01:00
if ( addedLinks . length > 0 || removedLinks . length > 0 ) {
2015-07-30 12:03:37 +02:00
var historyEvent = {
t : 'add' ,
2016-01-09 01:29:04 +01:00
links : addedLinks ,
removedLinks : removedLinks ,
2015-07-30 12:03:37 +02:00
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
}
}
}
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-01-09 01:29:04 +01:00
resetMouseVars ( ) ;
hideDragLines ( ) ;
2013-09-05 16:02:48 +02:00
selected _link = null ;
redraw ( ) ;
}
}
function nodeMouseUp ( d ) {
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 ;
}
2014-02-25 00:35:11 +01:00
var direction = d . _def ? ( d . inputs > 0 ? 1 : 0 ) : ( d . direction == "in" ? 0 : 1 )
portMouseUp ( d , direction , 0 ) ;
2013-09-05 16:02:48 +02:00
}
function nodeMouseDown ( d ) {
2015-03-02 23:55:34 +01:00
focusView ( ) ;
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 ) {
RED . keyboard . remove ( /* ESCAPE */ 27 ) ;
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 ;
}
mousedown _node = d ;
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
2014-07-27 23:08:27 +02:00
dblClickPrimed = ( lastClickNode == mousedown _node ) ;
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
2016-01-07 16:09:14 +01:00
if ( d . selected && ( d3 . event . ctrlKey || d3 . event . metaKey ) ) {
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 {
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 ( ) ;
}
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 ;
2014-08-08 01:01:35 +02:00
for ( i = 0 ; i < moving _set . length ; i ++ ) {
2013-09-05 16:02:48 +02:00
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 ] ;
}
mouse _offset = d3 . mouse ( document . body ) ;
if ( isNaN ( mouse _offset [ 0 ] ) ) {
mouse _offset = d3 . touches ( document . body ) [ 0 ] ;
}
}
}
d . dirty = true ;
updateSelection ( ) ;
redraw ( ) ;
d3 . event . stopPropagation ( ) ;
}
2013-11-19 09:48:44 +01:00
2013-10-28 17:45:31 +01:00
function nodeButtonClicked ( d ) {
2015-03-19 12:19:44 +01:00
if ( ! activeSubflow && ! d . changed ) {
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 if ( d . changed ) {
2015-07-01 00:42:03 +02:00
RED . notify ( RED . _ ( "notification.warning" , { message : RED . _ ( "notification.warnings.undeployedChanges" ) } ) , "warning" ) ;
2015-03-19 12:19:44 +01:00
} else {
2015-07-01 00:42:03 +02:00
RED . notify ( RED . _ ( "notification.warning" , { message : RED . _ ( "notification.warnings.nodeActionDisabled" ) } ) , "warning" ) ;
2013-10-28 17:45:31 +01:00
}
d3 . event . preventDefault ( ) ;
}
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 ( ) ; } } ) ;
options . push ( { name : "paste" , disabled : ( clipboard . length === 0 ) , onselect : function ( ) { importNodes ( clipboard , 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 ( ) ; } } ) ;
2014-10-09 11:05:45 +02:00
2014-05-15 23:49:07 +02:00
RED . touch . radialMenu . show ( obj , pos , options ) ;
resetMouseVars ( ) ;
}
2013-09-05 16:02:48 +02:00
function redraw ( ) {
vis . attr ( "transform" , "scale(" + scaleFactor + ")" ) ;
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
2013-09-05 16:02:48 +02:00
if ( 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 ) {
2014-11-13 00:51:42 +01:00
var subflowOutputs = vis . selectAll ( ".subflowoutput" ) . data ( activeSubflow . out , function ( d , i ) { return d . id ; } ) ;
2014-02-25 00:35:11 +01:00
subflowOutputs . exit ( ) . remove ( ) ;
var outGroup = subflowOutputs . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "node subflowoutput" ) . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - 20 ) + "," + ( d . y - 20 ) + ")" } ) ;
outGroup . each ( function ( d , i ) {
d . w = 40 ;
d . h = 40 ;
} ) ;
outGroup . append ( "rect" ) . attr ( "class" , "subflowport" ) . 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 )
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 ) ;
} ) ;
2015-07-01 00:42:03 +02:00
2014-02-25 00:35:11 +01:00
outGroup . append ( "rect" ) . attr ( "class" , "port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 ) . attr ( "x" , - 5 ) . attr ( "y" , 15 )
. on ( "mousedown" , function ( d , i ) { portMouseDown ( d , 1 , 0 ) ; } )
. on ( "touchstart" , function ( d , i ) { portMouseDown ( d , 1 , 0 ) ; } )
. on ( "mouseup" , function ( d , i ) { portMouseUp ( d , 1 , 0 ) ; } )
. on ( "touchend" , function ( d , i ) { portMouseUp ( d , 1 , 0 ) ; } )
2016-01-09 14:47:05 +01:00
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || ( drag _lines . length > 0 && drag _lines [ 0 ] . portType !== 1 ) ) ) ; } )
2014-02-25 00:35:11 +01:00
. on ( "mouseout" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } ) ;
outGroup . append ( "svg:text" ) . attr ( 'class' , 'port_label' ) . attr ( 'x' , 20 ) . attr ( 'y' , 8 ) . style ( "font-size" , "10px" ) . text ( "output" ) ;
2014-11-13 00:51:42 +01:00
outGroup . append ( "svg:text" ) . attr ( 'class' , 'port_label port_index' ) . attr ( 'x' , 20 ) . attr ( 'y' , 24 ) . text ( function ( d , i ) { return i + 1 } ) ;
2014-02-25 00:35:11 +01:00
2014-11-13 00:51:42 +01:00
var subflowInputs = vis . selectAll ( ".subflowinput" ) . data ( activeSubflow . in , function ( d , i ) { return d . id ; } ) ;
2014-02-25 00:35:11 +01:00
subflowInputs . exit ( ) . remove ( ) ;
var inGroup = subflowInputs . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "node subflowinput" ) . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - 20 ) + "," + ( d . y - 20 ) + ")" } ) ;
inGroup . each ( function ( d , i ) {
d . w = 40 ;
d . h = 40 ;
} ) ;
inGroup . append ( "rect" ) . attr ( "class" , "subflowport" ) . 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 )
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 ) ;
} ) ;
2015-07-01 00:42:03 +02:00
2014-02-25 00:35:11 +01:00
inGroup . append ( "rect" ) . attr ( "class" , "port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 ) . attr ( "x" , 35 ) . attr ( "y" , 15 )
. on ( "mousedown" , function ( d , i ) { portMouseDown ( d , 0 , i ) ; } )
. on ( "touchstart" , function ( d , i ) { portMouseDown ( d , 0 , i ) ; } )
. on ( "mouseup" , function ( d , i ) { portMouseUp ( d , 0 , i ) ; } )
. on ( "touchend" , function ( d , i ) { portMouseUp ( d , 0 , i ) ; } )
2016-01-09 14:47:05 +01:00
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || ( drag _lines . length > 0 && drag _lines [ 0 ] . portType !== 0 ) ) ) ; } )
2014-02-25 00:35:11 +01:00
. on ( "mouseout" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } ) ;
inGroup . append ( "svg:text" ) . attr ( 'class' , 'port_label' ) . attr ( 'x' , 18 ) . attr ( 'y' , 20 ) . style ( "font-size" , "10px" ) . text ( "input" ) ;
2015-07-01 00:42:03 +02: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 ) ;
output . selectAll ( ".subflowport" ) . classed ( "node_selected" , function ( d ) { return d . selected ; } )
2014-11-13 00:51:42 +01:00
output . selectAll ( ".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 ) ;
input . selectAll ( ".subflowport" ) . classed ( "node_selected" , function ( d ) { return d . selected ; } )
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 ;
}
} ) ;
} else {
vis . selectAll ( ".subflowoutput" ) . remove ( ) ;
vis . selectAll ( ".subflowinput" ) . remove ( ) ;
}
2015-07-01 00:42:03 +02:00
2015-03-12 12:21:05 +01:00
var node = vis . selectAll ( ".nodegroup" ) . data ( activeNodes , function ( d ) { return d . id } ) ;
2013-09-05 16:02:48 +02:00
node . exit ( ) . remove ( ) ;
var nodeEnter = node . enter ( ) . insert ( "svg:g" ) . attr ( "class" , "node nodegroup" ) ;
nodeEnter . each ( function ( d , i ) {
var node = d3 . select ( this ) ;
2014-05-11 00:33:02 +02:00
node . attr ( "id" , d . id ) ;
2013-09-05 16:02:48 +02:00
var l = d . _def . label ;
2016-03-01 22:58:57 +01:00
try {
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".label" , err ) ;
l = d . type ;
}
2016-01-07 15:39:01 +01:00
d . w = Math . max ( node _width , gridSize * ( Math . ceil ( ( calculateTextWidth ( l , "node_label" , 50 ) + ( d . _def . inputs > 0 ? 7 : 0 ) ) / gridSize ) ) ) ;
2013-09-05 16:02:48 +02:00
d . h = Math . max ( node _height , ( d . outputs || 0 ) * 15 ) ;
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 ) {
2013-10-28 17:45:31 +01:00
var nodeButtonGroup = node . append ( 'svg:g' )
. attr ( "transform" , function ( d ) { return "translate(" + ( ( d . _def . align == "right" ) ? 94 : - 25 ) + ",2)" ; } )
. attr ( "class" , function ( d ) { return "node_button " + ( ( d . _def . align == "right" ) ? "node_right_button" : "node_left_button" ) ; } ) ;
nodeButtonGroup . append ( 'rect' )
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 )
. attr ( "height" , node _height - 4 )
. attr ( "fill" , "#eee" ) ; //function(d) { return d._def.color;})
2013-10-28 17:45:31 +01:00
nodeButtonGroup . append ( 'rect' )
2015-03-12 14:58:53 +01:00
. attr ( "class" , "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 )
. attr ( "height" , node _height - 12 )
. attr ( "fill" , function ( d ) { return d . _def . color ; } )
. attr ( "cursor" , "pointer" )
2015-03-12 14:58:53 +01:00
. on ( "mousedown" , function ( d ) { if ( ! lasso && ! d . changed ) { focusView ( ) ; d3 . select ( this ) . attr ( "fill-opacity" , 0.2 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseup" , function ( d ) { if ( ! lasso && ! d . changed ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseover" , function ( d ) { if ( ! lasso && ! d . changed ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; } } )
. on ( "mouseout" , function ( d ) { if ( ! lasso && ! d . changed ) {
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 )
. on ( "touchstart" , nodeButtonClicked )
2013-09-05 16:02:48 +02:00
}
var mainRect = node . append ( "rect" )
. attr ( "class" , "node" )
2014-04-03 01:05:16 +02:00
. classed ( "node_unknown" , function ( d ) { return d . type == "unknown" ; } )
2015-07-12 00:43:45 +02:00
. attr ( "rx" , 5 )
. attr ( "ry" , 5 )
2013-09-05 16:02:48 +02:00
. attr ( "fill" , function ( d ) { return d . _def . color ; } )
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 )
2014-05-15 23:49:07 +02:00
} )
. on ( "touchend" , function ( d ) {
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 ) {
2014-08-08 01:01:35 +02:00
if ( mouse _mode === 0 ) {
2013-09-05 16:02:48 +02:00
var node = d3 . select ( this ) ;
node . classed ( "node_hovered" , true ) ;
}
} )
. on ( "mouseout" , function ( d ) {
var node = d3 . select ( this ) ;
node . classed ( "node_hovered" , false ) ;
} ) ;
//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 ) {
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
var icon _group = node . append ( "g" )
. attr ( "class" , "node_icon_group" )
. 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 )
. attr ( "class" , "node_icon_shade" )
. attr ( "width" , "30" )
. attr ( "stroke" , "none" )
. attr ( "fill" , "#000" )
. attr ( "fill-opacity" , "0.05" )
. attr ( "height" , function ( d ) { return Math . min ( 50 , d . h - 4 ) ; } ) ;
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
var icon = icon _group . append ( "image" )
2013-10-12 22:43:26 +02:00
. attr ( "xlink:href" , "icons/" + d . _def . icon )
. attr ( "class" , "node_icon" )
2014-06-04 23:43:44 +02:00
. attr ( "x" , 0 )
. attr ( "width" , "30" )
. attr ( "height" , "30" ) ;
2014-10-09 11:05:45 +02:00
2014-06-04 23:43:44 +02:00
var icon _shade _border = icon _group . append ( "path" )
. attr ( "d" , function ( d ) { return "M 30 1 l 0 " + ( d . h - 2 ) } )
. attr ( "class" , "node_icon_shade_border" )
. attr ( "stroke-opacity" , "0.1" )
. attr ( "stroke" , "#000" )
2015-07-14 00:21:03 +02:00
. attr ( "stroke-width" , "1" ) ;
2014-06-04 23:43:44 +02:00
if ( "right" == d . _def . align ) {
icon _group . attr ( 'class' , 'node_icon_group node_icon_group_' + d . _def . align ) ;
icon _shade _border . attr ( "d" , function ( d ) { return "M 0 1 l 0 " + ( d . h - 2 ) } )
//icon.attr('class','node_icon node_icon_'+d._def.align);
//icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
//icon.attr('class','node_icon_shade_border 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
var img = new Image ( ) ;
img . src = "icons/" + d . _def . icon ;
img . onload = function ( ) {
icon . attr ( "width" , Math . min ( img . width , 30 ) ) ;
icon . attr ( "height" , Math . min ( img . height , 30 ) ) ;
icon . attr ( "x" , 15 - Math . min ( img . width , 30 ) / 2 ) ;
//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
}
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
}
2014-06-04 23:43:44 +02:00
var text = node . append ( 'svg:text' ) . attr ( 'class' , 'node_label' ) . attr ( 'x' , 38 ) . attr ( 'dy' , '.35em' ) . attr ( 'text-anchor' , 'start' ) ;
2013-09-05 16:02:48 +02:00
if ( d . _def . align ) {
text . attr ( 'class' , 'node_label node_label_' + d . _def . align ) ;
2015-10-15 15:26:43 +02:00
if ( d . _def . align === "right" ) {
text . attr ( 'text-anchor' , 'end' ) ;
}
2013-09-05 16:02:48 +02:00
}
2014-04-03 01:05:16 +02:00
2014-05-08 15:15:54 +02:00
var status = node . append ( "svg:g" ) . attr ( "class" , "node_status_group" ) . style ( "display" , "none" ) ;
2014-05-14 15:22:28 +02:00
2014-05-08 23:56:17 +02:00
var statusRect = status . append ( "rect" ) . attr ( "class" , "node_status" )
. attr ( "x" , 6 ) . attr ( "y" , 1 ) . attr ( "width" , 9 ) . attr ( "height" , 9 )
. attr ( "rx" , 2 ) . attr ( "ry" , 2 ) . attr ( "stroke-width" , "3" ) ;
2014-05-14 15:22:28 +02:00
2014-05-08 15:15:54 +02:00
var statusLabel = status . append ( "svg:text" )
. attr ( "class" , "node_status_label" )
2015-09-23 23:49:48 +02:00
. attr ( 'x' , 20 ) . attr ( 'y' , 9 ) ;
2014-05-14 15:22:28 +02:00
2014-03-22 14:47:47 +01:00
//node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5});
2013-09-05 16:02:48 +02:00
//node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z");
node . append ( "image" ) . attr ( "class" , "node_error hidden" ) . attr ( "xlink:href" , "icons/node-error.png" ) . attr ( "x" , 0 ) . attr ( "y" , - 6 ) . attr ( "width" , 10 ) . attr ( "height" , 9 ) ;
2013-11-19 00:02:27 +01:00
node . append ( "image" ) . attr ( "class" , "node_changed hidden" ) . attr ( "xlink:href" , "icons/node-changed.png" ) . attr ( "x" , 12 ) . attr ( "y" , - 6 ) . attr ( "width" , 10 ) . attr ( "height" , 10 ) ;
2013-09-05 16:02:48 +02:00
} ) ;
node . each ( function ( d , i ) {
if ( d . dirty ) {
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
2013-09-05 16:02:48 +02:00
if ( d . resize ) {
var l = d . _def . label ;
2016-03-01 22:58:57 +01:00
try {
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".label" , err ) ;
l = d . type ;
}
2016-01-07 15:39:01 +01:00
var ow = d . w ;
d . w = Math . max ( node _width , gridSize * ( Math . ceil ( ( calculateTextWidth ( l , "node_label" , 50 ) + ( d . _def . inputs > 0 ? 7 : 0 ) ) / gridSize ) ) ) ;
2013-09-05 16:02:48 +02:00
d . h = Math . max ( node _height , ( d . outputs || 0 ) * 15 ) ;
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
}
var thisNode = d3 . select ( this ) ;
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}});
2013-09-05 16:02:48 +02:00
thisNode . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ; } ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
if ( mouse _mode != RED . state . MOVING _ACTIVE ) {
thisNode . selectAll ( ".node" )
. attr ( "width" , function ( d ) { return d . w } )
. attr ( "height" , function ( d ) { return d . h } )
. classed ( "node_selected" , function ( d ) { return d . selected ; } )
. classed ( "node_highlighted" , function ( d ) { return d . highlighted ; } )
;
//thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
//thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".node_icon_group_right" ) . attr ( 'transform' , function ( d ) { return "translate(" + ( d . w - 30 ) + ",0)" } ) ;
thisNode . selectAll ( ".node_label_right" ) . attr ( 'x' , function ( d ) { return d . w - 38 } ) ;
//thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
//thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
var inputPorts = thisNode . selectAll ( ".port_input" ) ;
if ( d . inputs === 0 && ! inputPorts . empty ( ) ) {
inputPorts . remove ( ) ;
//nodeLabel.attr("x",30);
} else if ( d . inputs === 1 && inputPorts . empty ( ) ) {
var inputGroup = thisNode . append ( "g" ) . attr ( "class" , "port_input" ) ;
inputGroup . append ( "rect" ) . attr ( "class" , "port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
. on ( "mousedown" , function ( d ) { portMouseDown ( d , 1 , 0 ) ; } )
. on ( "touchstart" , function ( d ) { portMouseDown ( d , 1 , 0 ) ; } )
. on ( "mouseup" , function ( d ) { portMouseUp ( d , 1 , 0 ) ; } )
. on ( "touchend" , function ( d ) { portMouseUp ( d , 1 , 0 ) ; } )
2016-01-09 14:47:05 +01:00
. on ( "mouseover" , function ( d ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || ( drag _lines . length > 0 && drag _lines [ 0 ] . portType !== 1 ) ) ) ; } )
2015-06-03 11:05:31 +02:00
. on ( "mouseout" , function ( d ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } )
}
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
var numOutputs = d . outputs ;
var y = ( d . h / 2 ) - ( ( numOutputs - 1 ) / 2 ) * 13 ;
d . ports = d . ports || d3 . range ( numOutputs ) ;
d . _ports = thisNode . selectAll ( ".port_output" ) . data ( d . ports ) ;
var output _group = d . _ports . enter ( ) . append ( "g" ) . attr ( "class" , "port_output" ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
output _group . append ( "rect" ) . attr ( "class" , "port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
. on ( "mousedown" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseDown ( node , 0 , i ) ; } } ) ( ) )
. on ( "touchstart" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseDown ( node , 0 , i ) ; } } ) ( ) )
. on ( "mouseup" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseUp ( node , 0 , i ) ; } } ) ( ) )
. on ( "touchend" , ( function ( ) { var node = d ; return function ( d , i ) { portMouseUp ( node , 0 , i ) ; } } ) ( ) )
2016-01-09 14:47:05 +01:00
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || ( drag _lines . length > 0 && drag _lines [ 0 ] . portType !== 0 ) ) ) ; } )
2015-06-03 11:05:31 +02:00
. on ( "mouseout" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } ) ;
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("y",(y+13*i)-5).attr("x",x);
port . attr ( "transform" , function ( d ) { return "translate(" + x + "," + ( ( y + 13 * i ) - 5 ) + ")" ; } ) ;
} ) ;
}
thisNode . selectAll ( 'text.node_label' ) . text ( function ( d , i ) {
2016-03-01 22:58:57 +01:00
var l = "" ;
2015-06-03 11:05:31 +02:00
if ( d . _def . label ) {
2016-03-01 22:58:57 +01:00
l = d . _def . label ;
try {
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".label" , err ) ;
l = d . type ;
2015-06-03 11:05:31 +02:00
}
}
2016-03-01 22:58:57 +01:00
return l ;
2015-06-03 11:05:31 +02:00
} )
. attr ( 'y' , function ( d ) { return ( d . h / 2 ) - 1 ; } )
. attr ( 'class' , function ( d ) {
2016-03-01 22:58:57 +01:00
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 ;
}
2015-06-03 11:05:31 +02:00
return 'node_label' +
2016-03-01 22:58:57 +01:00
( d . _def . align ? ' node_label_' + d . _def . align : '' ) + s ;
2015-06-03 11:05:31 +02:00
} ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
if ( d . _def . icon ) {
icon = thisNode . select ( ".node_icon" ) ;
var current _url = icon . attr ( "xlink:href" ) ;
var icon _url ;
if ( typeof d . _def . icon == "function" ) {
2016-03-01 22:58:57 +01:00
try {
icon _url = d . _def . icon . call ( d ) ;
} catch ( err ) {
console . log ( "icon" , err ) ;
icon _url = "arrow-in.png" ;
}
2015-06-03 11:05:31 +02:00
} else {
icon _url = d . _def . icon ;
}
if ( "icons/" + icon _url != current _url ) {
icon . attr ( "xlink:href" , "icons/" + icon _url ) ;
var img = new Image ( ) ;
img . src = "icons/" + d . _def . icon ;
img . onload = function ( ) {
icon . attr ( "width" , Math . min ( img . width , 30 ) ) ;
icon . attr ( "height" , Math . min ( img . height , 30 ) ) ;
icon . attr ( "x" , 15 - Math . min ( img . width , 30 ) / 2 ) ;
}
}
}
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".node_tools" ) . attr ( "x" , function ( d ) { return d . w - 35 ; } ) . attr ( "y" , function ( d ) { return d . h - 20 ; } ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".node_changed" )
. attr ( "x" , function ( d ) { return d . w - 10 } )
. classed ( "hidden" , function ( d ) { return ! d . changed ; } ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".node_error" )
. attr ( "x" , function ( d ) { return d . w - 10 - ( d . changed ? 13 : 0 ) } )
. classed ( "hidden" , function ( d ) { return d . valid ; } ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".port_input" ) . each ( function ( d , i ) {
2013-09-05 16:02:48 +02:00
var port = d3 . select ( this ) ;
2015-06-03 11:05:31 +02:00
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
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( ".node_icon" ) . attr ( "y" , function ( d ) { return ( d . h - d3 . select ( this ) . attr ( "height" ) ) / 2 ; } ) ;
thisNode . selectAll ( ".node_icon_shade" ) . attr ( "height" , function ( d ) { return d . h ; } ) ;
thisNode . selectAll ( ".node_icon_shade_border" ) . attr ( "d" , function ( d ) { return "M " + ( ( "right" == d . _def . align ) ? 0 : 30 ) + " 1 l 0 " + ( d . h - 2 ) } ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
thisNode . selectAll ( '.node_button' ) . attr ( "opacity" , function ( d ) {
return ( activeSubflow || d . changed ) ? 0.4 : 1
} ) ;
thisNode . selectAll ( '.node_button_button' ) . attr ( "cursor" , function ( d ) {
return ( activeSubflow || d . changed ) ? "" : "pointer" ;
} ) ;
thisNode . selectAll ( '.node_right_button' ) . attr ( "transform" , function ( d ) {
var x = d . w - 6 ;
if ( d . _def . button . toggle && ! d [ d . _def . button . toggle ] ) {
x = x - 8 ;
}
return "translate(" + x + ",2)" ;
} ) ;
thisNode . selectAll ( '.node_right_button rect' ) . attr ( "fill-opacity" , function ( d ) {
if ( d . _def . button . toggle ) {
return d [ d . _def . button . toggle ] ? 1 : 0.2 ;
}
return 1 ;
} ) ;
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +02:00
//thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
// return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color)
//});
2015-07-01 00:42:03 +02:00
2015-06-03 11:05:31 +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" ) {
2016-03-01 22:58:57 +01:00
try {
return d . _def . badge . call ( d ) ;
} catch ( err ) {
console . log ( "Definition error: " + d . type + ".badge" , err ) ;
return "" ;
}
2013-09-05 16:02:48 +02:00
} else {
2015-06-03 11:05:31 +02:00
return d . _def . badge ;
2013-09-05 16:02:48 +02:00
}
}
return "" ;
2015-06-03 11:05:31 +02:00
} ) ;
2015-06-02 23:07:45 +02:00
}
2013-09-05 16:02:48 +02:00
2014-05-11 00:33:02 +02:00
if ( ! showStatus || ! d . status ) {
2014-05-08 15:15:54 +02:00
thisNode . selectAll ( '.node_status_group' ) . style ( "display" , "none" ) ;
} else {
thisNode . selectAll ( '.node_status_group' ) . style ( "display" , "inline" ) . attr ( "transform" , "translate(3," + ( d . h + 3 ) + ")" ) ;
var fill = status _colours [ d . status . fill ] ; // Only allow our colours for now
if ( d . status . shape == null && fill == null ) {
thisNode . selectAll ( '.node_status' ) . style ( "display" , "none" ) ;
} else {
var style ;
if ( d . status . shape == null || d . status . shape == "dot" ) {
style = {
display : "inline" ,
fill : fill ,
stroke : fill
} ;
} else if ( d . status . shape == "ring" ) {
style = {
display : "inline" ,
fill : '#fff' ,
stroke : fill
}
}
thisNode . selectAll ( '.node_status' ) . style ( style ) ;
}
if ( d . status . text ) {
thisNode . selectAll ( '.node_status_label' ) . text ( d . status . text ) ;
} else {
thisNode . selectAll ( '.node_status_label' ) . text ( "" ) ;
}
}
2014-05-14 15:22:28 +02:00
2013-09-05 16:02:48 +02:00
d . dirty = false ;
}
} ) ;
2015-03-12 14:26:31 +01:00
var link = vis . selectAll ( ".link" ) . data (
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
) ;
var linkEnter = link . enter ( ) . insert ( "g" , ".node" ) . attr ( "class" , "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 ;
l . append ( "svg:path" ) . attr ( "class" , "link_background link_path" )
. on ( "mousedown" , function ( d ) {
mousedown _link = d ;
clearSelection ( ) ;
selected _link = mousedown _link ;
updateSelection ( ) ;
redraw ( ) ;
focusView ( ) ;
d3 . event . stopPropagation ( ) ;
} )
. on ( "touchstart" , function ( d ) {
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 ) ;
2016-01-08 23:34:10 +01:00
} )
2015-03-12 14:26:31 +01:00
l . append ( "svg:path" ) . attr ( "class" , "link_outline link_path" ) ;
l . append ( "svg:path" ) . attr ( "class" , "link_line link_path" )
. classed ( "link_subflow" , function ( d ) { return activeSubflow && ( d . source . type === "subflow" || d . target . type === "subflow" ) } ) ;
} ) ;
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
link . exit ( ) . remove ( ) ;
var links = vis . selectAll ( ".link_path" ) ;
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 ] ) {
link . attr ( "d" , function ( d ) {
var numOutputs = d . source . outputs || 1 ;
var sourcePort = d . sourcePort || 0 ;
var y = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
var dy = d . target . y - ( d . source . y + y ) ;
var dx = ( d . target . x - d . target . w / 2 ) - ( d . source . x + d . source . w / 2 ) ;
var delta = Math . sqrt ( dy * dy + dx * dx ) ;
var scale = lineCurveScale ;
var scaleY = 0 ;
if ( delta < node _width ) {
scale = 0.75 - 0.75 * ( ( node _width - delta ) / node _width ) ;
}
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
if ( dx < 0 ) {
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 ) ) ;
}
}
2015-07-01 00:42:03 +02:00
2015-03-12 14:26:31 +01:00
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
2015-03-12 14:26:31 +01:00
return "M " + ( d . source . x + d . source . w / 2 ) + " " + ( d . source . y + y ) +
" C " + ( d . source . x + d . source . w / 2 + scale * node _width ) + " " + ( d . source . y + y + scaleY * node _height ) + " " +
( d . target . x - d . target . w / 2 - scale * node _width ) + " " + ( d . target . y - scaleY * node _height ) + " " +
( d . target . x - d . target . w / 2 ) + " " + d . target . y ;
} ) ;
2013-09-28 22:15:32 +02:00
}
2015-03-12 14:26:31 +01:00
} )
2015-07-01 00:42:03 +02:00
2015-03-12 14:35:39 +01:00
link . classed ( "link_selected" , function ( d ) { return d === selected _link || d . selected ; } ) ;
2015-07-01 00:42:03 +02:00
link . classed ( "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
} ) ;
2015-03-12 14:35:39 +01:00
} else {
// JOINING - unselect any selected links
vis . selectAll ( ".link_selected" ) . data (
activeLinks ,
function ( d ) {
return d . source . id + ":" + d . sourcePort + ":" + d . target . id + ":" + d . target . i ;
}
) . classed ( "link_selected" , false ) ;
2015-03-12 14:26:31 +01:00
}
2015-07-01 00:42:03 +02:00
2014-04-03 01:05:16 +02:00
2013-09-05 16:02:48 +02:00
if ( d3 . event ) {
d3 . event . preventDefault ( ) ;
}
2015-07-01 00:42:03 +02:00
2013-09-05 16:02:48 +02:00
}
2015-03-02 23:55:34 +01:00
function focusView ( ) {
$ ( "#chart svg" ) . focus ( ) ;
}
2013-09-05 16:02:48 +02:00
/ * *
* Imports a new collection of nodes from a JSON String .
* - all get new IDs assigned
* - all 'selected'
* - attached to mouse for placing - 'IMPORT_DRAGGING'
* /
2014-05-15 23:49:07 +02:00
function importNodes ( newNodesStr , touchImport ) {
2013-09-05 16:02:48 +02:00
try {
2015-07-30 12:03:37 +02:00
var activeSubflowChanged ;
if ( activeSubflow ) {
activeSubflowChanged = activeSubflow . changed ;
}
2013-09-05 16:02:48 +02:00
var result = RED . nodes . import ( newNodesStr , true ) ;
if ( result ) {
var new _nodes = result [ 0 ] ;
var new _links = result [ 1 ] ;
2014-09-08 11:53:18 +02:00
var new _workspaces = result [ 2 ] ;
2014-02-25 00:35:11 +01:00
var new _subflows = result [ 3 ] ;
2015-07-01 00:42:03 +02:00
2015-09-25 18:43:28 +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 } ; } ) ;
2013-09-05 16:02:48 +02:00
var new _node _ids = new _nodes . map ( function ( n ) { return n . id ; } ) ;
2014-10-09 11:05:45 +02:00
2014-09-08 11:53:18 +02:00
// TODO: pick a more sensible root node
if ( new _ms . length > 0 ) {
var root _node = new _ms [ 0 ] . n ;
var dx = root _node . x ;
var dy = root _node . y ;
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
2014-09-08 11:53:18 +02:00
var minX = 0 ;
var minY = 0 ;
var i ;
var node ;
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 ;
node . n . x -= dx - mouse _position [ 0 ] ;
node . n . y -= dy - mouse _position [ 1 ] ;
node . dx = node . n . x - mouse _position [ 0 ] ;
node . dy = node . n . y - mouse _position [ 1 ] ;
minX = Math . min ( node . n . x - node _width / 2 - 5 , minX ) ;
minY = Math . min ( node . n . y - node _height / 2 - 5 , minY ) ;
}
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 ;
}
if ( ! touchImport ) {
mouse _mode = RED . state . IMPORT _DRAGGING ;
}
2014-10-09 11:05:45 +02:00
2014-09-08 11:53:18 +02:00
RED . keyboard . add ( /* ESCAPE */ 27 , function ( ) {
RED . keyboard . remove ( /* ESCAPE */ 27 ) ;
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 = {
2014-02-25 00:35:11 +01:00
t : 'add' ,
nodes : new _node _ids ,
links : new _links ,
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
} ;
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 ( ) ;
}
} 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
}
}
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
2015-03-12 12:21:05 +01:00
redraw : function ( updateActive ) {
if ( updateActive ) {
updateActiveNodes ( ) ;
}
2015-07-01 00:42:03 +02:00
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-05-11 00:33:02 +02:00
status : function ( s ) {
2015-03-13 00:38:37 +01:00
if ( s == null ) {
return showStatus ;
} else {
showStatus = s ;
RED . nodes . eachNode ( function ( n ) { n . dirty = true ; } ) ;
//TODO: subscribe/unsubscribe here
redraw ( ) ;
}
2014-08-20 22:58:54 +02:00
} ,
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 } ] ;
}
2015-03-04 14:19:13 +01:00
}
}
updateSelection ( ) ;
redraw ( ) ;
} ,
2015-03-12 01:08:47 +01:00
selection : function ( ) {
var selection = { } ;
if ( moving _set . length > 0 ) {
selection . nodes = moving _set . map ( function ( n ) { return n . n ; } ) ;
}
if ( selected _link != null ) {
selection . link = selected _link ;
}
return selection ;
2016-01-07 15:39:01 +01:00
} ,
toggleShowGrid : function ( state ) {
if ( state ) {
grid . style ( "visibility" , "visible" ) ;
} else {
grid . style ( "visibility" , "hidden" ) ;
}
} ,
toggleSnapGrid : function ( state ) {
snapGrid = state ;
redraw ( ) ;
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 = [ ] ;
var links = outer . selectAll ( ".link_background" ) [ 0 ] ;
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 ;
2014-02-25 00:35:11 +01:00
}
2013-09-05 16:02:48 +02:00
} ;
2014-08-08 01:01:35 +02:00
} ) ( ) ;