2013-09-05 16:02:48 +02:00
/ * *
2014-05-14 14:48:47 +02:00
* Copyright 2013 , 2014 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 ;
2013-09-05 16:02:48 +02:00
2013-10-21 00:11:38 +02:00
var activeWorkspace = 0 ;
2014-02-25 00:35:11 +01:00
var activeSubflow = null ;
2013-10-27 22:05:12 +01:00
var workspaceScrollPositions = { } ;
2013-11-19 09:48:44 +01: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 = [ ] ,
dirty = false ,
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 )
. attr ( "pointer-events" , "all" )
. style ( "cursor" , "crosshair" ) ;
2014-05-13 00:57:14 +02:00
var vis = outer
. 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' ) ;
2014-03-22 14:47:47 +01:00
//var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]);
//var grid = vis.append('g');
//
//grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter()
// .append("line")
// .attr(
// {
// "class":"horizontal",
// "x1" : 0,
// "x2" : 2000,
// "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(100)).enter()
// .append("line")
// .attr(
// {
// "class":"vertical",
// "y1" : 0,
// "y2" : 2000,
// "x1" : function(d){ return gridScale(d);},
// "x2" : function(d){ return gridScale(d);},
// "fill" : "none",
// "shape-rendering" : "crispEdges",
// "stroke" : "#eee",
// "stroke-width" : "1px"
// });
2014-04-03 01:05:16 +02:00
2013-09-05 16:02:48 +02:00
var drag _line = vis . append ( "svg:path" ) . attr ( "class" , "drag_line" ) ;
2014-02-25 00:35:11 +01:00
$ ( "#workspace-edit-subflow" ) . click ( function ( event ) {
showSubflowDialog ( activeSubflow . id ) ;
event . preventDefault ( ) ;
} ) ;
2013-10-23 01:02:22 +02:00
var workspace _tabs = RED . tabs . create ( {
id : "workspace-tabs" ,
2014-02-21 10:54:50 +01:00
onchange : function ( tab ) {
if ( tab . type == "subflow" ) {
$ ( "#workspace-toolbar" ) . show ( ) ;
} else {
$ ( "#workspace-toolbar" ) . hide ( ) ;
}
2014-02-25 00:42:24 +01:00
var chart = $ ( "#chart" ) ;
2014-08-08 01:01:35 +02:00
if ( activeWorkspace !== 0 ) {
2014-02-25 00:42:24 +01:00
workspaceScrollPositions [ activeWorkspace ] = {
left : chart . scrollLeft ( ) ,
top : chart . scrollTop ( )
} ;
}
var scrollStartLeft = chart . scrollLeft ( ) ;
var scrollStartTop = chart . scrollTop ( ) ;
2014-04-03 01:05:16 +02:00
2014-02-25 00:42:24 +01:00
activeWorkspace = tab . id ;
2014-02-25 00:35:11 +01:00
activeSubflow = RED . nodes . subflow ( activeWorkspace ) ;
2014-02-25 00:42:24 +01:00
if ( workspaceScrollPositions [ activeWorkspace ] ) {
chart . scrollLeft ( workspaceScrollPositions [ activeWorkspace ] . left ) ;
chart . scrollTop ( workspaceScrollPositions [ activeWorkspace ] . top ) ;
} 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 ;
}
2014-02-25 00:35:11 +01:00
RED . menu . setDisabled ( "btn-workspace-edit" , activeSubflow ) ;
RED . menu . setDisabled ( "btn-workspace-delete" , workspace _tabs . count ( ) == 1 || activeSubflow ) ;
2014-04-03 01:05:16 +02:00
2014-02-25 00:42:24 +01:00
clearSelection ( ) ;
RED . nodes . eachNode ( function ( n ) {
n . dirty = true ;
} ) ;
redraw ( ) ;
2013-10-23 17:42:13 +02:00
} ,
2014-02-21 10:54:50 +01:00
ondblclick : function ( tab ) {
2014-02-25 00:35:11 +01:00
if ( tab . type != "subflow" ) {
showRenameWorkspaceDialog ( tab . id ) ;
} else {
showSubflowDialog ( tab . id ) ;
}
2013-10-23 17:42:13 +02:00
} ,
onadd : function ( tab ) {
2014-08-20 22:58:54 +02:00
RED . menu . addItem ( "btn-workspace-menu" , {
id : "btn-workspace-menu-" + tab . id . replace ( "." , "-" ) ,
label : tab . label ,
onselect : function ( ) {
workspace _tabs . activateTab ( tab . id ) ;
}
2013-10-23 17:42:13 +02:00
} ) ;
2014-08-20 22:58:54 +02:00
RED . menu . setDisabled ( "btn-workspace-delete" , workspace _tabs . count ( ) == 1 ) ;
2013-10-30 20:25:22 +01:00
} ,
onremove : function ( tab ) {
2014-08-20 22:58:54 +02:00
RED . menu . setDisabled ( "btn-workspace-delete" , workspace _tabs . count ( ) == 1 ) ;
RED . menu . removeItem ( "btn-workspace-menu-" + tab . id . replace ( "." , "-" ) ) ;
2013-10-23 01:02:22 +02:00
}
} ) ;
2013-11-19 09:48:44 +01:00
2013-10-26 23:29:24 +02:00
var workspaceIndex = 0 ;
2013-11-19 09:48:44 +01:00
2013-10-28 23:31:36 +01:00
function addWorkspace ( ) {
2013-10-25 22:34:00 +02:00
var tabId = RED . nodes . id ( ) ;
2013-10-26 23:29:24 +02:00
do {
workspaceIndex += 1 ;
2014-08-08 01:01:35 +02:00
} while ( $ ( "#workspace-tabs a[title='Sheet " + workspaceIndex + "']" ) . size ( ) !== 0 ) ;
2013-11-19 09:48:44 +01:00
2013-10-30 22:45:45 +01:00
var ws = { type : "tab" , id : tabId , label : "Sheet " + workspaceIndex } ;
2013-10-25 22:34:00 +02:00
RED . nodes . addWorkspace ( ws ) ;
workspace _tabs . addTab ( ws ) ;
workspace _tabs . activateTab ( tabId ) ;
2013-10-27 21:42:42 +01:00
RED . history . push ( { t : 'add' , workspaces : [ ws ] , dirty : dirty } ) ;
RED . view . dirty ( true ) ;
2013-10-28 23:31:36 +01:00
}
2014-08-20 22:58:54 +02:00
$ ( function ( ) {
$ ( '#btn-workspace-add-tab' ) . on ( "click" , addWorkspace ) ;
2014-02-25 00:35:11 +01:00
RED . menu . setAction ( 'btn-workspace-add' , addWorkspace ) ;
RED . menu . setAction ( 'btn-workspace-edit' , function ( ) {
2014-08-20 22:58:54 +02:00
showRenameWorkspaceDialog ( activeWorkspace ) ;
} ) ;
2014-02-25 00:35:11 +01:00
RED . menu . setAction ( 'btn-workspace-delete' , function ( ) {
2014-08-20 22:58:54 +02:00
deleteWorkspace ( activeWorkspace ) ;
} ) ;
2013-10-28 21:28:44 +01:00
} ) ;
2013-11-19 09:48:44 +01:00
2013-10-28 21:28:44 +01:00
function deleteWorkspace ( id ) {
2013-10-28 21:48:25 +01:00
if ( workspace _tabs . count ( ) == 1 ) {
return ;
}
2013-10-28 21:28:44 +01:00
var ws = RED . nodes . workspace ( id ) ;
2013-10-26 23:29:24 +02:00
$ ( "#node-dialog-delete-workspace" ) . dialog ( 'option' , 'workspace' , ws ) ;
$ ( "#node-dialog-delete-workspace-name" ) . text ( ws . label ) ;
$ ( "#node-dialog-delete-workspace" ) . dialog ( 'open' ) ;
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 ] )
. attr ( "rx" , 2 )
. attr ( "ry" , 2 )
. 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 ( ) {
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
drag _line . attr ( "class" , "drag_line" ) ;
2014-08-08 01:01:35 +02:00
mousePos = mouse _position ;
var numOutputs = ( mousedown _port _type === 0 ) ? ( mousedown _node . outputs || 1 ) : 1 ;
2013-09-05 16:02:48 +02:00
var sourcePort = mousedown _port _index ;
2014-08-08 01:01:35 +02:00
var portY = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
2013-09-05 16:02:48 +02:00
2014-08-08 01:01:35 +02:00
var sc = ( mousedown _port _type === 0 ) ? 1 : - 1 ;
2013-09-05 16:02:48 +02:00
2014-08-08 01:01:35 +02:00
var dy = mousePos [ 1 ] - ( mousedown _node . y + portY ) ;
2013-09-05 16:02:48 +02:00
var dx = mousePos [ 0 ] - ( mousedown _node . x + sc * mousedown _node . w / 2 ) ;
var delta = Math . sqrt ( dy * dy + dx * dx ) ;
var scale = lineCurveScale ;
2013-09-28 22:15:32 +02:00
var scaleY = 0 ;
2013-10-28 11:01:12 +01:00
2013-09-05 16:02:48 +02:00
if ( delta < node _width ) {
scale = 0.75 - 0.75 * ( ( node _width - delta ) / node _width ) ;
}
2013-09-28 22:15:32 +02:00
if ( dx * sc < 0 ) {
2013-10-01 12:38:46 +02:00
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-09-28 22:15:32 +02:00
}
}
2013-10-28 11:01:12 +01:00
2013-09-05 16:02:48 +02:00
drag _line . attr ( "d" ,
2014-08-08 01:01:35 +02:00
"M " + ( mousedown _node . x + sc * mousedown _node . w / 2 ) + " " + ( mousedown _node . y + portY ) +
" C " + ( mousedown _node . x + sc * ( mousedown _node . w / 2 + node _width * scale ) ) + " " + ( mousedown _node . y + portY + scaleY * node _height ) + " " +
2013-09-28 22:15:32 +02:00
( mousePos [ 0 ] - sc * ( scale ) * node _width ) + " " + ( mousePos [ 1 ] - scaleY * node _height ) + " " +
2013-09-05 16:02:48 +02:00
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 ;
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 ;
var node ;
var i ;
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 ;
}
}
if ( d3 . event . shiftKey && moving _set . length > 0 ) {
var gridOffset = [ 0 , 0 ] ;
2014-08-08 01:01:35 +02:00
node = moving _set [ 0 ] ;
2014-03-22 14:47:47 +01:00
gridOffset [ 0 ] = node . n . x - ( 20 * Math . floor ( ( node . n . x - node . n . w / 2 ) / 20 ) + node . n . w / 2 ) ;
gridOffset [ 1 ] = node . n . y - ( 20 * Math . floor ( node . n . y / 20 ) ) ;
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
}
}
2014-02-25 00:35:11 +01:00
if ( mouse _mode !== 0 ) {
redraw ( ) ;
}
2013-09-05 16:02:48 +02:00
}
function canvasMouseUp ( ) {
if ( mousedown _node && mouse _mode == RED . state . JOINING ) {
drag _line . attr ( "class" , "drag_line_hidden" ) ;
}
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 ) {
2013-10-22 00:22:56 +02:00
if ( n . z == activeWorkspace && ! 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 ;
2014-09-02 17:00:01 +02:00
} else if ( mouse _mode == RED . state . DEFAULT && mousedown _link == null && ! d3 . event . ctrlKey ) {
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
}
RED . history . push ( { t : 'move' , nodes : ns , dirty : dirty } ) ;
}
}
2014-04-16 14:39:16 +02:00
if ( mouse _mode == RED . state . MOVING || mouse _mode == RED . state . MOVING _ACTIVE ) {
for ( var i = 0 ; i < moving _set . length ; i ++ ) {
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 ) ;
2014-02-19 21:08:25 +01:00
setDirty ( true ) ;
2013-09-05 16:02:48 +02:00
}
redraw ( ) ;
// clear mouse event vars
resetMouseVars ( ) ;
}
$ ( '#btn-zoom-out' ) . click ( function ( ) { zoomOut ( ) ; } ) ;
$ ( '#btn-zoom-zero' ) . click ( function ( ) { zoomZero ( ) ; } ) ;
$ ( '#btn-zoom-in' ) . click ( function ( ) { zoomIn ( ) ; } ) ;
$ ( "#chart" ) . on ( 'DOMMouseScroll mousewheel' , function ( evt ) {
2014-05-14 18:41:04 +02:00
if ( evt . altKey ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
var move = - ( evt . originalEvent . detail ) || evt . originalEvent . wheelDelta ;
if ( move <= 0 ) { zoomOut ( ) ; }
else { zoomIn ( ) ; }
}
2013-09-05 16:02:48 +02:00
} ) ;
2014-02-25 00:35:11 +01:00
2013-09-05 16:02:48 +02:00
$ ( "#chart" ) . droppable ( {
accept : ".palette_node" ,
drop : function ( event , ui ) {
d3 . event = event ;
var selected _tool = ui . draggable [ 0 ] . type ;
2014-02-25 00:35:11 +01:00
var m = /^subflow:(.+)$/ . exec ( selected _tool ) ;
if ( activeSubflow && m ) {
var subflowId = m [ 1 ] ;
if ( subflowId === activeSubflow . id ) {
RED . notify ( "<strong>Error</strong>: Cannot add subflow to itself" , "error" ) ;
return ;
}
if ( RED . nodes . subflowContains ( m [ 1 ] , activeSubflow . id ) ) {
RED . notify ( "<strong>Error</strong>: Cannot add subflow - circular reference detected" , "error" ) ;
return ;
}
}
2013-09-05 16:02:48 +02:00
var mousePos = d3 . touches ( this ) [ 0 ] || d3 . mouse ( this ) ;
mousePos [ 1 ] += this . scrollTop ;
mousePos [ 0 ] += this . scrollLeft ;
mousePos [ 1 ] /= scaleFactor ;
mousePos [ 0 ] /= scaleFactor ;
2013-10-21 00:11:38 +02:00
var nn = { id : ( 1 + Math . random ( ) * 4294967295 ) . toString ( 16 ) , x : mousePos [ 0 ] , y : mousePos [ 1 ] , w : node _width , z : activeWorkspace } ;
2013-09-05 16:02:48 +02:00
nn . type = selected _tool ;
nn . _def = RED . nodes . getType ( nn . type ) ;
2014-02-25 00:35:11 +01:00
if ( ! m ) {
nn . inputs = nn . _def . inputs || 0 ;
nn . outputs = nn . _def . outputs ;
nn . changed = true ;
for ( var d in nn . _def . defaults ) {
if ( nn . _def . defaults . hasOwnProperty ( d ) ) {
nn [ d ] = nn . _def . defaults [ d ] . value ;
}
2014-08-08 01:01:35 +02:00
}
2014-02-25 00:35:11 +01:00
if ( nn . _def . onadd ) {
nn . _def . onadd . call ( nn ) ;
}
} else {
var subflow = RED . nodes . subflow ( m [ 1 ] ) ;
nn . inputs = subflow . in . length ;
nn . outputs = subflow . out . length ;
2014-04-15 23:31:03 +02:00
}
2014-05-14 15:22:28 +02:00
2014-04-15 23:31:03 +02:00
nn . h = Math . max ( node _height , ( nn . outputs || 0 ) * 15 ) ;
2013-09-05 16:02:48 +02:00
RED . history . push ( { t : 'add' , nodes : [ nn . id ] , dirty : dirty } ) ;
RED . nodes . add ( nn ) ;
RED . editor . validateNode ( nn ) ;
setDirty ( true ) ;
2013-11-19 09:48:44 +01:00
// auto select dropped node - so info shows (if visible)
clearSelection ( ) ;
nn . selected = true ;
moving _set . push ( { n : nn } ) ;
updateSelection ( ) ;
2013-09-05 16:02:48 +02:00
redraw ( ) ;
if ( nn . _def . autoedit ) {
RED . editor . edit ( nn ) ;
}
}
} ) ;
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 ) {
2013-10-21 00:11:38 +02:00
if ( n . z == activeWorkspace ) {
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 } ) ;
}
} ) ;
}
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 ) {
2014-08-20 22:58:54 +02:00
RED . menu . setDisabled ( "btn-export-menu" , true ) ;
RED . menu . setDisabled ( "btn-export-clipboard" , true ) ;
RED . menu . setDisabled ( "btn-export-library" , true ) ;
2014-02-25 00:35:11 +01:00
RED . menu . setDisabled ( "btn-convert-subflow" , true ) ;
2013-09-05 16:02:48 +02:00
} else {
2014-08-20 22:58:54 +02:00
RED . menu . setDisabled ( "btn-export-menu" , false ) ;
RED . menu . setDisabled ( "btn-export-clipboard" , false ) ;
RED . menu . setDisabled ( "btn-export-library" , false ) ;
2014-02-25 00:35:11 +01:00
RED . menu . setDisabled ( "btn-convert-subflow" , false ) ;
2013-09-05 16:02:48 +02:00
}
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
}
2013-09-05 16:02:48 +02:00
if ( moving _set . length == 1 ) {
2014-02-25 00:35:11 +01:00
if ( moving _set [ 0 ] . n . type === "subflow" && ! moving _set [ 0 ] . n . id ) {
RED . sidebar . info . refresh ( RED . nodes . subflow ( moving _set [ 0 ] . n . z ) ) ;
} else {
RED . sidebar . info . refresh ( moving _set [ 0 ] . n ) ;
}
} else if ( moving _set . length === 0 && activeSubflow ) {
RED . sidebar . info . refresh ( activeSubflow ) ;
2013-09-05 16:02:48 +02:00
} else {
2014-02-26 23:58:44 +01:00
RED . sidebar . info . clear ( ) ;
2013-09-05 16:02:48 +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 ;
}
RED . history . push ( { t : 'move' , nodes : ns , dirty : dirty } ) ;
}
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 ( ) {
var removedNodes = [ ] ;
var removedLinks = [ ] ;
var startDirty = dirty ;
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
}
var rmlinks = RED . nodes . remove ( node . id ) ;
removedNodes . push ( node ) ;
removedLinks = removedLinks . concat ( rmlinks ) ;
} else {
node . dirty = true ;
2014-08-08 01:01:35 +02:00
}
2013-09-05 16:02:48 +02:00
}
moving _set = [ ] ;
2014-02-25 00:35:11 +01:00
if ( removedNodes . length > 0 ) {
setDirty ( true ) ;
}
2013-09-05 16:02:48 +02:00
}
if ( selected _link ) {
RED . nodes . removeLink ( selected _link ) ;
removedLinks . push ( selected _link ) ;
setDirty ( true ) ;
}
RED . history . push ( { t : 'delete' , nodes : removedNodes , links : removedLinks , dirty : startDirty } ) ;
selected _link = null ;
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 ;
2014-02-25 00:35:11 +01:00
if ( node . type != "subflow" ) {
nns . push ( RED . nodes . convertNode ( node ) ) ;
}
2013-09-05 16:02:48 +02:00
}
clipboard = JSON . stringify ( nns ) ;
2014-02-25 00:35:11 +01:00
RED . notify ( nns . length + " node" + ( nns . length > 1 ? "s" : "" ) + " copied" ) ;
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 ;
}
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 ) {
document . body . style . cursor = "" ;
if ( mouse _mode == RED . state . JOINING && mousedown _node ) {
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 ) {
if ( n . z == activeWorkspace ) {
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 ;
2014-02-25 00:35:11 +01:00
portType = mouseup _node . inputs > 0 ? 1 : 0 ;
2014-05-10 00:30:00 +02:00
portIndex = 0 ;
}
}
} ) ;
} else {
mouseup _node = d ;
}
2013-09-05 16:02:48 +02:00
if ( portType == mousedown _port _type || mouseup _node === mousedown _node ) {
drag _line . attr ( "class" , "drag_line_hidden" ) ;
2014-05-10 00:30:00 +02:00
resetMouseVars ( ) ;
return ;
2013-09-05 16:02:48 +02:00
}
var src , dst , src _port ;
2014-08-08 01:01:35 +02:00
if ( mousedown _port _type === 0 ) {
2013-09-05 16:02:48 +02:00
src = mousedown _node ;
src _port = mousedown _port _index ;
dst = mouseup _node ;
} else if ( mousedown _port _type == 1 ) {
src = mouseup _node ;
dst = mousedown _node ;
src _port = portIndex ;
}
var existingLink = false ;
RED . nodes . eachLink ( function ( d ) {
2014-02-25 00:35:11 +01:00
existingLink = existingLink || ( d . source === src && d . target === dst && d . sourcePort == src _port ) ;
2013-09-05 16:02:48 +02:00
} ) ;
if ( ! existingLink ) {
var link = { source : src , sourcePort : src _port , target : dst } ;
RED . nodes . addLink ( link ) ;
RED . history . push ( { t : 'add' , links : [ link ] , dirty : dirty } ) ;
setDirty ( true ) ;
2014-02-25 00:35:11 +01:00
} else {
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 ) {
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 ( ) ;
setDirty ( true ) ;
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
2013-09-05 16:02:48 +02:00
if ( d . selected && d3 . event . ctrlKey ) {
d . selected = false ;
2014-08-08 01:01:35 +02:00
for ( i = 0 ; i < moving _set . length ; i += 1 ) {
2013-09-05 16:02:48 +02:00
if ( moving _set [ i ] . n === d ) {
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 ) {
if ( ! d3 . event . ctrlKey ) {
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 ) {
if ( d . _def . button . toggle ) {
d [ d . _def . button . toggle ] = ! d [ d . _def . button . toggle ] ;
d . dirty = true ;
}
if ( d . _def . button . onclick ) {
d . _def . button . onclick . call ( d ) ;
}
if ( d . dirty ) {
redraw ( ) ;
}
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 = [ ] ;
2014-08-08 01:01:35 +02:00
options . push ( { name : "delete" , disabled : ( moving _set . length === 0 ) , onselect : function ( ) { deleteSelection ( ) ; } } ) ;
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 ) ;
if ( mouse _mode != RED . state . JOINING ) {
// Don't bother redrawing nodes if we're drawing links
2014-02-25 00:35:11 +01:00
if ( activeSubflow ) {
var subflowOutputs = vis . selectAll ( ".subflowoutput" ) . data ( activeSubflow . out , function ( d , i ) { return d . z + ":" + i ; } ) ;
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 )
// 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 )
} )
. on ( "touchend" , function ( d ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
} ) ;
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 ) ; } )
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || mousedown _port _type !== 0 ) ) ; } )
. 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" ) ;
outGroup . append ( "svg:text" ) . attr ( 'class' , 'port_label' ) . attr ( 'x' , 20 ) . attr ( 'y' , 24 ) . text ( function ( d , i ) { return i + 1 } ) ;
var subflowInputs = vis . selectAll ( ".subflowinput" ) . data ( activeSubflow . in , function ( d , i ) { return d . z + ":" + i ; } ) ;
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 )
// 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 )
} )
. on ( "touchend" , function ( d ) {
clearTimeout ( touchStartTime ) ;
touchStartTime = null ;
if ( RED . touch . radialMenu . active ( ) ) {
d3 . event . stopPropagation ( ) ;
return ;
}
nodeMouseUp . call ( this , d ) ;
} ) ;
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 ) ; } )
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || mousedown _port _type !== 0 ) ) ; } )
. 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" ) ;
subflowOutputs . each ( function ( d ) {
if ( d . dirty ) {
var output = d3 . select ( this ) ;
output . selectAll ( ".subflowport" ) . classed ( "node_selected" , function ( d ) { return d . selected ; } )
output . attr ( "transform" , function ( d ) { return "translate(" + ( d . x - d . w / 2 ) + "," + ( d . y - d . h / 2 ) + ")" ; } ) ;
d . dirty = false ;
}
} ) ;
subflowInputs . each ( function ( d ) {
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 ) + ")" ; } ) ;
d . dirty = false ;
}
} ) ;
} else {
vis . selectAll ( ".subflowoutput" ) . remove ( ) ;
vis . selectAll ( ".subflowinput" ) . remove ( ) ;
}
2013-10-21 00:11:38 +02:00
var node = vis . selectAll ( ".nodegroup" ) . data ( RED . nodes . nodes . filter ( function ( d ) { return d . z == activeWorkspace } ) , 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 ;
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
2014-10-09 11:05:45 +02:00
d . w = Math . max ( node _width , calculateTextWidth ( l , "node_label" , 50 ) + ( d . _def . inputs > 0 ? 7 : 0 ) ) ;
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' )
2013-09-05 16:02:48 +02:00
. attr ( "rx" , 8 )
. attr ( "ry" , 8 )
. 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' )
. attr ( "x" , function ( d ) { return d . _def . align == "right" ? 10 : 5 } )
. attr ( "y" , 4 )
2013-09-05 16:02:48 +02:00
. attr ( "rx" , 5 )
. attr ( "ry" , 5 )
. attr ( "width" , 16 )
. attr ( "height" , node _height - 12 )
. attr ( "fill" , function ( d ) { return d . _def . color ; } )
. attr ( "cursor" , "pointer" )
. on ( "mousedown" , function ( d ) { if ( ! lasso ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.2 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseup" , function ( d ) { if ( ! lasso ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; d3 . event . preventDefault ( ) ; d3 . event . stopPropagation ( ) ; } } )
. on ( "mouseover" , function ( d ) { if ( ! lasso ) { d3 . select ( this ) . attr ( "fill-opacity" , 0.4 ) ; } } )
2013-11-19 09:48:44 +01:00
. on ( "mouseout" , function ( d ) { if ( ! lasso ) {
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" ; } )
2013-09-05 16:02:48 +02:00
. attr ( "rx" , 6 )
. attr ( "ry" , 6 )
. 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" )
. attr ( "stroke-width" , "2" ) ;
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
}
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 ) ;
text . attr ( 'text-anchor' , 'end' ) ;
}
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" )
2014-05-08 23:56:17 +02:00
. attr ( 'x' , 20 ) . attr ( 'y' , 9 )
2014-05-08 15:15:54 +02:00
. style ( {
'stroke-width' : 0 ,
2014-05-08 23:56:17 +02:00
'fill' : '#888' ,
2014-05-08 15:15:54 +02:00
'font-size' : '9pt' ,
'stroke' : '#000' ,
'text-anchor' : 'start'
} ) ;
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 ) {
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 ;
l = ( typeof l === "function" ? l . call ( d ) : l ) || "" ;
2014-10-09 11:05:45 +02:00
d . w = Math . max ( node _width , calculateTextWidth ( l , "node_label" , 50 ) + ( d . _def . inputs > 0 ? 7 : 0 ) ) ;
2013-09-05 16:02:48 +02:00
d . h = Math . max ( node _height , ( d . outputs || 0 ) * 15 ) ;
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 ) + ")" ; } ) ;
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});
2014-06-04 23:43:44 +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)});
2013-09-05 16:02:48 +02:00
2014-02-25 00:35:11 +01: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 ) ; } )
. on ( "mouseover" , function ( d ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || mousedown _port _type != 1 ) ) ; } )
. on ( "mouseout" , function ( d ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } )
}
2013-09-05 16:02:48 +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 ) ;
2014-02-25 00:35:11 +01:00
var output _group = d . _ports . enter ( ) . append ( "g" ) . attr ( "class" , "port_output" ) ;
output _group . append ( "rect" ) . attr ( "class" , "port" ) . attr ( "rx" , 3 ) . attr ( "ry" , 3 ) . attr ( "width" , 10 ) . attr ( "height" , 10 )
2014-08-08 01:01:35 +02:00
. 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 ) ; } } ) ( ) )
. on ( "mouseover" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , ( mouse _mode != RED . state . JOINING || mousedown _port _type !== 0 ) ) ; } )
2013-09-05 16:02:48 +02:00
. on ( "mouseout" , function ( d , i ) { var port = d3 . select ( this ) ; port . classed ( "port_hovered" , false ) ; } ) ;
2014-02-25 00:35:11 +01:00
2013-09-05 16:02:48 +02:00
d . _ports . exit ( ) . remove ( ) ;
if ( d . _ports ) {
2014-08-08 01:01:35 +02:00
numOutputs = d . outputs || 1 ;
y = ( d . h / 2 ) - ( ( numOutputs - 1 ) / 2 ) * 13 ;
2013-09-05 16:02:48 +02:00
var x = d . w - 5 ;
d . _ports . each ( function ( d , i ) {
var port = d3 . select ( this ) ;
2014-02-25 00:35:11 +01:00
//port.attr("y",(y+13*i)-5).attr("x",x);
port . attr ( "transform" , function ( d ) { return "translate(" + x + "," + ( ( y + 13 * i ) - 5 ) + ")" ; } ) ;
2013-09-05 16:02:48 +02:00
} ) ;
}
thisNode . selectAll ( 'text.node_label' ) . text ( function ( d , i ) {
if ( d . _def . label ) {
if ( typeof d . _def . label == "function" ) {
return d . _def . label . call ( d ) ;
} else {
return d . _def . label ;
}
}
return "" ;
2014-02-25 00:35:11 +01:00
} )
2013-09-05 16:02:48 +02:00
. attr ( 'y' , function ( d ) { return ( d . h / 2 ) - 1 ; } )
. attr ( 'class' , function ( d ) {
return 'node_label' +
( d . _def . align ? ' node_label_' + d . _def . align : '' ) +
2014-09-04 20:56:53 +02:00
( d . _def . labelStyle ? ' ' + ( typeof d . _def . labelStyle == "function" ? d . _def . labelStyle . call ( d ) : d . _def . labelStyle ) : '' ) ;
2013-09-05 16:02:48 +02:00
} ) ;
thisNode . selectAll ( ".node_tools" ) . attr ( "x" , function ( d ) { return d . w - 35 ; } ) . attr ( "y" , function ( d ) { return d . h - 20 ; } ) ;
2014-04-03 01:05:16 +02:00
2013-11-16 00:40:36 +01:00
thisNode . selectAll ( ".node_changed" )
2013-11-19 00:02:27 +01:00
. attr ( "x" , function ( d ) { return d . w - 10 } )
2013-11-16 00:40:36 +01:00
. classed ( "hidden" , function ( d ) { return ! d . changed ; } ) ;
2013-11-17 16:52:34 +01:00
thisNode . selectAll ( ".node_error" )
2013-11-19 00:02:27 +01:00
. attr ( "x" , function ( d ) { return d . w - 10 - ( d . changed ? 13 : 0 ) } )
2013-11-17 16:52:34 +01:00
. classed ( "hidden" , function ( d ) { return d . valid ; } ) ;
2014-04-03 01:05:16 +02:00
2013-09-05 16:02:48 +02:00
thisNode . selectAll ( ".port_input" ) . each ( function ( d , i ) {
var port = d3 . select ( this ) ;
2014-02-25 00:35:11 +01:00
port . attr ( "transform" , function ( d ) { return "translate(-5," + ( ( d . h / 2 ) - 5 ) + ")" ; } )
2013-09-05 16:02:48 +02:00
} ) ;
2014-06-04 23:43:44 +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 ) } ) ;
2014-10-09 11:05:45 +02:00
2013-10-28 17:45:31 +01:00
thisNode . selectAll ( '.node_right_button' ) . attr ( "transform" , function ( d ) {
var x = d . w - 6 ;
if ( d . _def . button . toggle && ! d [ d . _def . button . toggle ] ) {
2013-11-19 09:48:44 +01:00
x = x - 8 ;
2013-10-28 17:45:31 +01:00
}
return "translate(" + x + ",2)" ;
2013-09-05 16:02:48 +02:00
} ) ;
2013-10-28 17:45:31 +01:00
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 ;
2013-09-05 16:02:48 +02:00
} ) ;
2013-11-19 09:48:44 +01:00
2013-10-28 17:45:31 +01: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)
//});
2013-09-05 16:02:48 +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" ) {
return d . _def . badge . call ( d ) ;
} else {
return d . _def . badge ;
}
}
return "" ;
} ) ;
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 ;
}
} ) ;
}
2014-02-25 00:35:11 +01:00
var link = vis . selectAll ( ".link" ) . data (
RED . nodes . links . filter ( function ( d ) {
return d . source . z == activeWorkspace && d . target . z == activeWorkspace ;
} ) ,
function ( d ) {
return d . source . id + ":" + d . sourcePort + ":" + d . target . id + ":" + d . target . i ;
}
) ;
2013-09-05 16:02:48 +02:00
2014-05-15 23:44:07 +02:00
var linkEnter = link . enter ( ) . insert ( "g" , ".node" ) . attr ( "class" , "link" ) ;
2014-10-09 11:05:45 +02:00
2014-05-15 23:44:07 +02:00
linkEnter . each ( function ( d , i ) {
var l = d3 . select ( this ) ;
l . append ( "svg:path" ) . attr ( "class" , "link_background link_path" )
. on ( "mousedown" , function ( d ) {
mousedown _link = d ;
clearSelection ( ) ;
selected _link = mousedown _link ;
updateSelection ( ) ;
redraw ( ) ;
d3 . event . stopPropagation ( ) ;
} )
. on ( "touchstart" , function ( d ) {
mousedown _link = d ;
clearSelection ( ) ;
selected _link = mousedown _link ;
updateSelection ( ) ;
redraw ( ) ;
d3 . event . stopPropagation ( ) ;
} ) ;
l . append ( "svg:path" ) . attr ( "class" , "link_outline link_path" ) ;
2014-02-25 00:35:11 +01:00
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" ) } ) ;
2014-05-15 23:44:07 +02:00
} ) ;
2013-09-05 16:02:48 +02:00
link . exit ( ) . remove ( ) ;
2014-05-15 23:44:07 +02:00
var links = vis . selectAll ( ".link_path" )
links . attr ( "d" , function ( d ) {
2013-09-05 16:02:48 +02:00
var numOutputs = d . source . outputs || 1 ;
var sourcePort = d . sourcePort || 0 ;
var y = - ( ( numOutputs - 1 ) / 2 ) * 13 + 13 * sourcePort ;
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 ;
2013-09-28 22:15:32 +02:00
var scaleY = 0 ;
2013-09-05 16:02:48 +02:00
if ( delta < node _width ) {
scale = 0.75 - 0.75 * ( ( node _width - delta ) / node _width ) ;
}
2013-10-28 11:01:12 +01:00
2013-09-28 22:15:32 +02:00
if ( dx < 0 ) {
2013-10-01 12:38:46 +02:00
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-09-28 22:15:32 +02:00
}
}
2013-10-28 11:01:12 +01:00
2013-09-05 16:02:48 +02: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 ;
return "M " + ( d . source . x + d . source . w / 2 ) + " " + ( d . source . y + y ) +
2013-09-28 22:15:32 +02:00
" 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 ) + " " +
2013-09-05 16:02:48 +02:00
( d . target . x - d . target . w / 2 ) + " " + d . target . y ;
} )
link . classed ( "link_selected" , function ( d ) { return d === selected _link || d . selected ; } ) ;
2013-12-12 16:51:15 +01:00
link . classed ( "link_unknown" , function ( d ) { return d . target . type == "unknown" || d . source . type == "unknown" } ) ;
2014-04-03 01:05:16 +02:00
2013-09-05 16:02:48 +02:00
if ( d3 . event ) {
d3 . event . preventDefault ( ) ;
}
2014-02-25 00:35:11 +01:00
2013-09-05 16:02:48 +02:00
}
2014-10-29 09:49:07 +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 ( ) ; } ) ;
RED . keyboard . add ( /* e */ 69 , { ctrl : true } , function ( ) { showExportNodesDialog ( ) ; d3 . event . preventDefault ( ) ; } ) ;
RED . keyboard . add ( /* i */ 73 , { ctrl : true } , function ( ) { showImportNodesDialog ( ) ; d3 . event . preventDefault ( ) ; } ) ;
2013-09-05 16:02:48 +02:00
// TODO: 'dirty' should be a property of RED.nodes - with an event callback for ui hooks
2014-05-14 16:14:53 +02:00
function setDirty ( d ) {
2013-09-05 16:02:48 +02:00
dirty = d ;
if ( dirty ) {
2014-08-22 12:07:32 +02:00
$ ( "#btn-deploy" ) . removeClass ( "disabled" ) ;
2013-09-05 16:02:48 +02:00
} else {
2014-08-22 12:07:32 +02:00
$ ( "#btn-deploy" ) . addClass ( "disabled" ) ;
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 {
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 ] ;
2014-09-08 11:53:18 +02:00
var new _ms = new _nodes . filter ( function ( n ) { return n . z == activeWorkspace } ) . 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
2014-02-25 00:35:11 +01:00
RED . history . push ( {
t : 'add' ,
nodes : new _node _ids ,
links : new _links ,
workspaces : new _workspaces ,
subflows : new _subflows ,
dirty : RED . view . dirty ( )
} ) ;
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 ) ;
RED . notify ( "<strong>Error</strong>: " + error , "error" ) ;
} else {
RED . notify ( "<strong>Error</strong>: " + error . message , "error" ) ;
}
2013-09-05 16:02:48 +02:00
}
}
function showExportNodesDialog ( ) {
mouse _mode = RED . state . EXPORT ;
var nns = RED . nodes . createExportableNodeSet ( moving _set ) ;
$ ( "#dialog-form" ) . html ( $ ( "script[data-template-name='export-clipboard-dialog']" ) . html ( ) ) ;
$ ( "#node-input-export" ) . val ( JSON . stringify ( nns ) ) ;
$ ( "#node-input-export" ) . focus ( function ( ) {
var textarea = $ ( this ) ;
textarea . select ( ) ;
textarea . mouseup ( function ( ) {
textarea . unbind ( "mouseup" ) ;
return false ;
} ) ;
} ) ;
$ ( "#dialog" ) . dialog ( "option" , "title" , "Export nodes to clipboard" ) . dialog ( "open" ) ;
$ ( "#node-input-export" ) . focus ( ) ;
}
function showExportNodesLibraryDialog ( ) {
mouse _mode = RED . state . EXPORT ;
var nns = RED . nodes . createExportableNodeSet ( moving _set ) ;
$ ( "#dialog-form" ) . html ( $ ( "script[data-template-name='export-library-dialog']" ) . html ( ) ) ;
$ ( "#node-input-filename" ) . attr ( 'nodes' , JSON . stringify ( nns ) ) ;
$ ( "#dialog" ) . dialog ( "option" , "title" , "Export nodes to library" ) . dialog ( "open" ) ;
}
function showImportNodesDialog ( ) {
mouse _mode = RED . state . IMPORT ;
$ ( "#dialog-form" ) . html ( $ ( "script[data-template-name='import-dialog']" ) . html ( ) ) ;
$ ( "#node-input-import" ) . val ( "" ) ;
$ ( "#dialog" ) . dialog ( "option" , "title" , "Import nodes" ) . dialog ( "open" ) ;
}
2013-11-19 09:48:44 +01:00
2013-10-26 23:29:24 +02:00
function showRenameWorkspaceDialog ( id ) {
var ws = RED . nodes . workspace ( id ) ;
$ ( "#node-dialog-rename-workspace" ) . dialog ( "option" , "workspace" , ws ) ;
2013-10-28 21:48:25 +01:00
if ( workspace _tabs . count ( ) == 1 ) {
$ ( "#node-dialog-rename-workspace" ) . next ( ) . find ( ".leftButton" )
. prop ( 'disabled' , true )
. addClass ( "ui-state-disabled" ) ;
} else {
$ ( "#node-dialog-rename-workspace" ) . next ( ) . find ( ".leftButton" )
. prop ( 'disabled' , false )
. removeClass ( "ui-state-disabled" ) ;
}
2013-11-19 09:48:44 +01:00
2013-10-26 23:29:24 +02:00
$ ( "#node-input-workspace-name" ) . val ( ws . label ) ;
$ ( "#node-dialog-rename-workspace" ) . dialog ( "open" ) ;
}
2014-02-25 00:35:11 +01:00
function showSubflowDialog ( id ) {
RED . editor . editSubflow ( RED . nodes . subflow ( id ) ) ;
}
2013-11-19 09:48:44 +01:00
2013-10-26 23:29:24 +02:00
$ ( "#node-dialog-rename-workspace form" ) . submit ( function ( e ) { e . preventDefault ( ) ; } ) ;
$ ( "#node-dialog-rename-workspace" ) . dialog ( {
modal : true ,
autoOpen : false ,
width : 500 ,
2013-10-30 22:45:45 +01:00
title : "Rename sheet" ,
2013-10-26 23:29:24 +02:00
buttons : [
2013-10-28 21:28:44 +01:00
{
class : 'leftButton' ,
text : "Delete" ,
click : function ( ) {
var workspace = $ ( this ) . dialog ( 'option' , 'workspace' ) ;
$ ( this ) . dialog ( "close" ) ;
deleteWorkspace ( workspace . id ) ;
}
} ,
2013-10-26 23:29:24 +02:00
{
text : "Ok" ,
click : function ( ) {
var workspace = $ ( this ) . dialog ( 'option' , 'workspace' ) ;
var label = $ ( "#node-input-workspace-name" ) . val ( ) ;
if ( workspace . label != label ) {
2014-02-25 00:35:11 +01:00
workspace _tabs . renameTab ( workspace . id , label ) ;
2013-10-26 23:29:24 +02:00
RED . view . dirty ( true ) ;
2014-02-25 00:35:11 +01:00
$ ( "#btn-workspace-menu-" + workspace . id . replace ( "." , "-" ) ) . text ( label ) ;
// TODO: update entry in menu
2013-10-26 23:29:24 +02:00
}
$ ( this ) . dialog ( "close" ) ;
}
} ,
{
text : "Cancel" ,
click : function ( ) {
$ ( this ) . dialog ( "close" ) ;
}
}
2013-12-28 18:59:45 +01:00
] ,
open : function ( e ) {
RED . keyboard . disable ( ) ;
} ,
close : function ( e ) {
RED . keyboard . enable ( ) ;
}
2013-10-26 23:29:24 +02:00
} ) ;
$ ( "#node-dialog-delete-workspace" ) . dialog ( {
modal : true ,
autoOpen : false ,
width : 500 ,
title : "Confirm delete" ,
buttons : [
{
text : "Ok" ,
click : function ( ) {
var workspace = $ ( this ) . dialog ( 'option' , 'workspace' ) ;
2013-10-28 21:14:59 +01:00
RED . view . removeWorkspace ( workspace ) ;
2013-10-27 21:42:42 +01:00
var historyEvent = RED . nodes . removeWorkspace ( workspace . id ) ;
historyEvent . t = 'delete' ;
historyEvent . dirty = dirty ;
historyEvent . workspaces = [ workspace ] ;
RED . history . push ( historyEvent ) ;
2013-10-26 23:29:24 +02:00
RED . view . dirty ( true ) ;
$ ( this ) . dialog ( "close" ) ;
}
} ,
{
text : "Cancel" ,
click : function ( ) {
$ ( this ) . dialog ( "close" ) ;
}
}
2013-12-28 18:59:45 +01:00
] ,
open : function ( e ) {
RED . keyboard . disable ( ) ;
} ,
close : function ( e ) {
RED . keyboard . enable ( ) ;
}
2013-10-26 23:29:24 +02:00
} ) ;
2013-09-05 16:02:48 +02:00
return {
state : function ( state ) {
if ( state == null ) {
return mouse _mode
} else {
mouse _mode = state ;
}
} ,
2013-10-25 22:34:00 +02:00
addWorkspace : function ( ws ) {
workspace _tabs . addTab ( ws ) ;
workspace _tabs . resize ( ) ;
} ,
2013-10-27 21:42:42 +01:00
removeWorkspace : function ( ws ) {
2014-02-25 00:35:11 +01:00
if ( workspace _tabs . contains ( ws . id ) ) {
workspace _tabs . removeTab ( ws . id ) ;
}
2013-10-27 21:42:42 +01:00
} ,
2013-10-25 22:34:00 +02:00
getWorkspace : function ( ) {
return activeWorkspace ;
} ,
2014-04-16 14:39:16 +02:00
showWorkspace : function ( id ) {
workspace _tabs . activateTab ( id ) ;
} ,
2014-02-25 00:35:11 +01:00
redraw : function ( ) {
RED . nodes . eachSubflow ( function ( sf ) {
if ( workspace _tabs . contains ( sf . id ) ) {
workspace _tabs . renameTab ( sf . id , "Subflow: " + sf . name ) ;
}
} ) ;
redraw ( ) ;
} ,
2013-09-05 16:02:48 +02:00
dirty : function ( d ) {
if ( d == null ) {
return dirty ;
} else {
setDirty ( d ) ;
}
} ,
2013-10-23 11:44:08 +02:00
importNodes : importNodes ,
resize : function ( ) {
workspace _tabs . resize ( ) ;
2014-05-11 00:33:02 +02:00
} ,
status : function ( s ) {
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 ,
2014-08-20 22:58:54 +02:00
//TODO: should these move to an import/export module?
showImportNodesDialog : showImportNodesDialog ,
showExportNodesDialog : showExportNodesDialog ,
2014-02-25 00:35:11 +01:00
showExportNodesLibraryDialog : showExportNodesLibraryDialog ,
addFlow : function ( ) {
var ws = { type : "subflow" , id : RED . nodes . id ( ) , label : "Flow 1" , closeable : true } ;
RED . nodes . addWorkspace ( ws ) ;
workspace _tabs . addTab ( ws ) ;
workspace _tabs . activateTab ( ws . id ) ;
return ws ;
} ,
showSubflow : function ( id ) {
if ( ! workspace _tabs . contains ( id ) ) {
var sf = RED . nodes . subflow ( id ) ;
workspace _tabs . addTab ( { type : "subflow" , id : id , label : "Subflow: " + sf . name , closeable : true } ) ;
workspace _tabs . resize ( ) ;
}
workspace _tabs . activateTab ( id ) ;
} ,
createSubflow : function ( ) {
var lastIndex = 0 ;
RED . nodes . eachSubflow ( function ( sf ) {
var m = ( new RegExp ( "^Subflow (\\d+)$" ) ) . exec ( sf . name ) ;
if ( m ) {
lastIndex = Math . max ( lastIndex , m [ 1 ] ) ;
}
} ) ;
var name = "Subflow " + ( lastIndex + 1 ) ;
var subflowId = RED . nodes . id ( ) ;
var subflow = {
type : "subflow" ,
id : subflowId ,
name : name ,
in : [ ] ,
out : [ ]
} ;
RED . nodes . addSubflow ( subflow ) ;
RED . history . push ( {
t : 'createSubflow' ,
subflow : subflow ,
dirty : RED . view . dirty ( )
} ) ;
RED . view . showSubflow ( subflowId ) ;
} ,
convertToSubflow : function ( ) {
if ( moving _set . length === 0 ) {
RED . notify ( "<strong>Cannot create subflow</strong>: no nodes selected" , "error" ) ;
return ;
}
var i ;
var nodes = { } ;
var new _links = [ ] ;
var removedLinks = [ ] ;
var candidateInputs = [ ] ;
var candidateOutputs = [ ] ;
var boundingBox = [ moving _set [ 0 ] . n . x , moving _set [ 0 ] . n . y , moving _set [ 0 ] . n . x , moving _set [ 0 ] . n . y ] ;
for ( i = 0 ; i < moving _set . length ; i ++ ) {
var n = moving _set [ i ] ;
nodes [ n . n . id ] = { n : n . n , outputs : { } } ;
boundingBox = [
Math . min ( boundingBox [ 0 ] , n . n . x ) ,
Math . min ( boundingBox [ 1 ] , n . n . y ) ,
Math . max ( boundingBox [ 2 ] , n . n . x ) ,
Math . max ( boundingBox [ 3 ] , n . n . y )
]
}
var center = [ ( boundingBox [ 2 ] + boundingBox [ 0 ] ) / 2 , ( boundingBox [ 3 ] + boundingBox [ 1 ] ) / 2 ] ;
RED . nodes . eachLink ( function ( link ) {
if ( nodes [ link . source . id ] && nodes [ link . target . id ] ) {
// A link wholely within the selection
}
if ( nodes [ link . source . id ] && ! nodes [ link . target . id ] ) {
// An outbound link from the selection
candidateOutputs . push ( link ) ;
removedLinks . push ( link ) ;
}
if ( ! nodes [ link . source . id ] && nodes [ link . target . id ] ) {
// An inbound link
candidateInputs . push ( link ) ;
removedLinks . push ( link ) ;
}
} ) ;
var outputs = { } ;
candidateOutputs = candidateOutputs . filter ( function ( v ) {
if ( outputs [ v . source . id + ":" + v . sourcePort ] ) {
outputs [ v . source . id + ":" + v . sourcePort ] . targets . push ( v . target ) ;
return false ;
}
v . targets = [ ] ;
v . targets . push ( v . target ) ;
outputs [ v . source . id + ":" + v . sourcePort ] = v ;
return true ;
} ) ;
candidateOutputs . sort ( function ( a , b ) { return a . source . y - b . source . y } ) ;
if ( candidateInputs . length > 1 ) {
RED . notify ( "<strong>Cannot create subflow</strong>: multiple inputs to selection" , "error" ) ;
return ;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0 ;
RED . nodes . eachSubflow ( function ( sf ) {
var m = ( new RegExp ( "^Subflow (\\d+)$" ) ) . exec ( sf . name ) ;
if ( m ) {
lastIndex = Math . max ( lastIndex , m [ 1 ] ) ;
}
} ) ;
var name = "Subflow " + ( lastIndex + 1 ) ;
var subflowId = RED . nodes . id ( ) ;
var subflow = {
type : "subflow" ,
id : subflowId ,
name : name ,
in : candidateInputs . map ( function ( v , i ) { return {
type : "subflow" ,
direction : "in" ,
x : v . target . x - ( v . target . w / 2 ) - 80 ,
y : v . target . y ,
z : subflowId ,
wires : [ { id : v . target . id } ]
} } ) ,
out : candidateOutputs . map ( function ( v ) { return {
type : "subflow" ,
direction : "in" ,
x : v . source . x + ( v . source . w / 2 ) + 80 ,
y : v . source . y ,
z : subflowId ,
wires : [ { id : v . source . id , port : v . sourcePort } ]
} } )
} ;
RED . nodes . addSubflow ( subflow ) ;
var subflowInstance = {
id : RED . nodes . id ( ) ,
type : "subflow:" + subflow . id ,
x : center [ 0 ] ,
y : center [ 1 ] ,
z : activeWorkspace ,
inputs : subflow . in . length ,
outputs : subflow . out . length ,
h : Math . max ( node _height , ( subflow . out . length || 0 ) * 15 ) ,
changed : true
}
subflowInstance . _def = RED . nodes . getType ( subflowInstance . type ) ;
RED . editor . validateNode ( subflowInstance ) ;
RED . nodes . add ( subflowInstance ) ;
candidateInputs . forEach ( function ( l ) {
var link = { source : l . source , sourcePort : l . sourcePort , target : subflowInstance } ;
new _links . push ( link ) ;
RED . nodes . addLink ( link ) ;
} ) ;
candidateOutputs . forEach ( function ( output , i ) {
output . targets . forEach ( function ( target ) {
var link = { source : subflowInstance , sourcePort : i , target : target } ;
new _links . push ( link ) ;
RED . nodes . addLink ( link ) ;
} ) ;
} ) ;
subflow . in . forEach ( function ( input ) {
input . wires . forEach ( function ( wire ) {
var link = { source : input , sourcePort : 0 , target : RED . nodes . node ( wire . id ) }
new _links . push ( link ) ;
RED . nodes . addLink ( link ) ;
} ) ;
} ) ;
subflow . out . forEach ( function ( output , i ) {
output . wires . forEach ( function ( wire ) {
var link = { source : RED . nodes . node ( wire . id ) , sourcePort : wire . port , target : output }
new _links . push ( link ) ;
RED . nodes . addLink ( link ) ;
} ) ;
} ) ;
for ( i = 0 ; i < removedLinks . length ; i ++ ) {
RED . nodes . removeLink ( removedLinks [ i ] ) ;
}
for ( i = 0 ; i < moving _set . length ; i ++ ) {
moving _set [ i ] . n . z = subflow . id ;
}
RED . history . push ( {
t : 'createSubflow' ,
nodes : [ subflowInstance . id ] ,
links : new _links ,
subflow : subflow ,
activeWorkspace : activeWorkspace ,
removedLinks : removedLinks ,
dirty : RED . view . dirty ( )
} ) ;
setDirty ( true ) ;
redraw ( ) ;
}
2013-09-05 16:02:48 +02:00
} ;
2014-08-08 01:01:35 +02:00
} ) ( ) ;