2 * Copyright 2013, 2014 IBM Corp.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 RED.view = (function() {
19 /* increasing the width and height from 5000 to 7500*/
20 var isImportAction = false;
21 var space_width = 7500,
23 lineCurveScale = 0.75,
28 var touchLongPressTimeout = 1000,
29 startTouchDistance = 0,
30 startTouchCenter = [],
35 var activeWorkspace = 0;
36 var workspaceScrollPositions = {};
38 var selected_link = null,
39 mousedown_link = null,
40 mousedown_node = null,
41 mousedown_port_type = null,
42 mousedown_port_index = 0,
45 mouse_position = null,
52 showNodePalette = true,
54 dblClickPrimed = null,
60 var status_colours = {
68 var outer = d3.select("#chart")
70 .attr("width", space_width)
71 .attr("height", space_height)
72 .attr("pointer-events", "all")
73 .style("cursor","crosshair");
77 .on("dblclick.zoom", null)
79 .on("mousemove", canvasMouseMove)
80 .on("mousedown", canvasMouseDown)
81 .on("mouseup", canvasMouseUp)
82 .on("touchend", function() {
83 clearTimeout(touchStartTime);
84 touchStartTime = null;
85 if (RED.touch.radialMenu.active()) {
89 outer_background.attr("fill","#fff");
91 canvasMouseUp.call(this);
93 .on("touchcancel", canvasMouseUp)
94 .on("touchstart", function() {
96 if (d3.event.touches.length>1) {
97 clearTimeout(touchStartTime);
98 touchStartTime = null;
99 d3.event.preventDefault();
100 touch0 = d3.event.touches.item(0);
101 var touch1 = d3.event.touches.item(1);
102 var a = touch0['pageY']-touch1['pageY'];
103 var b = touch0['pageX']-touch1['pageX'];
105 var offset = $("#chart").offset();
106 var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
108 (touch1['pageX']+(b/2)-offset.left+scrollPos[0])/scaleFactor,
109 (touch1['pageY']+(a/2)-offset.top+scrollPos[1])/scaleFactor
112 touch1['pageX']+(b/2),
113 touch1['pageY']+(a/2)
115 startTouchDistance = Math.sqrt((a*a)+(b*b));
117 var obj = d3.select(document.body);
118 touch0 = d3.event.touches.item(0);
119 var pos = [touch0.pageX,touch0.pageY];
120 startTouchCenter = [touch0.pageX,touch0.pageY];
121 startTouchDistance = 0;
122 var point = d3.touches(this)[0];
123 touchStartTime = setTimeout(function() {
124 touchStartTime = null;
125 showTouchMenu(obj,pos);
126 //lasso = vis.append('rect')
127 // .attr("ox",point[0])
128 // .attr("oy",point[1])
131 // .attr("x",point[0])
132 // .attr("y",point[1])
135 // .attr("class","lasso");
136 //outer_background.attr("fill","#e3e3f3");
137 },touchLongPressTimeout);
140 .on("touchmove", function(){
141 if (RED.touch.radialMenu.active()) {
142 d3.event.preventDefault();
146 if (d3.event.touches.length<2) {
147 if (touchStartTime) {
148 touch0 = d3.event.touches.item(0);
149 var dx = (touch0.pageX-startTouchCenter[0]);
150 var dy = (touch0.pageY-startTouchCenter[1]);
151 var d = Math.abs(dx*dx+dy*dy);
153 clearTimeout(touchStartTime);
154 touchStartTime = null;
157 d3.event.preventDefault();
159 canvasMouseMove.call(this);
161 touch0 = d3.event.touches.item(0);
162 var touch1 = d3.event.touches.item(1);
163 var a = touch0['pageY']-touch1['pageY'];
164 var b = touch0['pageX']-touch1['pageX'];
165 var offset = $("#chart").offset();
166 var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
167 var moveTouchDistance = Math.sqrt((a*a)+(b*b));
169 touch1['pageX']+(b/2),
170 touch1['pageY']+(a/2)
173 if (!isNaN(moveTouchDistance)) {
174 oldScaleFactor = scaleFactor;
175 scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000)));
177 var deltaTouchCenter = [ // Try to pan whilst zooming - not 100%
178 startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]),
179 startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1])
182 startTouchDistance = moveTouchDistance;
183 moveTouchCenter = touchCenter;
185 $("#chart").scrollLeft(scrollPos[0]+deltaTouchCenter[0]);
186 $("#chart").scrollTop(scrollPos[1]+deltaTouchCenter[1]);
192 var outer_background = vis.append('svg:rect')
193 .attr('width', space_width)
194 .attr('height', space_height)
195 .attr('fill','#fff');
197 //var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]);
198 //var grid = vis.append('g');
200 //grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter()
204 // "class":"horizontal",
207 // "y1" : function(d){ return gridScale(d);},
208 // "y2" : function(d){ return gridScale(d);},
210 // "shape-rendering" : "crispEdges",
211 // "stroke" : "#eee",
212 // "stroke-width" : "1px"
214 //grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter()
218 // "class":"vertical",
221 // "x1" : function(d){ return gridScale(d);},
222 // "x2" : function(d){ return gridScale(d);},
224 // "shape-rendering" : "crispEdges",
225 // "stroke" : "#eee",
226 // "stroke-width" : "1px"
230 var drag_line = vis.append("svg:path").attr("class", "drag_line");
232 var workspace_tabs = RED.tabs.create({
233 id: "workspace-tabs",
234 onchange: function(tab) {
235 if (tab.type == "subflow") {
236 $("#workspace-toolbar").show();
238 $("#workspace-toolbar").hide();
240 var chart = $("#chart");
241 if (activeWorkspace !== 0) {
242 workspaceScrollPositions[activeWorkspace] = {
243 left:chart.scrollLeft(),
244 top:chart.scrollTop()
247 var scrollStartLeft = chart.scrollLeft();
248 var scrollStartTop = chart.scrollTop();
250 activeWorkspace = tab.id;
251 if (workspaceScrollPositions[activeWorkspace]) {
252 chart.scrollLeft(workspaceScrollPositions[activeWorkspace].left);
253 chart.scrollTop(workspaceScrollPositions[activeWorkspace].top);
258 var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft;
259 var scrollDeltaTop = chart.scrollTop() - scrollStartTop;
260 if (mouse_position != null) {
261 mouse_position[0] += scrollDeltaLeft;
262 mouse_position[1] += scrollDeltaTop;
266 RED.nodes.eachNode(function(n) {
271 ondblclick: function(tab) {
272 showRenameWorkspaceDialog(tab.id);
274 onadd: function(tab) {
275 RED.menu.addItem("btn-workspace-menu",{
276 id:"btn-workspace-menu-"+tab.id.replace(".","-"),
278 onselect:function() {
279 workspace_tabs.activateTab(tab.id);
282 RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
284 onremove: function(tab) {
285 RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
286 RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-"));
290 var workspaceIndex = 0;
292 function addWorkspace() {
293 var tabId = RED.nodes.id();
296 } while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
298 var ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
299 RED.nodes.addWorkspace(ws);
300 workspace_tabs.addTab(ws);
301 workspace_tabs.activateTab(tabId);
302 RED.history.push({t:'add',workspaces:[ws],dirty:dirty});
303 RED.view.dirty(true);
306 $('#btn-workspace-add-tab').on("click",addWorkspace);
307 $('#btn-workspace-add').on("click",addWorkspace);
308 $('#btn-workspace-edit').on("click",function() {
309 showRenameWorkspaceDialog(activeWorkspace);
311 $('#btn-workspace-delete').on("click",function() {
312 deleteWorkspace(activeWorkspace);
316 function deleteWorkspace(id) {
317 if (workspace_tabs.count() == 1) {
320 var ws = RED.nodes.workspace(id);
321 $( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
322 $( "#node-dialog-delete-workspace-name" ).text(ws.label);
323 $( "#node-dialog-delete-workspace" ).dialog('open');
326 function canvasMouseDown() {
327 console.log("The state in canvasMouseDown:" + RED.view.state());
328 if (!mousedown_node && !mousedown_link) {
329 selected_link = null;
332 if (mouse_mode === 0) {
338 if (!touchStartTime) {
339 var point = d3.mouse(this);
340 lasso = vis.append('rect')
349 .attr("class","lasso");
350 d3.event.preventDefault();
355 function canvasMouseMove() {
356 mouse_position = d3.touches(this)[0]||d3.mouse(this);
358 // Prevent touch scrolling...
359 //if (d3.touches(this)[0]) {
360 // d3.event.preventDefault();
363 // TODO: auto scroll the container
364 //var point = d3.mouse(this);
365 //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; }
366 //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
369 var ox = parseInt(lasso.attr("ox"));
370 var oy = parseInt(lasso.attr("oy"));
371 var x = parseInt(lasso.attr("x"));
372 var y = parseInt(lasso.attr("y"));
375 if (mouse_position[0] < ox) {
376 x = mouse_position[0];
379 w = mouse_position[0]-x;
381 if (mouse_position[1] < oy) {
382 y = mouse_position[1];
385 h = mouse_position[1]-y;
396 if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
401 if (mouse_mode == RED.state.JOINING) {
403 drag_line.attr("class", "drag_line");
404 mousePos = mouse_position;
405 var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1;
406 var sourcePort = mousedown_port_index;
407 var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
409 var sc = (mousedown_port_type === 0)?1:-1;
411 var dy = mousePos[1]-(mousedown_node.y+portY);
412 var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2);
413 var delta = Math.sqrt(dy*dy+dx*dx);
414 var scale = lineCurveScale;
417 if (delta < node_width) {
418 scale = 0.75-0.75*((node_width-delta)/node_width);
421 scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
422 if (Math.abs(dy) < 3*node_height) {
423 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)) ;
428 "M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+
429 " C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+
430 (mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
431 mousePos[0]+" "+mousePos[1]
433 d3.event.preventDefault();
434 } else if (mouse_mode == RED.state.MOVING) {
435 //console.log("node mouse moving");
436 mousePos = mouse_position;
437 var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]);
439 mouse_mode = RED.state.MOVING_ACTIVE;
442 } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
443 //console.log("node mouse moving active or IMPORT_DRAGGING");
444 mousePos = mouse_position;
449 for (var n = 0; n<moving_set.length; n++) {
450 node = moving_set[n];
451 if (d3.event.shiftKey) {
452 node.n.ox = node.n.x;
453 node.n.oy = node.n.y;
455 node.n.x = mousePos[0]+node.dx;
456 node.n.y = mousePos[1]+node.dy;
458 minX = Math.min(node.n.x-node.n.w/2-5,minX);
459 minY = Math.min(node.n.y-node.n.h/2-5,minY);
461 if (minX !== 0 || minY !== 0) {
462 for (i = 0; i<moving_set.length; i++) {
463 node = moving_set[i];
468 if (d3.event.shiftKey && moving_set.length > 0) {
469 var gridOffset = [0,0];
470 node = moving_set[0];
471 gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2);
472 gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20));
473 if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
474 for (i = 0; i<moving_set.length; i++) {
475 node = moving_set[i];
476 node.n.x -= gridOffset[0];
477 node.n.y -= gridOffset[1];
478 if (node.n.x == node.n.ox && node.n.y == node.n.oy) {
488 function canvasMouseUp() {
489 console.log("The state in canvasMouseUp:" + RED.view.state());
490 if (mousedown_node && mouse_mode == RED.state.JOINING) {
491 drag_line.attr("class", "drag_line_hidden");
494 var x = parseInt(lasso.attr("x"));
495 var y = parseInt(lasso.attr("y"));
496 var x2 = x+parseInt(lasso.attr("width"));
497 var y2 = y+parseInt(lasso.attr("height"));
498 if (!d3.event.ctrlKey) {
501 RED.nodes.eachNode(function(n) {
502 if (n.z == activeWorkspace && !n.selected) {
503 n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
506 moving_set.push({n:n});
513 } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) {
517 if (mouse_mode == RED.state.MOVING_ACTIVE) {
518 //console.log("node moved active.");
519 //CSS setting view dirty if the node was moved
520 //RED.view.dirty(true);
521 if (moving_set.length > 0) {
523 for (var j=0;j<moving_set.length;j++) {
524 ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy});
526 RED.history.push({t:'move',nodes:ns,dirty:dirty});
529 if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
530 //console.log("node moving or MOVING_ACTIVE.");
531 for (var i=0;i<moving_set.length;i++) {
532 delete moving_set[i].ox;
533 delete moving_set[i].oy;
536 if (mouse_mode == RED.state.IMPORT_DRAGGING) {
537 RED.keyboard.remove(/* ESCAPE */ 27);
540 console.log("isImportAction:" + RED.view.getIsImportAction());
541 if (RED.view.getIsImportAction() === true){
542 RED.view.setIsImportAction(false);
543 console.log("updated isImportAction:" + isImportAction);
545 // clear mouse event vars
547 //save the imported DG
549 var obj = getCurrentFlowNodeSet();
551 //console.log("workspace id:" + RED.view.getWorkspace());
552 var dgTabId = RED.view.getWorkspace();
553 console.log("dgTabId:" + dgTabId);
554 $.post("/saveImportedDG",{"importedNodes" :JSON.stringify(obj,null,4),"currTabId": dgTabId})
555 .done(function( data ) {
556 console.log("saved imported DG");
558 .fail(function(err) {
559 console.log("error saving imported DG");
568 // clear mouse event vars
574 $('#btn-zoom-out').click(function() {zoomOut();});
575 $('#btn-zoom-zero').click(function() {zoomZero();});
576 $('#btn-zoom-in').click(function() {zoomIn();});
577 $("#chart").on('DOMMouseScroll mousewheel', function (evt) {
579 evt.preventDefault();
580 evt.stopPropagation();
581 var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta;
582 if (move <= 0) { zoomOut(); }
586 $("#chart").droppable({
587 accept:".palette_node",
588 drop: function( event, ui ) {
590 var selected_tool = ui.draggable[0].type;
591 var mousePos = d3.touches(this)[0]||d3.mouse(this);
592 mousePos[1] += this.scrollTop;
593 mousePos[0] += this.scrollLeft;
594 mousePos[1] /= scaleFactor;
595 mousePos[0] /= scaleFactor;
597 var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:activeWorkspace};
599 nn.type = selected_tool;
600 nn._def = RED.nodes.getType(nn.type);
601 nn.outputs = nn._def.outputs;
604 for (var d in nn._def.defaults) {
605 if (nn._def.defaults.hasOwnProperty(d)) {
606 nn[d] = nn._def.defaults[d].value;
611 nn._def.onadd.call(nn);
614 nn.h = Math.max(node_height,(nn.outputs||0) * 15);
615 RED.history.push({t:'add',nodes:[nn.id],dirty:dirty});
617 RED.editor.validateNode(nn);
619 // auto select dropped node - so info shows (if visible)
622 moving_set.push({n:nn});
626 if (nn._def.autoedit) {
633 if (scaleFactor < 2) {
639 if (scaleFactor > 0.3) {
644 function zoomZero() {
649 function selectAll() {
650 RED.nodes.eachNode(function(n) {
651 if (n.z == activeWorkspace) {
655 moving_set.push({n:n});
659 selected_link = null;
664 function clearSelection() {
665 for (var i=0;i<moving_set.length;i++) {
666 var n = moving_set[i];
668 n.n.selected = false;
671 selected_link = null;
674 function updateSelection() {
675 if (moving_set.length === 0) {
676 RED.menu.setDisabled("btn-export-menu",true);
677 RED.menu.setDisabled("btn-export-clipboard",true);
678 RED.menu.setDisabled("btn-export-library",true);
680 RED.menu.setDisabled("btn-export-menu",false);
681 RED.menu.setDisabled("btn-export-clipboard",false);
682 RED.menu.setDisabled("btn-export-library",false);
684 if (moving_set.length === 0 && selected_link == null) {
685 //RED.keyboard.remove(/* backspace */ 8);
686 RED.keyboard.remove(/* delete */ 46);
687 RED.keyboard.remove(/* c */ 67);
688 RED.keyboard.remove(/* x */ 88);
690 //RED.keyboard.add(/* backspace */ 8,function(){deleteSelection();d3.event.preventDefault();});
691 RED.keyboard.add(/* delete */ 46,function(){deleteSelection();d3.event.preventDefault();});
692 RED.keyboard.add(/* c */ 67,{ctrl:true},function(){copySelection();d3.event.preventDefault();});
693 RED.keyboard.add(/* x */ 88,{ctrl:true},function(){copySelection();deleteSelection();d3.event.preventDefault();});
695 if (moving_set.length === 0) {
696 RED.keyboard.remove(/* up */ 38);
697 RED.keyboard.remove(/* down */ 40);
698 RED.keyboard.remove(/* left */ 37);
699 RED.keyboard.remove(/* right*/ 39);
701 RED.keyboard.add(/* up */ 38, function() { if(d3.event.shiftKey){moveSelection( 0,-20)}else{moveSelection( 0,-1);}d3.event.preventDefault();},endKeyboardMove);
702 RED.keyboard.add(/* down */ 40, function() { if(d3.event.shiftKey){moveSelection( 0, 20)}else{moveSelection( 0, 1);}d3.event.preventDefault();},endKeyboardMove);
703 RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove);
704 RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove);
706 if (moving_set.length == 1) {
707 RED.sidebar.info.refresh(moving_set[0].n);
709 RED.sidebar.info.clear();
712 function endKeyboardMove() {
713 //console.log("end keyboard move.");
714 //CSS setting view dirty if the node was moved
715 //RED.view.dirty(true);
717 for (var i=0;i<moving_set.length;i++) {
718 ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy});
719 delete moving_set[i].ox;
720 delete moving_set[i].oy;
722 RED.history.push({t:'move',nodes:ns,dirty:dirty});
724 function moveSelection(dx,dy) {
729 for (var i=0;i<moving_set.length;i++) {
730 node = moving_set[i];
731 if (node.ox == null && node.oy == null) {
738 minX = Math.min(node.n.x-node.n.w/2-5,minX);
739 minY = Math.min(node.n.y-node.n.h/2-5,minY);
742 if (minX !== 0 || minY !== 0) {
743 for (var n = 0; n<moving_set.length; n++) {
744 node = moving_set[n];
752 function deleteSelection() {
753 var removedNodes = [];
754 var removedLinks = [];
755 var startDirty = dirty;
756 if (moving_set.length > 0) {
757 for (var i=0;i<moving_set.length;i++) {
758 var node = moving_set[i].n;
759 node.selected = false;
763 var rmlinks = RED.nodes.remove(node.id);
764 removedNodes.push(node);
765 removedLinks = removedLinks.concat(rmlinks);
771 RED.nodes.removeLink(selected_link);
772 removedLinks.push(selected_link);
775 RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,dirty:startDirty});
777 selected_link = null;
782 function copySelection() {
783 if (moving_set.length > 0) {
785 for (var n=0;n<moving_set.length;n++) {
786 var node = moving_set[n].n;
787 nns.push(RED.nodes.convertNode(node));
789 clipboard = JSON.stringify(nns);
790 RED.notify(moving_set.length+" node"+(moving_set.length>1?"s":"")+" copied");
795 function calculateTextWidth(str) {
796 var sp = document.createElement("span");
797 sp.className = "node_label";
798 sp.style.position = "absolute";
799 sp.style.top = "-1000px";
800 sp.innerHTML = (str||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
801 document.body.appendChild(sp);
802 var w = sp.offsetWidth;
803 document.body.removeChild(sp);
807 function resetMouseVars() {
808 mousedown_node = null;
810 mousedown_link = null;
812 mousedown_port_type = 0;
815 function portMouseDown(d,portType,portIndex) {
817 //vis.call(d3.behavior.zoom().on("zoom"), null);
819 selected_link = null;
820 mouse_mode = RED.state.JOINING;
821 mousedown_port_type = portType;
822 mousedown_port_index = portIndex || 0;
823 document.body.style.cursor = "crosshair";
824 d3.event.preventDefault();
827 function portMouseUp(d,portType,portIndex) {
828 document.body.style.cursor = "";
829 if (mouse_mode == RED.state.JOINING && mousedown_node) {
830 if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
831 RED.nodes.eachNode(function(n) {
832 if (n.z == activeWorkspace) {
835 if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
836 n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
838 portType = mouseup_node._def.inputs>0?1:0;
846 if (portType == mousedown_port_type || mouseup_node === mousedown_node) {
847 drag_line.attr("class", "drag_line_hidden");
851 var src,dst,src_port;
852 if (mousedown_port_type === 0) {
853 src = mousedown_node;
854 src_port = mousedown_port_index;
856 } else if (mousedown_port_type == 1) {
858 dst = mousedown_node;
859 src_port = portIndex;
862 var existingLink = false;
863 RED.nodes.eachLink(function(d) {
864 existingLink = existingLink || (d.source === src && d.target === dst && d.sourcePort == src_port);
867 var link = {source: src, sourcePort:src_port, target: dst};
868 RED.nodes.addLink(link);
869 RED.history.push({t:'add',links:[link],dirty:dirty});
872 selected_link = null;
877 function nodeMouseUp(d) {
878 if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < 750) {
881 d3.event.stopPropagation();
884 portMouseUp(d, d._def.inputs > 0 ? 1 : 0, 0);
887 function nodeMouseDown(d) {
888 //var touch0 = d3.event;
889 //var pos = [touch0.pageX,touch0.pageY];
890 //RED.touch.radialMenu.show(d3.select(this),pos);
891 if (mouse_mode == RED.state.IMPORT_DRAGGING) {
892 RED.keyboard.remove(/* ESCAPE */ 27);
897 d3.event.stopPropagation();
901 var now = Date.now();
902 clickElapsed = now-clickTime;
905 dblClickPrimed = (lastClickNode == mousedown_node);
906 lastClickNode = mousedown_node;
910 if (d.selected && d3.event.ctrlKey) {
912 for (i=0;i<moving_set.length;i+=1) {
913 if (moving_set[i].n === d) {
914 moving_set.splice(i,1);
919 if (d3.event.shiftKey) {
921 var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
922 for (var n=0;n<cnodes.length;n++) {
923 cnodes[n].selected = true;
924 cnodes[n].dirty = true;
925 moving_set.push({n:cnodes[n]});
927 } else if (!d.selected) {
928 if (!d3.event.ctrlKey) {
931 mousedown_node.selected = true;
932 moving_set.push({n:mousedown_node});
934 selected_link = null;
935 if (d3.event.button != 2) {
936 mouse_mode = RED.state.MOVING;
937 var mouse = d3.touches(this)[0]||d3.mouse(this);
938 mouse[0] += d.x-d.w/2;
939 mouse[1] += d.y-d.h/2;
940 for (i=0;i<moving_set.length;i++) {
941 moving_set[i].ox = moving_set[i].n.x;
942 moving_set[i].oy = moving_set[i].n.y;
943 moving_set[i].dx = moving_set[i].n.x-mouse[0];
944 moving_set[i].dy = moving_set[i].n.y-mouse[1];
946 mouse_offset = d3.mouse(document.body);
947 if (isNaN(mouse_offset[0])) {
948 mouse_offset = d3.touches(document.body)[0];
955 d3.event.stopPropagation();
958 function nodeButtonClicked(d) {
959 if (d._def.button.toggle) {
960 d[d._def.button.toggle] = !d[d._def.button.toggle];
963 if (d._def.button.onclick) {
964 d._def.button.onclick.call(d);
969 d3.event.preventDefault();
972 function showTouchMenu(obj,pos) {
973 var mdn = mousedown_node;
975 options.push({name:"delete",disabled:(moving_set.length===0),onselect:function() {deleteSelection();}});
976 options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}});
977 options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}});
978 options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}});
979 options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}});
980 options.push({name:"select",onselect:function() {selectAll();}});
981 options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
983 RED.touch.radialMenu.show(obj,pos,options);
987 vis.attr("transform","scale("+scaleFactor+")");
988 outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
990 if (mouse_mode != RED.state.JOINING) {
991 // Don't bother redrawing nodes if we're drawing links
993 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
994 node.exit().remove();
996 var nodeEnter = node.enter().insert("svg:g").attr("class", "node nodegroup");
997 nodeEnter.each(function(d,i) {
998 var node = d3.select(this);
999 node.attr("id",d.id);
1000 var l = d._def.label;
1001 l = (typeof l === "function" ? l.call(d) : l)||"";
1002 d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) );
1003 d.h = Math.max(node_height,(d.outputs||0) * 15);
1006 var badge = node.append("svg:g").attr("class","node_badge_group");
1007 var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15);
1008 badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr('text-anchor','end').text(d._def.badge());
1009 if (d._def.onbadgeclick) {
1010 badgeRect.attr("cursor","pointer")
1011 .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();});
1015 if (d._def.button) {
1016 var nodeButtonGroup = node.append('svg:g')
1017 .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; })
1018 .attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); });
1019 nodeButtonGroup.append('rect')
1023 .attr("height",node_height-4)
1024 .attr("fill","#eee");//function(d) { return d._def.color;})
1025 nodeButtonGroup.append('rect')
1026 .attr("x",function(d) { return d._def.align == "right"? 10:5})
1031 .attr("height",node_height-12)
1032 .attr("fill",function(d) { return d._def.color;})
1033 .attr("cursor","pointer")
1034 .on("mousedown",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}})
1035 .on("mouseup",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}})
1036 .on("mouseover",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);}})
1037 .on("mouseout",function(d) {if (!lasso) {
1039 if (d._def.button.toggle) {
1040 op = d[d._def.button.toggle]?1:0.2;
1042 d3.select(this).attr("fill-opacity",op);
1044 .on("click",nodeButtonClicked)
1045 .on("touchstart",nodeButtonClicked)
1048 var mainRect = node.append("rect")
1049 .attr("class", "node")
1050 .classed("node_unknown",function(d) { return d.type == "unknown"; })
1053 .attr("fill",function(d) { return d._def.color;})
1054 .on("mouseup",nodeMouseUp)
1055 .on("mousedown",nodeMouseDown)
1056 .on("touchstart",function(d) {
1057 var obj = d3.select(this);
1058 var touch0 = d3.event.touches.item(0);
1059 var pos = [touch0.pageX,touch0.pageY];
1060 startTouchCenter = [touch0.pageX,touch0.pageY];
1061 startTouchDistance = 0;
1062 touchStartTime = setTimeout(function() {
1063 showTouchMenu(obj,pos);
1064 },touchLongPressTimeout);
1065 nodeMouseDown.call(this,d)
1067 .on("touchend", function(d) {
1068 clearTimeout(touchStartTime);
1069 touchStartTime = null;
1070 if (RED.touch.radialMenu.active()) {
1071 d3.event.stopPropagation();
1074 nodeMouseUp.call(this,d);
1076 .on("mouseover",function(d) {
1077 if (mouse_mode === 0) {
1078 var node = d3.select(this);
1079 node.classed("node_hovered",true);
1082 .on("mouseout",function(d) {
1083 var node = d3.select(this);
1084 node.classed("node_hovered",false);
1087 //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");
1088 //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");
1092 var icon_group = node.append("g")
1093 .attr("class","node_icon_group")
1094 .attr("x",0).attr("y",0);
1096 var icon_shade = icon_group.append("rect")
1097 .attr("x",0).attr("y",0)
1098 .attr("class","node_icon_shade")
1100 .attr("stroke","none")
1101 .attr("fill","#000")
1102 .attr("fill-opacity","0.05")
1103 .attr("height",function(d){return Math.min(50,d.h-4);});
1105 var icon = icon_group.append("image")
1106 .attr("xlink:href","icons/"+d._def.icon)
1107 .attr("class","node_icon")
1110 .attr("height","30");
1112 var icon_shade_border = icon_group.append("path")
1113 .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)})
1114 .attr("class","node_icon_shade_border")
1115 .attr("stroke-opacity","0.1")
1116 .attr("stroke","#000")
1117 .attr("stroke-width","2");
1119 if ("right" == d._def.align) {
1120 icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align);
1121 icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)})
1122 //icon.attr('class','node_icon node_icon_'+d._def.align);
1123 //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
1124 //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align);
1127 //if (d._def.inputs > 0 && d._def.align == null) {
1128 // icon_shade.attr("width",35);
1129 // icon.attr("transform","translate(5,0)");
1130 // icon_shade_border.attr("transform","translate(5,0)");
1132 //if (d._def.outputs > 0 && "right" == d._def.align) {
1133 // icon_shade.attr("width",35); //icon.attr("x",5);
1136 var img = new Image();
1137 img.src = "icons/"+d._def.icon;
1138 img.onload = function() {
1139 icon.attr("width",Math.min(img.width,30));
1140 icon.attr("height",Math.min(img.height,30));
1141 icon.attr("x",15-Math.min(img.width,30)/2);
1142 //if ("right" == d._def.align) {
1143 // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);});
1144 // icon_shade.attr("x",function(d){return d.w-30});
1145 // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);});
1149 //icon.style("pointer-events","none");
1150 icon_group.style("pointer-events","none");
1152 var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start');
1154 text.attr('class','node_label node_label_'+d._def.align);
1155 text.attr('text-anchor','end');
1158 var status = node.append("svg:g").attr("class","node_status_group").style("display","none");
1160 var statusRect = status.append("rect").attr("class","node_status")
1161 .attr("x",6).attr("y",1).attr("width",9).attr("height",9)
1162 .attr("rx",2).attr("ry",2).attr("stroke-width","3");
1164 var statusLabel = status.append("svg:text")
1165 .attr("class","node_status_label")
1166 .attr('x',20).attr('y',9)
1172 'text-anchor':'start'
1175 var dgNumber = node.append("svg:g").attr("class","node_dgnumber_group").style("display","none");
1177 /*var dgNumberRect = dgNumber.append("rect").attr("class","node_dgnumber")
1178 .attr("x",6).attr("y",-49).attr("width",9).attr("height",9)
1179 .attr("rx",2).attr("ry",2).attr("stroke-width","3");
1182 var dgNumberLabel = dgNumber.append("svg:text")
1183 .attr("class","node_dgnumber_label")
1184 .attr('x',1).attr('y',-43)
1187 /*'fill': '#2E4F83',*/
1191 'text-anchor':'start'
1194 var dgNumberTitle = dgNumber.append("title")
1195 .attr("class","node_dgnumber_title");
1197 //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5});
1199 if (d._def.inputs > 0) {
1201 node.append("rect").attr("class","port port_input").attr("rx",3).attr("ry",3).attr("x",-5).attr("width",10).attr("height",10)
1202 .on("mousedown",function(d){portMouseDown(d,1,0);})
1203 .on("touchstart",function(d){portMouseDown(d,1,0);})
1204 .on("mouseup",function(d){portMouseUp(d,1,0);} )
1205 .on("touchend",function(d){portMouseUp(d,1,0);} )
1206 .on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
1207 .on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
1210 //node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z");
1211 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);
1212 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);
1215 node.each(function(d,i) {
1217 //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
1219 var l = d._def.label;
1220 l = (typeof l === "function" ? l.call(d) : l)||"";
1221 d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) );
1222 d.h = Math.max(node_height,(d.outputs||0) * 15);
1224 var thisNode = d3.select(this);
1225 //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
1226 thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
1227 thisNode.selectAll(".node")
1228 .attr("width",function(d){return d.w})
1229 .attr("height",function(d){return d.h})
1230 .classed("node_selected",function(d) { return d.selected; })
1231 .classed("node_highlighted",function(d) { return d.highlighted; })
1233 //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
1234 //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
1236 thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"});
1237 thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38});
1238 //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
1239 //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
1240 //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
1243 var numOutputs = d.outputs;
1244 var y = (d.h/2)-((numOutputs-1)/2)*13;
1245 d.ports = d.ports || d3.range(numOutputs);
1246 d._ports = thisNode.selectAll(".port_output").data(d.ports);
1247 d._ports.enter().append("rect").attr("class","port port_output").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
1248 .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
1249 .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
1250 .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
1251 .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
1252 .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
1253 .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
1254 d._ports.exit().remove();
1256 numOutputs = d.outputs || 1;
1257 y = (d.h/2)-((numOutputs-1)/2)*13;
1259 d._ports.each(function(d,i) {
1260 var port = d3.select(this);
1261 port.attr("y",(y+13*i)-5).attr("x",x);
1264 thisNode.selectAll('text.node_label').text(function(d,i){
1266 if (typeof d._def.label == "function") {
1267 return d._def.label.call(d);
1269 return d._def.label;
1274 .attr('y', function(d){return (d.h/2)-1;})
1275 .attr('class',function(d){
1276 return 'node_label'+
1277 (d._def.align?' node_label_'+d._def.align:'')+
1278 (d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
1280 thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;});
1282 thisNode.selectAll(".node_changed")
1283 .attr("x",function(d){return d.w-10})
1284 .classed("hidden",function(d) { return !d.changed; });
1286 thisNode.selectAll(".node_error")
1287 .attr("x",function(d){return d.w-10-(d.changed?13:0)})
1288 .classed("hidden",function(d) { return d.valid; });
1290 thisNode.selectAll(".port_input").each(function(d,i) {
1291 var port = d3.select(this);
1292 port.attr("y",function(d){return (d.h/2)-5;})
1295 thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
1296 thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
1297 thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
1300 thisNode.selectAll('.node_right_button').attr("transform",function(d){
1302 if (d._def.button.toggle && !d[d._def.button.toggle]) {
1305 return "translate("+x+",2)";
1307 thisNode.selectAll('.node_right_button rect').attr("fill-opacity",function(d){
1308 if (d._def.button.toggle) {
1309 return d[d._def.button.toggle]?1:0.2;
1314 //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
1315 // 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)
1318 thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
1319 thisNode.selectAll('text.node_badge_label').text(function(d,i) {
1321 if (typeof d._def.badge == "function") {
1322 return d._def.badge.call(d);
1324 return d._def.badge;
1329 if (!showStatus || !d.status) {
1330 thisNode.selectAll('.node_status_group').style("display","none");
1332 thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")");
1333 var fill = status_colours[d.status.fill]; // Only allow our colours for now
1334 if (d.status.shape == null && fill == null) {
1335 thisNode.selectAll('.node_status').style("display","none");
1338 if (d.status.shape == null || d.status.shape == "dot") {
1344 } else if (d.status.shape == "ring" ){
1351 thisNode.selectAll('.node_status').style(style);
1353 if (d.status.text) {
1354 thisNode.selectAll('.node_status_label').text(d.status.text);
1356 thisNode.selectAll('.node_status_label').text("");
1359 //console.dir("d value");
1361 if (showNumbers && d.dgnumber != null && d.dgnumber != undefined && d.dgnumber.length >0) {
1362 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")");
1363 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1364 var dgnumberList = d.dgnumber;
1366 if(dgnumberList != null && dgnumberList.length >=1){
1367 dgnum = dgnumberList[0];
1368 thisNode.select('.node_dgnumber_label').text(dgnum);
1369 //console.log(dgnumberList);
1370 thisNode.select('.node_dgnumber_title').text(dgnumberList);
1373 if(d.dgnumber.length > 1){
1374 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h-15)+")");
1375 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1377 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")");
1378 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1382 //console.log("fhfjhfjh ");
1383 thisNode.select('.node_dgnumber').style("display","none");
1384 thisNode.select('.node_dgnumber_label').text("");
1385 thisNode.select('.node_dgnumber_title').text("");
1393 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;});
1395 var linkEnter = link.enter().insert("g",".node").attr("class","link");
1397 linkEnter.each(function(d,i) {
1398 var l = d3.select(this);
1399 l.append("svg:path").attr("class","link_background link_path")
1400 .on("mousedown",function(d) {
1403 selected_link = mousedown_link;
1406 d3.event.stopPropagation();
1408 .on("touchstart",function(d) {
1411 selected_link = mousedown_link;
1414 d3.event.stopPropagation();
1416 l.append("svg:path").attr("class","link_outline link_path");
1417 l.append("svg:path").attr("class","link_line link_path");
1420 link.exit().remove();
1422 var links = vis.selectAll(".link_path")
1423 links.attr("d",function(d){
1424 var numOutputs = d.source.outputs || 1;
1425 var sourcePort = d.sourcePort || 0;
1426 var y = -((numOutputs-1)/2)*13 +13*sourcePort;
1428 var dy = d.target.y-(d.source.y+y);
1429 var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
1430 var delta = Math.sqrt(dy*dy+dx*dx);
1431 var scale = lineCurveScale;
1433 if (delta < node_width) {
1434 scale = 0.75-0.75*((node_width-delta)/node_width);
1438 scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
1439 if (Math.abs(dy) < 3*node_height) {
1440 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)) ;
1444 d.x1 = d.source.x+d.source.w/2;
1445 d.y1 = d.source.y+y;
1446 d.x2 = d.target.x-d.target.w/2;
1449 return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+
1450 " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+
1451 (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+
1452 (d.target.x-d.target.w/2)+" "+d.target.y;
1455 link.classed("link_selected", function(d) { return d === selected_link || d.selected; });
1456 link.classed("link_unknown",function(d) { return d.target.type == "unknown" || d.source.type == "unknown"});
1459 d3.event.preventDefault();
1463 RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();});
1464 RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
1465 RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
1466 RED.keyboard.add(/* - */ 189,{ctrl:true},function(){zoomOut();d3.event.preventDefault();});
1467 RED.keyboard.add(/* 0 */ 48,{ctrl:true},function(){zoomZero();d3.event.preventDefault();});
1468 RED.keyboard.add(/* v */ 86,{ctrl:true},function(){importNodes(clipboard);d3.event.preventDefault();});
1469 RED.keyboard.add(/* e */ 69,{ctrl:true},function(){showExportNodesDialog();d3.event.preventDefault();});
1470 RED.keyboard.add(/* i */ 73,{ctrl:true},function(){showImportNodesDialog();d3.event.preventDefault();});
1471 RED.keyboard.add(/* B */ 66,{ctrl:true},function(){RED.view.showDgNumberDialog();d3.event.preventDefault();});
1472 RED.keyboard.add(/* [ */ 219,{ctrl:true},function(){RED.view.showSearchTextDialog();d3.event.preventDefault();});
1473 RED.keyboard.add(/* O */ 79,{ctrl:true},function(){RED.view.showRequestTemplateDialog();d3.event.preventDefault();});
1476 // TODO: 'dirty' should be a property of RED.nodes - with an event callback for ui hooks
1477 function setDirty(d) {
1480 $("#btn-deploy").removeClass("disabled");
1482 $("#btn-deploy").addClass("disabled");
1487 * Imports a new collection of nodes from a JSON String.
1488 * - all get new IDs assigned
1490 * - attached to mouse for placing - 'IMPORT_DRAGGING'
1492 function importNodes(newNodesStr,touchImport) {
1494 var result = RED.nodes.import(newNodesStr,true);
1496 var new_nodes = result[0];
1497 var new_links = result[1];
1498 var new_workspaces = result[2];
1500 var new_ms = new_nodes.filter(function(n) { return n.z == activeWorkspace }).map(function(n) { return {n:n};});
1501 var new_node_ids = new_nodes.map(function(n){ return n.id; });
1503 // TODO: pick a more sensible root node
1504 if (new_ms.length > 0) {
1505 var root_node = new_ms[0].n;
1506 var dx = root_node.x;
1507 var dy = root_node.y;
1509 if (mouse_position == null) {
1510 mouse_position = [0,0];
1518 for (i=0;i<new_ms.length;i++) {
1520 node.n.selected = true;
1521 node.n.changed = true;
1522 node.n.x -= dx - mouse_position[0];
1523 node.n.y -= dy - mouse_position[1];
1524 node.dx = node.n.x - mouse_position[0];
1525 node.dy = node.n.y - mouse_position[1];
1526 minX = Math.min(node.n.x-node_width/2-5,minX);
1527 minY = Math.min(node.n.y-node_height/2-5,minY);
1529 for (i=0;i<new_ms.length;i++) {
1537 mouse_mode = RED.state.IMPORT_DRAGGING;
1540 RED.keyboard.add(/* ESCAPE */ 27,function(){
1541 RED.keyboard.remove(/* ESCAPE */ 27);
1547 moving_set = new_ms;
1550 RED.history.push({t:'add',nodes:new_node_ids,links:new_links,workspaces:new_workspaces,dirty:RED.view.dirty()});
1556 console.log(error.stack);
1557 RED.notify("<strong>Error</strong>: "+error,"error");
1561 function showExportNodesDialog() {
1562 mouse_mode = RED.state.EXPORT;
1563 var nns = RED.nodes.createExportableNodeSet(moving_set);
1564 $("#dialog-form").html($("script[data-template-name='export-clipboard-dialog']").html());
1565 $("#node-input-export").val(JSON.stringify(nns));
1566 $("#node-input-export").focus(function() {
1567 var textarea = $(this);
1569 textarea.mouseup(function() {
1570 textarea.unbind("mouseup");
1574 $( "#dialog" ).dialog("option","title","Export nodes to clipboard").dialog( "open" );
1575 $("#node-input-export").focus();
1578 function showExportNodesLibraryDialog() {
1579 mouse_mode = RED.state.EXPORT;
1580 var nns = RED.nodes.createExportableNodeSet(moving_set);
1581 $("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
1582 $("#node-input-filename").attr('nodes',JSON.stringify(nns));
1583 $( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" );
1586 function showImportNodesDialog() {
1587 mouse_mode = RED.state.IMPORT;
1588 $("#dialog-form").html($("script[data-template-name='import-dialog']").html());
1589 $("#node-input-import").val("");
1590 $( "#dialog" ).dialog("option","title","Import nodes").dialog( "open" );
1593 function showRenameWorkspaceDialog(id) {
1594 var ws = RED.nodes.workspace(id);
1595 $( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
1597 if (workspace_tabs.count() == 1) {
1598 $( "#node-dialog-rename-workspace").next().find(".leftButton")
1599 .prop('disabled',true)
1600 .addClass("ui-state-disabled");
1602 $( "#node-dialog-rename-workspace").next().find(".leftButton")
1603 .prop('disabled',false)
1604 .removeClass("ui-state-disabled");
1607 $( "#node-input-workspace-name" ).val(ws.label);
1608 $( "#node-dialog-rename-workspace" ).dialog("open");
1611 $("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
1612 $( "#node-dialog-rename-workspace" ).dialog({
1616 title: "Rename sheet",
1619 class: 'leftButton',
1622 var workspace = $(this).dialog('option','workspace');
1623 $( this ).dialog( "close" );
1624 deleteWorkspace(workspace.id);
1630 var workspace = $(this).dialog('option','workspace');
1631 var label = $( "#node-input-workspace-name" ).val();
1632 if (workspace.label != label) {
1633 workspace.label = label;
1634 var link = $("#workspace-tabs a[href='#"+workspace.id+"']");
1635 link.attr("title",label);
1637 RED.view.dirty(true);
1639 $( this ).dialog( "close" );
1645 $( this ).dialog( "close" );
1650 RED.keyboard.disable();
1652 close: function(e) {
1653 RED.keyboard.enable();
1658 $( "#node-dialog-delete-workspace" ).dialog({
1662 title: "Confirm delete",
1667 var workspace = $(this).dialog('option','workspace');
1668 RED.view.removeWorkspace(workspace);
1669 var historyEvent = RED.nodes.removeWorkspace(workspace.id);
1670 historyEvent.t = 'delete';
1671 historyEvent.dirty = dirty;
1672 historyEvent.workspaces = [workspace];
1673 RED.history.push(historyEvent);
1674 RED.view.dirty(true);
1675 $( this ).dialog( "close" );
1681 $( this ).dialog( "close" );
1686 RED.keyboard.disable();
1688 close: function(e) {
1689 RED.keyboard.enable();
1694 state:function(state) {
1695 if (state == null) {
1701 addWorkspace: function(ws) {
1702 workspace_tabs.addTab(ws);
1703 workspace_tabs.resize();
1705 removeWorkspace: function(ws) {
1706 workspace_tabs.removeTab(ws.id);
1708 getWorkspace: function() {
1709 return activeWorkspace;
1711 setIsImportAction: function(iaction) {
1712 isImportAction = iaction ;
1714 getIsImportAction: function() {
1715 return isImportAction ;
1717 showWorkspace: function(id) {
1718 workspace_tabs.activateTab(id);
1721 dirty: function(d) {
1728 importNodes: importNodes,
1729 resize: function() {
1730 workspace_tabs.resize();
1732 status: function(s) {
1733 validateEachNodeXml();
1735 RED.nodes.eachNode(function(n) { n.dirty = true;});
1736 //TODO: subscribe/unsubscribe here
1739 showYangUploadDialog:function showYangUploadDialog(){
1741 var htmlStr= "<div id='yang-upload-div' style='width:375;height:225'>" +
1742 '<form id="uploadForm" name="uploadForm" enctype="multipart/form-data" action="/api/uploadyang" method="post" >' +
1743 "<input id='yang-file-id' name='yangFile' type='file' accept='.yang,.zip'><p style='font-size:0.7em'><i>For Module depending on multiple yang files, zip them and upload the zip file</i</p><br><br><br><br><br><p id='yang-upload-status'></p>" +
1744 //'<input id="upload-yang-button-id" style="font-size:1em;font-weight:bold" type="button" value="Upload Yang" name="upload-yang-button">' +
1747 $("#yang-upload-dialog").dialog({
1750 title: "Upload Yang",
1757 text: "Upload Yang",
1759 if( document.getElementById("yang-file-id").files.length == 0 ){
1760 $("#yang-upload-status").html("<span>No files selected.</span>");
1763 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("disable");
1764 //$("#yang-upload-status").empty().text("File is uploading...");
1765 $("#yang-upload-status").html("<span>Processing...Please wait</span><img src='images/page-loading.gif'>");
1767 url: "/api/uploadyang",
1769 data: new FormData(document.forms['uploadForm']),
1773 success: function(data) {
1774 $("#yang-upload-status").html("");
1775 $("#yang-upload-status").text(data.msg);
1776 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable");
1780 reqInputValues = {};
1781 for(var i=0;i<data.sliValuesObj.length;i++){
1782 var moduleName = data.sliValuesObj[i].moduleName;
1783 sliValuesObj[moduleName] = data.sliValuesObj[i][moduleName + '_PROPS'];
1784 rpcValues[moduleName] = data.sliValuesObj[i][ moduleName +'_RPCS'];
1785 for(var k=0;rpcValues[moduleName] != undefined && k<rpcValues[moduleName].length;k++){
1786 var rpcName = rpcValues[moduleName][k];
1787 reqInputValues[moduleName + "_" + rpcName] = data.sliValuesObj[i][rpcName +"-input"];
1791 //close the yang upload dialogog box and open the load dialogbox
1792 $('#yang-upload-dialog').dialog("close");
1793 $("#btn-available-yang-modules").trigger("click");
1795 error:function (xhr, desc, err){
1796 $("#yang-upload-status").html(err);
1797 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable");
1805 $("#yang-upload-dialog").dialog("close");
1809 }).dialog("open").html(htmlStr);
1812 showDgNumberDialog: function showDgNumberDialog(){
1814 var isLoopDetected = detectLoop();
1815 console.log("isLoopDetected:" + isLoopDetected);
1820 var htmlStr="<div id='find-dgnumber-div' style='width:375;height:225'><label>DG Number</label><input id='dgnumber-val-id' type='text' value=''><p id='find-dgnumber-status' style='color:red'></p></div>";
1821 $("#dgnumber-find-dialog").dialog({
1824 title: "Find Node By DGNumber",
1833 var dgnumVal = $("#dgnumber-val-id").val();
1834 $("#find-dgnumber-status").text("");
1835 if(dgnumVal != undefined && dgnumVal != '' && dgnumVal != ''){
1836 dgnumVal = dgnumVal.trim();
1841 var dgNumberFound = false;
1842 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
1843 node.each(function(d,i) {
1844 var thisNode = d3.select(this);
1845 var dgn = d.dgnumber;
1847 if(dgn != undefined && typeof dgn == 'object'){
1849 for(var k=0;k<dgn.length;k++){
1850 if(dgn[k] == dgnumVal){
1856 //thisNode.select("rect").style({"stroke":"blue","stroke-width" :"3","stroke-dasharray":"5,1"});
1857 //$("#" + d.id).find("rect").attr("class","node-found-selected");
1858 //$("#" + d.id).find("rect").attr("class","node node_selected");
1859 //thisNode.select("rect").attr("class","node node_selected");
1860 thisNode.select("rect").attr("class","node node_found");
1861 document.getElementById( d.id ).scrollIntoView();
1862 $("#dgnumber-find-dialog").dialog("close");
1864 //display the node edit dialogbox
1866 dgNumberFound = true;
1868 //thisNode.select("rect").style({"stroke":"#999","stroke-width" :"2","stroke-dasharray":"none"});
1869 //$("#" + d.id ).find("rect").attr("class","node-found-clear");
1870 thisNode.select("rect").attr("class","node");
1871 //$("#" + d.id ).find("rect").attr("class","node");
1872 //$("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found");
1878 $("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found");
1885 $("#dgnumber-find-dialog").dialog("close");
1890 //Bind the Enter key to Find button
1891 $('#dgnumber-find-dialog').keypress(function(e) {
1892 if (e.keyCode == $.ui.keyCode.ENTER) {
1893 $('#dgnumber-find-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
1898 //set focus on the input box
1899 $("#dgnumber-val-id").focus();
1902 }).dialog("open").html(htmlStr);
1905 showSearchTextDialog: function showSearchTextDialog(){
1907 var isLoopDetected = detectLoop();
1908 console.log("isLoopDetected:" + isLoopDetected);
1913 //console.log("In the showSearchTextDialog.");
1914 var htmlStr="<div id='search-text-div' style='width:675;height:525'><label>Search Text</label><input style='width:500px;' id='search-text-val-id' type='text' value=''><br><input id='ignore-case-id' type='checkbox' name='ignorecase' value='1' >Ignore Case<br><p id='search-text-status' style='color:red'></p></div>";
1915 //console.log("setting up search-text-dialog.");
1916 $("#search-text-dialog").dialog({
1919 title: "Search text in DG",
1929 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
1930 var searchText = $("#search-text-val-id").val();
1931 $("#search-text-status").text("");
1932 if(searchText != undefined && searchText != '' && searchText != ''){
1933 searchText = searchText.trim();
1936 node.each(function(d,i) {
1937 var thisNode = d3.select(this);
1938 thisNode.select("rect").attr("class","node");
1943 var foundSearchText = false;
1944 var foundInDgNumArr = [];
1945 //console.log("In search function");
1946 node.each(function(d,i) {
1947 var thisNode = d3.select(this);
1948 var dgn = d.dgnumber;
1952 var ignoreCase = $('#ignore-case-id').prop('checked')
1957 var searchPattern = new RegExp(searchText, options );
1958 if(xml == undefined || xml == null){
1961 if(nName == undefined || nName == null){
1964 //console.log(searchPattern);
1965 var count1 = (xml.match(searchPattern) || []).length;
1966 //console.log(count1);
1967 var count2 = (nName.match(searchPattern) || []).length;
1968 //console.log(count2);
1970 if(count1 >0 || count2 > 0){
1971 thisNode.select("rect").attr("class","node text_found");
1972 var dgn = d.dgnumber;
1975 if(dgn != undefined && typeof dgn == 'object'){
1976 console.log("DGNUMBERS:" + dgn);
1979 if(dgn != undefined ){
1980 foundInDgNumArr.push(dgNumber);
1982 foundInDgNumArr.push(d.type);
1984 foundSearchText=true;
1986 thisNode.select("rect").attr("class","node");
1989 if(!foundSearchText){
1990 $("#search-text-status").text("Search Text :" + searchText + " Not found");
1992 //console.log("closing dialog");
1993 //$("#search-text-dialog").dialog("close");
1994 console.log(foundInDgNumArr);
1995 $("#search-text-status").text("Found in DG numbers :" + foundInDgNumArr);
2002 //console.log("closing dialog");
2003 $("#search-text-dialog").dialog("close");
2008 //console.log("called open.");
2009 //Bind the Enter key to Find button
2010 $('#search-text-dialog').keypress(function(e) {
2011 if (e.keyCode == $.ui.keyCode.ENTER) {
2012 $('#search-text-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
2017 //set focus on the input box
2018 $("#search-text-id").focus();
2020 //console.log("done open call.");
2022 }).dialog('open').html(htmlStr);
2025 showRequestTemplateDialog: function showRequestTemplateDialog(){
2027 var currNodes = RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace })
2028 var moduleName = "";
2030 if(currNodes != null && currNodes.length > 1){
2031 currNodes.forEach(function(n){
2032 if(n.type == 'service-logic'){
2033 moduleName = getAttributeValue(n.xml,"module");
2034 }else if(n.type == 'method'){
2035 rpcName = getAttributeValue(n.xml,"rpc");
2039 console.log("moduleName:" + moduleName);
2040 console.log("rpcName:" + rpcName);
2041 var inputValObj = reqInputValues[moduleName + "_" + rpcName];
2042 var inputValStr = "Not found. Please make sure that the Module is loaded and the rpc has input.";
2043 if(inputValObj != undefined && inputValObj != null){
2044 inputValStr = "{\n\"input\" : " + JSON.stringify(inputValObj,null,4)+ "\n}";
2047 //var htmlStr="<div id='request-template-div' style='width:875px;height:575px'><textarea style='width:875px;height:575px'>" + inputValStr + "</textarea></div>"
2048 //var htmlStr="<div id='request-template-div' style='width:750px;height:550px;font-weight:bold;font-size:1em'><pre>" + inputValStr + "</pre></div>"
2049 var htmlStr="<textarea readonly='1' id='request-template-textarea' style='width:750px;height:550px;font-weight:bold;font-size:1em'>" + inputValStr + "</textarea>"
2050 $("#request-input-dialog").dialog({
2051 dialogClass :"no-close",
2054 title: "Request Template for Module:" + moduleName + " RPC:" + rpcName,
2061 $("#request-input-dialog").dialog("close");
2066 $('#request-input-dialog').css('overflow', 'hidden');
2068 }).dialog("open").html(htmlStr);
2071 showNumbers: function(s) {
2072 console.log("showNumbers:" + s);
2074 RED.nodes.eachNode(function(n) { n.dirty = true;});
2077 diffJsonSinceImportDialog: function diffJsonSinceImportDialog(){
2078 var currDGObj = getCurrentFlowNodeSet();
2079 var currDGObjStr = JSON.stringify(currDGObj,null,4);
2080 //console.log(currDGObjStr);
2082 var htmlStr = "<div id=\"flex-container\">" +
2083 "<div><div id=\"editor1\"></div></div>" +
2084 "<div id=\"gutter\"></div>" +
2085 "<div><div id=\"editor2\"></div></div>" +
2090 "var aceDiffer = new AceDiff({" +
2091 "mode: \"ace/mode/json\"," +
2092 "theme: \"ace/theme/eclipse\"," +
2094 "id: \"editor1\"," +
2095 "content: $(\"#example-content-1\").html()," +
2096 "editable: false," +
2097 "copyLinkEnabled: false" +
2100 "id: \"editor2\"," +
2101 "content: $(\"#example-content-2\").html()," +
2102 "editable: false," +
2103 "copyLinkEnabled: false" +
2106 "gutterID: \"gutter\"" +
2111 var origDGFile ="[]";
2112 var diffStatus = "DG JSON UNCHANGED";
2113 $.get("/readFile",{"filePath" : "orig_dgs/" + activeWorkspace })
2114 .done(function( data ) {
2115 if(data != undefined && data != null && data.output != undefined ){
2116 origDGFile= data.output;
2119 .fail(function(err) {
2121 .always(function() {
2122 if(origDGFile != currDGObjStr){
2123 diffStatus="DG JSON CHANGED";
2125 htmlStr += "<div id=\"example-content-1\" style=\"display: none\">" +
2128 "<div id=\"example-content-2\" style=\"display: none\">" +
2132 //var htmlStr='<object type="text/html" data="display-diff.html" ></object>';
2134 $("#diff-browser-dialog").dialog({
2137 title: "Json Diff :" + diffStatus,
2146 //$( this ).dialog( "close" );
2147 $("#diff-browser-dialog").dialog("close");
2152 $('#diff-browser-dialog').keypress(function(e) {
2153 if (e.keyCode == $.ui.keyCode.ENTER) {
2154 $('#diff-browser-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
2159 }).dialog('open').html(htmlStr);
2163 diffXmlSinceImportDialog: function diffXmlSinceImportDialog(){
2164 var currDGObj = getCurrentFlowNodeSet();
2165 //console.dir(currDGObj);
2166 var currDGObjStr ="";
2168 currDGObjStr = getNodeToXml(JSON.stringify(currDGObj));
2171 var curr_formatted_xml = vkbeautify.xml(currDGObjStr);
2172 //console.log(curr_formatted_xml);
2173 //console.log(currDGObjStr);
2175 var htmlStr = "<div id=\"flex-container\">" +
2176 "<div><div id=\"editor1\"></div></div>" +
2177 "<div id=\"gutter\"></div>" +
2178 "<div><div id=\"editor2\"></div></div>" +
2183 "var aceDiffer = new AceDiff({" +
2184 "mode: \"ace/mode/xml\"," +
2185 "theme: \"ace/theme/eclipse\"," +
2186 //"theme: \"ace/theme/twilight\"," +
2188 "id: \"editor1\"," +
2189 "content: $(\"#example-content-1\").html()," +
2190 "editable: false," +
2191 "copyLinkEnabled: false" +
2194 "id: \"editor2\"," +
2195 "content: $(\"#example-content-2\").html()," +
2196 "editable: false," +
2197 "copyLinkEnabled: false" +
2200 "gutterID: \"gutter\"" +
2205 var origXmlFile ="";
2207 var diffStatus = "DG XML UNCHANGED";
2208 $.get("/readFile",{"filePath" : "orig_dgs/" + activeWorkspace })
2209 .done(function( data ) {
2210 if(data != undefined && data != null && data.output != undefined ){
2211 origDGFile= data.output;
2213 var origDGObjStr = getNodeToXml(origDGFile);
2214 origXmlFile = vkbeautify.xml(origDGObjStr);
2219 .fail(function(err) {
2221 .always(function() {
2222 if(origXmlFile != curr_formatted_xml){
2223 diffStatus = "DG XML CHANGED";
2225 htmlStr += "<div id=\"example-content-1\" style=\"display: none\">" +
2228 "<div id=\"example-content-2\" style=\"display: none\">" +
2229 curr_formatted_xml +
2232 //var htmlStr='<object type="text/html" data="display-diff.html" ></object>';
2234 $("#diff-browser-dialog").dialog({
2246 //$( this ).dialog( "close" );
2247 $("#diff-browser-dialog").dialog("close");
2252 $('#diff-browser-dialog').keypress(function(e) {
2253 if (e.keyCode == $.ui.keyCode.ENTER) {
2254 $('#diff-browser-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
2259 }).dialog('open').html(htmlStr);
2263 showNodePalette: function(s) {
2266 $("#main-container").addClass("palette-bar-closed");
2267 //RED.menu.setSelected("btn-node-panel",true);
2269 $("#main-container").removeClass("palette-bar-closed");
2271 //console.log("showNodePalette:" + showNodePalette);
2273 //TODO: should these move to an import/export module?
2274 showImportNodesDialog: showImportNodesDialog,
2275 showExportNodesDialog: showExportNodesDialog,
2276 showExportNodesLibraryDialog: showExportNodesLibraryDialog