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 space_width = 7500,
22 lineCurveScale = 0.75,
27 var touchLongPressTimeout = 1000,
28 startTouchDistance = 0,
29 startTouchCenter = [],
34 var activeWorkspace = 0;
35 var workspaceScrollPositions = {};
37 var selected_link = null,
38 mousedown_link = null,
39 mousedown_node = null,
40 mousedown_port_type = null,
41 mousedown_port_index = 0,
44 mouse_position = null,
51 showNodePalette = true,
53 dblClickPrimed = null,
59 var status_colours = {
67 var outer = d3.select("#chart")
69 .attr("width", space_width)
70 .attr("height", space_height)
71 .attr("pointer-events", "all")
72 .style("cursor","crosshair");
76 .on("dblclick.zoom", null)
78 .on("mousemove", canvasMouseMove)
79 .on("mousedown", canvasMouseDown)
80 .on("mouseup", canvasMouseUp)
81 .on("touchend", function() {
82 clearTimeout(touchStartTime);
83 touchStartTime = null;
84 if (RED.touch.radialMenu.active()) {
88 outer_background.attr("fill","#fff");
90 canvasMouseUp.call(this);
92 .on("touchcancel", canvasMouseUp)
93 .on("touchstart", function() {
95 if (d3.event.touches.length>1) {
96 clearTimeout(touchStartTime);
97 touchStartTime = null;
98 d3.event.preventDefault();
99 touch0 = d3.event.touches.item(0);
100 var touch1 = d3.event.touches.item(1);
101 var a = touch0['pageY']-touch1['pageY'];
102 var b = touch0['pageX']-touch1['pageX'];
104 var offset = $("#chart").offset();
105 var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
107 (touch1['pageX']+(b/2)-offset.left+scrollPos[0])/scaleFactor,
108 (touch1['pageY']+(a/2)-offset.top+scrollPos[1])/scaleFactor
111 touch1['pageX']+(b/2),
112 touch1['pageY']+(a/2)
114 startTouchDistance = Math.sqrt((a*a)+(b*b));
116 var obj = d3.select(document.body);
117 touch0 = d3.event.touches.item(0);
118 var pos = [touch0.pageX,touch0.pageY];
119 startTouchCenter = [touch0.pageX,touch0.pageY];
120 startTouchDistance = 0;
121 var point = d3.touches(this)[0];
122 touchStartTime = setTimeout(function() {
123 touchStartTime = null;
124 showTouchMenu(obj,pos);
125 //lasso = vis.append('rect')
126 // .attr("ox",point[0])
127 // .attr("oy",point[1])
130 // .attr("x",point[0])
131 // .attr("y",point[1])
134 // .attr("class","lasso");
135 //outer_background.attr("fill","#e3e3f3");
136 },touchLongPressTimeout);
139 .on("touchmove", function(){
140 if (RED.touch.radialMenu.active()) {
141 d3.event.preventDefault();
145 if (d3.event.touches.length<2) {
146 if (touchStartTime) {
147 touch0 = d3.event.touches.item(0);
148 var dx = (touch0.pageX-startTouchCenter[0]);
149 var dy = (touch0.pageY-startTouchCenter[1]);
150 var d = Math.abs(dx*dx+dy*dy);
152 clearTimeout(touchStartTime);
153 touchStartTime = null;
156 d3.event.preventDefault();
158 canvasMouseMove.call(this);
160 touch0 = d3.event.touches.item(0);
161 var touch1 = d3.event.touches.item(1);
162 var a = touch0['pageY']-touch1['pageY'];
163 var b = touch0['pageX']-touch1['pageX'];
164 var offset = $("#chart").offset();
165 var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
166 var moveTouchDistance = Math.sqrt((a*a)+(b*b));
168 touch1['pageX']+(b/2),
169 touch1['pageY']+(a/2)
172 if (!isNaN(moveTouchDistance)) {
173 oldScaleFactor = scaleFactor;
174 scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000)));
176 var deltaTouchCenter = [ // Try to pan whilst zooming - not 100%
177 startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]),
178 startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1])
181 startTouchDistance = moveTouchDistance;
182 moveTouchCenter = touchCenter;
184 $("#chart").scrollLeft(scrollPos[0]+deltaTouchCenter[0]);
185 $("#chart").scrollTop(scrollPos[1]+deltaTouchCenter[1]);
191 var outer_background = vis.append('svg:rect')
192 .attr('width', space_width)
193 .attr('height', space_height)
194 .attr('fill','#fff');
196 //var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]);
197 //var grid = vis.append('g');
199 //grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter()
203 // "class":"horizontal",
206 // "y1" : function(d){ return gridScale(d);},
207 // "y2" : function(d){ return gridScale(d);},
209 // "shape-rendering" : "crispEdges",
210 // "stroke" : "#eee",
211 // "stroke-width" : "1px"
213 //grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter()
217 // "class":"vertical",
220 // "x1" : function(d){ return gridScale(d);},
221 // "x2" : function(d){ return gridScale(d);},
223 // "shape-rendering" : "crispEdges",
224 // "stroke" : "#eee",
225 // "stroke-width" : "1px"
229 var drag_line = vis.append("svg:path").attr("class", "drag_line");
231 var workspace_tabs = RED.tabs.create({
232 id: "workspace-tabs",
233 onchange: function(tab) {
234 if (tab.type == "subflow") {
235 $("#workspace-toolbar").show();
237 $("#workspace-toolbar").hide();
239 var chart = $("#chart");
240 if (activeWorkspace !== 0) {
241 workspaceScrollPositions[activeWorkspace] = {
242 left:chart.scrollLeft(),
243 top:chart.scrollTop()
246 var scrollStartLeft = chart.scrollLeft();
247 var scrollStartTop = chart.scrollTop();
249 activeWorkspace = tab.id;
250 if (workspaceScrollPositions[activeWorkspace]) {
251 chart.scrollLeft(workspaceScrollPositions[activeWorkspace].left);
252 chart.scrollTop(workspaceScrollPositions[activeWorkspace].top);
257 var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft;
258 var scrollDeltaTop = chart.scrollTop() - scrollStartTop;
259 if (mouse_position != null) {
260 mouse_position[0] += scrollDeltaLeft;
261 mouse_position[1] += scrollDeltaTop;
265 RED.nodes.eachNode(function(n) {
270 ondblclick: function(tab) {
271 showRenameWorkspaceDialog(tab.id);
273 onadd: function(tab) {
274 RED.menu.addItem("btn-workspace-menu",{
275 id:"btn-workspace-menu-"+tab.id.replace(".","-"),
277 onselect:function() {
278 workspace_tabs.activateTab(tab.id);
281 RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
283 onremove: function(tab) {
284 RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
285 RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-"));
289 var workspaceIndex = 0;
291 function addWorkspace() {
292 var tabId = RED.nodes.id();
295 } while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
297 var ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
298 RED.nodes.addWorkspace(ws);
299 workspace_tabs.addTab(ws);
300 workspace_tabs.activateTab(tabId);
301 RED.history.push({t:'add',workspaces:[ws],dirty:dirty});
302 RED.view.dirty(true);
305 $('#btn-workspace-add-tab').on("click",addWorkspace);
306 $('#btn-workspace-add').on("click",addWorkspace);
307 $('#btn-workspace-edit').on("click",function() {
308 showRenameWorkspaceDialog(activeWorkspace);
310 $('#btn-workspace-delete').on("click",function() {
311 deleteWorkspace(activeWorkspace);
315 function deleteWorkspace(id) {
316 if (workspace_tabs.count() == 1) {
319 var ws = RED.nodes.workspace(id);
320 $( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
321 $( "#node-dialog-delete-workspace-name" ).text(ws.label);
322 $( "#node-dialog-delete-workspace" ).dialog('open');
325 function canvasMouseDown() {
326 if (!mousedown_node && !mousedown_link) {
327 selected_link = null;
330 if (mouse_mode === 0) {
336 if (!touchStartTime) {
337 var point = d3.mouse(this);
338 lasso = vis.append('rect')
347 .attr("class","lasso");
348 d3.event.preventDefault();
353 function canvasMouseMove() {
354 mouse_position = d3.touches(this)[0]||d3.mouse(this);
356 // Prevent touch scrolling...
357 //if (d3.touches(this)[0]) {
358 // d3.event.preventDefault();
361 // TODO: auto scroll the container
362 //var point = d3.mouse(this);
363 //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; }
364 //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
367 var ox = parseInt(lasso.attr("ox"));
368 var oy = parseInt(lasso.attr("oy"));
369 var x = parseInt(lasso.attr("x"));
370 var y = parseInt(lasso.attr("y"));
373 if (mouse_position[0] < ox) {
374 x = mouse_position[0];
377 w = mouse_position[0]-x;
379 if (mouse_position[1] < oy) {
380 y = mouse_position[1];
383 h = mouse_position[1]-y;
394 if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
399 if (mouse_mode == RED.state.JOINING) {
401 drag_line.attr("class", "drag_line");
402 mousePos = mouse_position;
403 var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1;
404 var sourcePort = mousedown_port_index;
405 var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
407 var sc = (mousedown_port_type === 0)?1:-1;
409 var dy = mousePos[1]-(mousedown_node.y+portY);
410 var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2);
411 var delta = Math.sqrt(dy*dy+dx*dx);
412 var scale = lineCurveScale;
415 if (delta < node_width) {
416 scale = 0.75-0.75*((node_width-delta)/node_width);
419 scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
420 if (Math.abs(dy) < 3*node_height) {
421 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)) ;
426 "M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+
427 " C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+
428 (mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
429 mousePos[0]+" "+mousePos[1]
431 d3.event.preventDefault();
432 } else if (mouse_mode == RED.state.MOVING) {
433 //console.log("node mouse moving");
434 mousePos = mouse_position;
435 var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]);
437 mouse_mode = RED.state.MOVING_ACTIVE;
440 } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
441 //console.log("node mouse moving active or IMPORT_DRAGGING");
442 mousePos = mouse_position;
447 for (var n = 0; n<moving_set.length; n++) {
448 node = moving_set[n];
449 if (d3.event.shiftKey) {
450 node.n.ox = node.n.x;
451 node.n.oy = node.n.y;
453 node.n.x = mousePos[0]+node.dx;
454 node.n.y = mousePos[1]+node.dy;
456 minX = Math.min(node.n.x-node.n.w/2-5,minX);
457 minY = Math.min(node.n.y-node.n.h/2-5,minY);
459 if (minX !== 0 || minY !== 0) {
460 for (i = 0; i<moving_set.length; i++) {
461 node = moving_set[i];
466 if (d3.event.shiftKey && moving_set.length > 0) {
467 var gridOffset = [0,0];
468 node = moving_set[0];
469 gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2);
470 gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20));
471 if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
472 for (i = 0; i<moving_set.length; i++) {
473 node = moving_set[i];
474 node.n.x -= gridOffset[0];
475 node.n.y -= gridOffset[1];
476 if (node.n.x == node.n.ox && node.n.y == node.n.oy) {
486 function canvasMouseUp() {
487 if (mousedown_node && mouse_mode == RED.state.JOINING) {
488 drag_line.attr("class", "drag_line_hidden");
491 var x = parseInt(lasso.attr("x"));
492 var y = parseInt(lasso.attr("y"));
493 var x2 = x+parseInt(lasso.attr("width"));
494 var y2 = y+parseInt(lasso.attr("height"));
495 if (!d3.event.ctrlKey) {
498 RED.nodes.eachNode(function(n) {
499 if (n.z == activeWorkspace && !n.selected) {
500 n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
503 moving_set.push({n:n});
510 } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) {
514 if (mouse_mode == RED.state.MOVING_ACTIVE) {
515 //console.log("node moved active.");
516 //CSS setting view dirty if the node was moved
517 //RED.view.dirty(true);
518 if (moving_set.length > 0) {
520 for (var j=0;j<moving_set.length;j++) {
521 ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy});
523 RED.history.push({t:'move',nodes:ns,dirty:dirty});
526 if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
527 //console.log("node moving or MOVING_ACTIVE.");
528 for (var i=0;i<moving_set.length;i++) {
529 delete moving_set[i].ox;
530 delete moving_set[i].oy;
533 if (mouse_mode == RED.state.IMPORT_DRAGGING) {
534 RED.keyboard.remove(/* ESCAPE */ 27);
538 // clear mouse event vars
542 $('#btn-zoom-out').click(function() {zoomOut();});
543 $('#btn-zoom-zero').click(function() {zoomZero();});
544 $('#btn-zoom-in').click(function() {zoomIn();});
545 $("#chart").on('DOMMouseScroll mousewheel', function (evt) {
547 evt.preventDefault();
548 evt.stopPropagation();
549 var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta;
550 if (move <= 0) { zoomOut(); }
554 $("#chart").droppable({
555 accept:".palette_node",
556 drop: function( event, ui ) {
558 var selected_tool = ui.draggable[0].type;
559 var mousePos = d3.touches(this)[0]||d3.mouse(this);
560 mousePos[1] += this.scrollTop;
561 mousePos[0] += this.scrollLeft;
562 mousePos[1] /= scaleFactor;
563 mousePos[0] /= scaleFactor;
565 var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:activeWorkspace};
567 nn.type = selected_tool;
568 nn._def = RED.nodes.getType(nn.type);
569 nn.outputs = nn._def.outputs;
572 for (var d in nn._def.defaults) {
573 if (nn._def.defaults.hasOwnProperty(d)) {
574 nn[d] = nn._def.defaults[d].value;
579 nn._def.onadd.call(nn);
582 nn.h = Math.max(node_height,(nn.outputs||0) * 15);
583 RED.history.push({t:'add',nodes:[nn.id],dirty:dirty});
585 RED.editor.validateNode(nn);
587 // auto select dropped node - so info shows (if visible)
590 moving_set.push({n:nn});
594 if (nn._def.autoedit) {
601 if (scaleFactor < 2) {
607 if (scaleFactor > 0.3) {
612 function zoomZero() {
617 function selectAll() {
618 RED.nodes.eachNode(function(n) {
619 if (n.z == activeWorkspace) {
623 moving_set.push({n:n});
627 selected_link = null;
632 function clearSelection() {
633 for (var i=0;i<moving_set.length;i++) {
634 var n = moving_set[i];
636 n.n.selected = false;
639 selected_link = null;
642 function updateSelection() {
643 if (moving_set.length === 0) {
644 RED.menu.setDisabled("btn-export-menu",true);
645 RED.menu.setDisabled("btn-export-clipboard",true);
646 RED.menu.setDisabled("btn-export-library",true);
648 RED.menu.setDisabled("btn-export-menu",false);
649 RED.menu.setDisabled("btn-export-clipboard",false);
650 RED.menu.setDisabled("btn-export-library",false);
652 if (moving_set.length === 0 && selected_link == null) {
653 //RED.keyboard.remove(/* backspace */ 8);
654 RED.keyboard.remove(/* delete */ 46);
655 RED.keyboard.remove(/* c */ 67);
656 RED.keyboard.remove(/* x */ 88);
658 //RED.keyboard.add(/* backspace */ 8,function(){deleteSelection();d3.event.preventDefault();});
659 RED.keyboard.add(/* delete */ 46,function(){deleteSelection();d3.event.preventDefault();});
660 RED.keyboard.add(/* c */ 67,{ctrl:true},function(){copySelection();d3.event.preventDefault();});
661 RED.keyboard.add(/* x */ 88,{ctrl:true},function(){copySelection();deleteSelection();d3.event.preventDefault();});
663 if (moving_set.length === 0) {
664 RED.keyboard.remove(/* up */ 38);
665 RED.keyboard.remove(/* down */ 40);
666 RED.keyboard.remove(/* left */ 37);
667 RED.keyboard.remove(/* right*/ 39);
669 RED.keyboard.add(/* up */ 38, function() { if(d3.event.shiftKey){moveSelection( 0,-20)}else{moveSelection( 0,-1);}d3.event.preventDefault();},endKeyboardMove);
670 RED.keyboard.add(/* down */ 40, function() { if(d3.event.shiftKey){moveSelection( 0, 20)}else{moveSelection( 0, 1);}d3.event.preventDefault();},endKeyboardMove);
671 RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove);
672 RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove);
674 if (moving_set.length == 1) {
675 RED.sidebar.info.refresh(moving_set[0].n);
677 RED.sidebar.info.clear();
680 function endKeyboardMove() {
681 //console.log("end keyboard move.");
682 //CSS setting view dirty if the node was moved
683 //RED.view.dirty(true);
685 for (var i=0;i<moving_set.length;i++) {
686 ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy});
687 delete moving_set[i].ox;
688 delete moving_set[i].oy;
690 RED.history.push({t:'move',nodes:ns,dirty:dirty});
692 function moveSelection(dx,dy) {
697 for (var i=0;i<moving_set.length;i++) {
698 node = moving_set[i];
699 if (node.ox == null && node.oy == null) {
706 minX = Math.min(node.n.x-node.n.w/2-5,minX);
707 minY = Math.min(node.n.y-node.n.h/2-5,minY);
710 if (minX !== 0 || minY !== 0) {
711 for (var n = 0; n<moving_set.length; n++) {
712 node = moving_set[n];
720 function deleteSelection() {
721 var removedNodes = [];
722 var removedLinks = [];
723 var startDirty = dirty;
724 if (moving_set.length > 0) {
725 for (var i=0;i<moving_set.length;i++) {
726 var node = moving_set[i].n;
727 node.selected = false;
731 var rmlinks = RED.nodes.remove(node.id);
732 removedNodes.push(node);
733 removedLinks = removedLinks.concat(rmlinks);
739 RED.nodes.removeLink(selected_link);
740 removedLinks.push(selected_link);
743 RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,dirty:startDirty});
745 selected_link = null;
750 function copySelection() {
751 if (moving_set.length > 0) {
753 for (var n=0;n<moving_set.length;n++) {
754 var node = moving_set[n].n;
755 nns.push(RED.nodes.convertNode(node));
757 clipboard = JSON.stringify(nns);
758 RED.notify(moving_set.length+" node"+(moving_set.length>1?"s":"")+" copied");
763 function calculateTextWidth(str) {
764 var sp = document.createElement("span");
765 sp.className = "node_label";
766 sp.style.position = "absolute";
767 sp.style.top = "-1000px";
768 sp.innerHTML = (str||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
769 document.body.appendChild(sp);
770 var w = sp.offsetWidth;
771 document.body.removeChild(sp);
775 function resetMouseVars() {
776 mousedown_node = null;
778 mousedown_link = null;
780 mousedown_port_type = 0;
783 function portMouseDown(d,portType,portIndex) {
785 //vis.call(d3.behavior.zoom().on("zoom"), null);
787 selected_link = null;
788 mouse_mode = RED.state.JOINING;
789 mousedown_port_type = portType;
790 mousedown_port_index = portIndex || 0;
791 document.body.style.cursor = "crosshair";
792 d3.event.preventDefault();
795 function portMouseUp(d,portType,portIndex) {
796 document.body.style.cursor = "";
797 if (mouse_mode == RED.state.JOINING && mousedown_node) {
798 if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
799 RED.nodes.eachNode(function(n) {
800 if (n.z == activeWorkspace) {
803 if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
804 n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
806 portType = mouseup_node._def.inputs>0?1:0;
814 if (portType == mousedown_port_type || mouseup_node === mousedown_node) {
815 drag_line.attr("class", "drag_line_hidden");
819 var src,dst,src_port;
820 if (mousedown_port_type === 0) {
821 src = mousedown_node;
822 src_port = mousedown_port_index;
824 } else if (mousedown_port_type == 1) {
826 dst = mousedown_node;
827 src_port = portIndex;
830 var existingLink = false;
831 RED.nodes.eachLink(function(d) {
832 existingLink = existingLink || (d.source === src && d.target === dst && d.sourcePort == src_port);
835 var link = {source: src, sourcePort:src_port, target: dst};
836 RED.nodes.addLink(link);
837 RED.history.push({t:'add',links:[link],dirty:dirty});
840 selected_link = null;
845 function nodeMouseUp(d) {
846 if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < 750) {
849 d3.event.stopPropagation();
852 portMouseUp(d, d._def.inputs > 0 ? 1 : 0, 0);
855 function nodeMouseDown(d) {
856 //var touch0 = d3.event;
857 //var pos = [touch0.pageX,touch0.pageY];
858 //RED.touch.radialMenu.show(d3.select(this),pos);
859 if (mouse_mode == RED.state.IMPORT_DRAGGING) {
860 RED.keyboard.remove(/* ESCAPE */ 27);
865 d3.event.stopPropagation();
869 var now = Date.now();
870 clickElapsed = now-clickTime;
873 dblClickPrimed = (lastClickNode == mousedown_node);
874 lastClickNode = mousedown_node;
878 if (d.selected && d3.event.ctrlKey) {
880 for (i=0;i<moving_set.length;i+=1) {
881 if (moving_set[i].n === d) {
882 moving_set.splice(i,1);
887 if (d3.event.shiftKey) {
889 var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
890 for (var n=0;n<cnodes.length;n++) {
891 cnodes[n].selected = true;
892 cnodes[n].dirty = true;
893 moving_set.push({n:cnodes[n]});
895 } else if (!d.selected) {
896 if (!d3.event.ctrlKey) {
899 mousedown_node.selected = true;
900 moving_set.push({n:mousedown_node});
902 selected_link = null;
903 if (d3.event.button != 2) {
904 mouse_mode = RED.state.MOVING;
905 var mouse = d3.touches(this)[0]||d3.mouse(this);
906 mouse[0] += d.x-d.w/2;
907 mouse[1] += d.y-d.h/2;
908 for (i=0;i<moving_set.length;i++) {
909 moving_set[i].ox = moving_set[i].n.x;
910 moving_set[i].oy = moving_set[i].n.y;
911 moving_set[i].dx = moving_set[i].n.x-mouse[0];
912 moving_set[i].dy = moving_set[i].n.y-mouse[1];
914 mouse_offset = d3.mouse(document.body);
915 if (isNaN(mouse_offset[0])) {
916 mouse_offset = d3.touches(document.body)[0];
923 d3.event.stopPropagation();
926 function nodeButtonClicked(d) {
927 if (d._def.button.toggle) {
928 d[d._def.button.toggle] = !d[d._def.button.toggle];
931 if (d._def.button.onclick) {
932 d._def.button.onclick.call(d);
937 d3.event.preventDefault();
940 function showTouchMenu(obj,pos) {
941 var mdn = mousedown_node;
943 options.push({name:"delete",disabled:(moving_set.length===0),onselect:function() {deleteSelection();}});
944 options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}});
945 options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}});
946 options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}});
947 options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}});
948 options.push({name:"select",onselect:function() {selectAll();}});
949 options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
951 RED.touch.radialMenu.show(obj,pos,options);
955 vis.attr("transform","scale("+scaleFactor+")");
956 outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
958 if (mouse_mode != RED.state.JOINING) {
959 // Don't bother redrawing nodes if we're drawing links
961 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
962 node.exit().remove();
964 var nodeEnter = node.enter().insert("svg:g").attr("class", "node nodegroup");
965 nodeEnter.each(function(d,i) {
966 var node = d3.select(this);
967 node.attr("id",d.id);
968 var l = d._def.label;
969 l = (typeof l === "function" ? l.call(d) : l)||"";
970 d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) );
971 d.h = Math.max(node_height,(d.outputs||0) * 15);
974 var badge = node.append("svg:g").attr("class","node_badge_group");
975 var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15);
976 badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr('text-anchor','end').text(d._def.badge());
977 if (d._def.onbadgeclick) {
978 badgeRect.attr("cursor","pointer")
979 .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();});
984 var nodeButtonGroup = node.append('svg:g')
985 .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; })
986 .attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); });
987 nodeButtonGroup.append('rect')
991 .attr("height",node_height-4)
992 .attr("fill","#eee");//function(d) { return d._def.color;})
993 nodeButtonGroup.append('rect')
994 .attr("x",function(d) { return d._def.align == "right"? 10:5})
999 .attr("height",node_height-12)
1000 .attr("fill",function(d) { return d._def.color;})
1001 .attr("cursor","pointer")
1002 .on("mousedown",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}})
1003 .on("mouseup",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}})
1004 .on("mouseover",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);}})
1005 .on("mouseout",function(d) {if (!lasso) {
1007 if (d._def.button.toggle) {
1008 op = d[d._def.button.toggle]?1:0.2;
1010 d3.select(this).attr("fill-opacity",op);
1012 .on("click",nodeButtonClicked)
1013 .on("touchstart",nodeButtonClicked)
1016 var mainRect = node.append("rect")
1017 .attr("class", "node")
1018 .classed("node_unknown",function(d) { return d.type == "unknown"; })
1021 .attr("fill",function(d) { return d._def.color;})
1022 .on("mouseup",nodeMouseUp)
1023 .on("mousedown",nodeMouseDown)
1024 .on("touchstart",function(d) {
1025 var obj = d3.select(this);
1026 var touch0 = d3.event.touches.item(0);
1027 var pos = [touch0.pageX,touch0.pageY];
1028 startTouchCenter = [touch0.pageX,touch0.pageY];
1029 startTouchDistance = 0;
1030 touchStartTime = setTimeout(function() {
1031 showTouchMenu(obj,pos);
1032 },touchLongPressTimeout);
1033 nodeMouseDown.call(this,d)
1035 .on("touchend", function(d) {
1036 clearTimeout(touchStartTime);
1037 touchStartTime = null;
1038 if (RED.touch.radialMenu.active()) {
1039 d3.event.stopPropagation();
1042 nodeMouseUp.call(this,d);
1044 .on("mouseover",function(d) {
1045 if (mouse_mode === 0) {
1046 var node = d3.select(this);
1047 node.classed("node_hovered",true);
1050 .on("mouseout",function(d) {
1051 var node = d3.select(this);
1052 node.classed("node_hovered",false);
1055 //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");
1056 //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");
1060 var icon_group = node.append("g")
1061 .attr("class","node_icon_group")
1062 .attr("x",0).attr("y",0);
1064 var icon_shade = icon_group.append("rect")
1065 .attr("x",0).attr("y",0)
1066 .attr("class","node_icon_shade")
1068 .attr("stroke","none")
1069 .attr("fill","#000")
1070 .attr("fill-opacity","0.05")
1071 .attr("height",function(d){return Math.min(50,d.h-4);});
1073 var icon = icon_group.append("image")
1074 .attr("xlink:href","icons/"+d._def.icon)
1075 .attr("class","node_icon")
1078 .attr("height","30");
1080 var icon_shade_border = icon_group.append("path")
1081 .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)})
1082 .attr("class","node_icon_shade_border")
1083 .attr("stroke-opacity","0.1")
1084 .attr("stroke","#000")
1085 .attr("stroke-width","2");
1087 if ("right" == d._def.align) {
1088 icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align);
1089 icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)})
1090 //icon.attr('class','node_icon node_icon_'+d._def.align);
1091 //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
1092 //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align);
1095 //if (d._def.inputs > 0 && d._def.align == null) {
1096 // icon_shade.attr("width",35);
1097 // icon.attr("transform","translate(5,0)");
1098 // icon_shade_border.attr("transform","translate(5,0)");
1100 //if (d._def.outputs > 0 && "right" == d._def.align) {
1101 // icon_shade.attr("width",35); //icon.attr("x",5);
1104 var img = new Image();
1105 img.src = "icons/"+d._def.icon;
1106 img.onload = function() {
1107 icon.attr("width",Math.min(img.width,30));
1108 icon.attr("height",Math.min(img.height,30));
1109 icon.attr("x",15-Math.min(img.width,30)/2);
1110 //if ("right" == d._def.align) {
1111 // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);});
1112 // icon_shade.attr("x",function(d){return d.w-30});
1113 // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);});
1117 //icon.style("pointer-events","none");
1118 icon_group.style("pointer-events","none");
1120 var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start');
1122 text.attr('class','node_label node_label_'+d._def.align);
1123 text.attr('text-anchor','end');
1126 var status = node.append("svg:g").attr("class","node_status_group").style("display","none");
1128 var statusRect = status.append("rect").attr("class","node_status")
1129 .attr("x",6).attr("y",1).attr("width",9).attr("height",9)
1130 .attr("rx",2).attr("ry",2).attr("stroke-width","3");
1132 var statusLabel = status.append("svg:text")
1133 .attr("class","node_status_label")
1134 .attr('x',20).attr('y',9)
1140 'text-anchor':'start'
1143 var dgNumber = node.append("svg:g").attr("class","node_dgnumber_group").style("display","none");
1145 /*var dgNumberRect = dgNumber.append("rect").attr("class","node_dgnumber")
1146 .attr("x",6).attr("y",-49).attr("width",9).attr("height",9)
1147 .attr("rx",2).attr("ry",2).attr("stroke-width","3");
1150 var dgNumberLabel = dgNumber.append("svg:text")
1151 .attr("class","node_dgnumber_label")
1152 .attr('x',1).attr('y',-43)
1155 /*'fill': '#2E4F83',*/
1159 'text-anchor':'start'
1162 var dgNumberTitle = dgNumber.append("title")
1163 .attr("class","node_dgnumber_title");
1165 //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5});
1167 if (d._def.inputs > 0) {
1169 node.append("rect").attr("class","port port_input").attr("rx",3).attr("ry",3).attr("x",-5).attr("width",10).attr("height",10)
1170 .on("mousedown",function(d){portMouseDown(d,1,0);})
1171 .on("touchstart",function(d){portMouseDown(d,1,0);})
1172 .on("mouseup",function(d){portMouseUp(d,1,0);} )
1173 .on("touchend",function(d){portMouseUp(d,1,0);} )
1174 .on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
1175 .on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
1178 //node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z");
1179 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);
1180 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);
1183 node.each(function(d,i) {
1185 //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
1187 var l = d._def.label;
1188 l = (typeof l === "function" ? l.call(d) : l)||"";
1189 d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) );
1190 d.h = Math.max(node_height,(d.outputs||0) * 15);
1192 var thisNode = d3.select(this);
1193 //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
1194 thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
1195 thisNode.selectAll(".node")
1196 .attr("width",function(d){return d.w})
1197 .attr("height",function(d){return d.h})
1198 .classed("node_selected",function(d) { return d.selected; })
1199 .classed("node_highlighted",function(d) { return d.highlighted; })
1201 //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
1202 //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
1204 thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"});
1205 thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38});
1206 //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
1207 //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
1208 //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
1211 var numOutputs = d.outputs;
1212 var y = (d.h/2)-((numOutputs-1)/2)*13;
1213 d.ports = d.ports || d3.range(numOutputs);
1214 d._ports = thisNode.selectAll(".port_output").data(d.ports);
1215 d._ports.enter().append("rect").attr("class","port port_output").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
1216 .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
1217 .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
1218 .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
1219 .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
1220 .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
1221 .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
1222 d._ports.exit().remove();
1224 numOutputs = d.outputs || 1;
1225 y = (d.h/2)-((numOutputs-1)/2)*13;
1227 d._ports.each(function(d,i) {
1228 var port = d3.select(this);
1229 port.attr("y",(y+13*i)-5).attr("x",x);
1232 thisNode.selectAll('text.node_label').text(function(d,i){
1234 if (typeof d._def.label == "function") {
1235 return d._def.label.call(d);
1237 return d._def.label;
1242 .attr('y', function(d){return (d.h/2)-1;})
1243 .attr('class',function(d){
1244 return 'node_label'+
1245 (d._def.align?' node_label_'+d._def.align:'')+
1246 (d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
1248 thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;});
1250 thisNode.selectAll(".node_changed")
1251 .attr("x",function(d){return d.w-10})
1252 .classed("hidden",function(d) { return !d.changed; });
1254 thisNode.selectAll(".node_error")
1255 .attr("x",function(d){return d.w-10-(d.changed?13:0)})
1256 .classed("hidden",function(d) { return d.valid; });
1258 thisNode.selectAll(".port_input").each(function(d,i) {
1259 var port = d3.select(this);
1260 port.attr("y",function(d){return (d.h/2)-5;})
1263 thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
1264 thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
1265 thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
1268 thisNode.selectAll('.node_right_button').attr("transform",function(d){
1270 if (d._def.button.toggle && !d[d._def.button.toggle]) {
1273 return "translate("+x+",2)";
1275 thisNode.selectAll('.node_right_button rect').attr("fill-opacity",function(d){
1276 if (d._def.button.toggle) {
1277 return d[d._def.button.toggle]?1:0.2;
1282 //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
1283 // 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)
1286 thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
1287 thisNode.selectAll('text.node_badge_label').text(function(d,i) {
1289 if (typeof d._def.badge == "function") {
1290 return d._def.badge.call(d);
1292 return d._def.badge;
1297 if (!showStatus || !d.status) {
1298 thisNode.selectAll('.node_status_group').style("display","none");
1300 thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")");
1301 var fill = status_colours[d.status.fill]; // Only allow our colours for now
1302 if (d.status.shape == null && fill == null) {
1303 thisNode.selectAll('.node_status').style("display","none");
1306 if (d.status.shape == null || d.status.shape == "dot") {
1312 } else if (d.status.shape == "ring" ){
1319 thisNode.selectAll('.node_status').style(style);
1321 if (d.status.text) {
1322 thisNode.selectAll('.node_status_label').text(d.status.text);
1324 thisNode.selectAll('.node_status_label').text("");
1327 //console.dir("d value");
1329 if (showNumbers && d.dgnumber != null && d.dgnumber != undefined && d.dgnumber.length >0) {
1330 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")");
1331 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1332 var dgnumberList = d.dgnumber;
1334 if(dgnumberList != null && dgnumberList.length >=1){
1335 dgnum = dgnumberList[0];
1336 thisNode.select('.node_dgnumber_label').text(dgnum);
1337 //console.log(dgnumberList);
1338 thisNode.select('.node_dgnumber_title').text(dgnumberList);
1341 if(d.dgnumber.length > 1){
1342 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h-15)+")");
1343 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1345 thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")");
1346 thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString());
1350 //console.log("fhfjhfjh ");
1351 thisNode.select('.node_dgnumber').style("display","none");
1352 thisNode.select('.node_dgnumber_label').text("");
1353 thisNode.select('.node_dgnumber_title').text("");
1361 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;});
1363 var linkEnter = link.enter().insert("g",".node").attr("class","link");
1365 linkEnter.each(function(d,i) {
1366 var l = d3.select(this);
1367 l.append("svg:path").attr("class","link_background link_path")
1368 .on("mousedown",function(d) {
1371 selected_link = mousedown_link;
1374 d3.event.stopPropagation();
1376 .on("touchstart",function(d) {
1379 selected_link = mousedown_link;
1382 d3.event.stopPropagation();
1384 l.append("svg:path").attr("class","link_outline link_path");
1385 l.append("svg:path").attr("class","link_line link_path");
1388 link.exit().remove();
1390 var links = vis.selectAll(".link_path")
1391 links.attr("d",function(d){
1392 var numOutputs = d.source.outputs || 1;
1393 var sourcePort = d.sourcePort || 0;
1394 var y = -((numOutputs-1)/2)*13 +13*sourcePort;
1396 var dy = d.target.y-(d.source.y+y);
1397 var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
1398 var delta = Math.sqrt(dy*dy+dx*dx);
1399 var scale = lineCurveScale;
1401 if (delta < node_width) {
1402 scale = 0.75-0.75*((node_width-delta)/node_width);
1406 scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
1407 if (Math.abs(dy) < 3*node_height) {
1408 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)) ;
1412 d.x1 = d.source.x+d.source.w/2;
1413 d.y1 = d.source.y+y;
1414 d.x2 = d.target.x-d.target.w/2;
1417 return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+
1418 " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+
1419 (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+
1420 (d.target.x-d.target.w/2)+" "+d.target.y;
1423 link.classed("link_selected", function(d) { return d === selected_link || d.selected; });
1424 link.classed("link_unknown",function(d) { return d.target.type == "unknown" || d.source.type == "unknown"});
1427 d3.event.preventDefault();
1431 RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();});
1432 RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
1433 RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
1434 RED.keyboard.add(/* - */ 189,{ctrl:true},function(){zoomOut();d3.event.preventDefault();});
1435 RED.keyboard.add(/* 0 */ 48,{ctrl:true},function(){zoomZero();d3.event.preventDefault();});
1436 RED.keyboard.add(/* v */ 86,{ctrl:true},function(){importNodes(clipboard);d3.event.preventDefault();});
1437 RED.keyboard.add(/* e */ 69,{ctrl:true},function(){showExportNodesDialog();d3.event.preventDefault();});
1438 RED.keyboard.add(/* i */ 73,{ctrl:true},function(){showImportNodesDialog();d3.event.preventDefault();});
1439 RED.keyboard.add(/* B */ 66,{ctrl:true},function(){RED.view.showDgNumberDialog();d3.event.preventDefault();});
1440 RED.keyboard.add(/* [ */ 219,{ctrl:true},function(){RED.view.showSearchTextDialog();d3.event.preventDefault();});
1441 RED.keyboard.add(/* O */ 79,{ctrl:true},function(){RED.view.showRequestTemplateDialog();d3.event.preventDefault();});
1444 // TODO: 'dirty' should be a property of RED.nodes - with an event callback for ui hooks
1445 function setDirty(d) {
1448 $("#btn-deploy").removeClass("disabled");
1450 $("#btn-deploy").addClass("disabled");
1455 * Imports a new collection of nodes from a JSON String.
1456 * - all get new IDs assigned
1458 * - attached to mouse for placing - 'IMPORT_DRAGGING'
1460 function importNodes(newNodesStr,touchImport) {
1462 var result = RED.nodes.import(newNodesStr,true);
1464 var new_nodes = result[0];
1465 var new_links = result[1];
1466 var new_workspaces = result[2];
1468 var new_ms = new_nodes.filter(function(n) { return n.z == activeWorkspace }).map(function(n) { return {n:n};});
1469 var new_node_ids = new_nodes.map(function(n){ return n.id; });
1471 // TODO: pick a more sensible root node
1472 if (new_ms.length > 0) {
1473 var root_node = new_ms[0].n;
1474 var dx = root_node.x;
1475 var dy = root_node.y;
1477 if (mouse_position == null) {
1478 mouse_position = [0,0];
1486 for (i=0;i<new_ms.length;i++) {
1488 node.n.selected = true;
1489 node.n.changed = true;
1490 node.n.x -= dx - mouse_position[0];
1491 node.n.y -= dy - mouse_position[1];
1492 node.dx = node.n.x - mouse_position[0];
1493 node.dy = node.n.y - mouse_position[1];
1494 minX = Math.min(node.n.x-node_width/2-5,minX);
1495 minY = Math.min(node.n.y-node_height/2-5,minY);
1497 for (i=0;i<new_ms.length;i++) {
1505 mouse_mode = RED.state.IMPORT_DRAGGING;
1508 RED.keyboard.add(/* ESCAPE */ 27,function(){
1509 RED.keyboard.remove(/* ESCAPE */ 27);
1515 moving_set = new_ms;
1518 RED.history.push({t:'add',nodes:new_node_ids,links:new_links,workspaces:new_workspaces,dirty:RED.view.dirty()});
1524 console.log(error.stack);
1525 RED.notify("<strong>Error</strong>: "+error,"error");
1529 function showExportNodesDialog() {
1530 mouse_mode = RED.state.EXPORT;
1531 var nns = RED.nodes.createExportableNodeSet(moving_set);
1532 $("#dialog-form").html($("script[data-template-name='export-clipboard-dialog']").html());
1533 $("#node-input-export").val(JSON.stringify(nns));
1534 $("#node-input-export").focus(function() {
1535 var textarea = $(this);
1537 textarea.mouseup(function() {
1538 textarea.unbind("mouseup");
1542 $( "#dialog" ).dialog("option","title","Export nodes to clipboard").dialog( "open" );
1543 $("#node-input-export").focus();
1546 function showExportNodesLibraryDialog() {
1547 mouse_mode = RED.state.EXPORT;
1548 var nns = RED.nodes.createExportableNodeSet(moving_set);
1549 $("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
1550 $("#node-input-filename").attr('nodes',JSON.stringify(nns));
1551 $( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" );
1554 function showImportNodesDialog() {
1555 mouse_mode = RED.state.IMPORT;
1556 $("#dialog-form").html($("script[data-template-name='import-dialog']").html());
1557 $("#node-input-import").val("");
1558 $( "#dialog" ).dialog("option","title","Import nodes").dialog( "open" );
1561 function showRenameWorkspaceDialog(id) {
1562 var ws = RED.nodes.workspace(id);
1563 $( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
1565 if (workspace_tabs.count() == 1) {
1566 $( "#node-dialog-rename-workspace").next().find(".leftButton")
1567 .prop('disabled',true)
1568 .addClass("ui-state-disabled");
1570 $( "#node-dialog-rename-workspace").next().find(".leftButton")
1571 .prop('disabled',false)
1572 .removeClass("ui-state-disabled");
1575 $( "#node-input-workspace-name" ).val(ws.label);
1576 $( "#node-dialog-rename-workspace" ).dialog("open");
1579 $("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
1580 $( "#node-dialog-rename-workspace" ).dialog({
1584 title: "Rename sheet",
1587 class: 'leftButton',
1590 var workspace = $(this).dialog('option','workspace');
1591 $( this ).dialog( "close" );
1592 deleteWorkspace(workspace.id);
1598 var workspace = $(this).dialog('option','workspace');
1599 var label = $( "#node-input-workspace-name" ).val();
1600 if (workspace.label != label) {
1601 workspace.label = label;
1602 var link = $("#workspace-tabs a[href='#"+workspace.id+"']");
1603 link.attr("title",label);
1605 RED.view.dirty(true);
1607 $( this ).dialog( "close" );
1613 $( this ).dialog( "close" );
1618 RED.keyboard.disable();
1620 close: function(e) {
1621 RED.keyboard.enable();
1624 $( "#node-dialog-delete-workspace" ).dialog({
1628 title: "Confirm delete",
1633 var workspace = $(this).dialog('option','workspace');
1634 RED.view.removeWorkspace(workspace);
1635 var historyEvent = RED.nodes.removeWorkspace(workspace.id);
1636 historyEvent.t = 'delete';
1637 historyEvent.dirty = dirty;
1638 historyEvent.workspaces = [workspace];
1639 RED.history.push(historyEvent);
1640 RED.view.dirty(true);
1641 $( this ).dialog( "close" );
1647 $( this ).dialog( "close" );
1652 RED.keyboard.disable();
1654 close: function(e) {
1655 RED.keyboard.enable();
1660 state:function(state) {
1661 if (state == null) {
1667 addWorkspace: function(ws) {
1668 workspace_tabs.addTab(ws);
1669 workspace_tabs.resize();
1671 removeWorkspace: function(ws) {
1672 workspace_tabs.removeTab(ws.id);
1674 getWorkspace: function() {
1675 return activeWorkspace;
1677 showWorkspace: function(id) {
1678 workspace_tabs.activateTab(id);
1681 dirty: function(d) {
1688 importNodes: importNodes,
1689 resize: function() {
1690 workspace_tabs.resize();
1692 status: function(s) {
1693 validateEachNodeXml();
1695 RED.nodes.eachNode(function(n) { n.dirty = true;});
1696 //TODO: subscribe/unsubscribe here
1699 showYangUploadDialog:function showYangUploadDialog(){
1701 var htmlStr= "<div id='yang-upload-div' style='width:375;height:225'>" +
1702 '<form id="uploadForm" name="uploadForm" enctype="multipart/form-data" action="/api/uploadyang" method="post" >' +
1703 "<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>" +
1704 //'<input id="upload-yang-button-id" style="font-size:1em;font-weight:bold" type="button" value="Upload Yang" name="upload-yang-button">' +
1707 $("#yang-upload-dialog").dialog({
1710 title: "Upload Yang",
1717 text: "Upload Yang",
1719 if( document.getElementById("yang-file-id").files.length == 0 ){
1720 $("#yang-upload-status").html("<span>No files selected.</span>");
1723 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("disable");
1724 //$("#yang-upload-status").empty().text("File is uploading...");
1725 $("#yang-upload-status").html("<span>Processing...Please wait</span><img src='images/page-loading.gif'>");
1727 url: "/api/uploadyang",
1729 data: new FormData(document.forms['uploadForm']),
1733 success: function(data) {
1734 $("#yang-upload-status").html("");
1735 $("#yang-upload-status").text(data.msg);
1736 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable");
1740 reqInputValues = {};
1741 for(var i=0;i<data.sliValuesObj.length;i++){
1742 var moduleName = data.sliValuesObj[i].moduleName;
1743 sliValuesObj[moduleName] = data.sliValuesObj[i][moduleName + '_PROPS'];
1744 rpcValues[moduleName] = data.sliValuesObj[i][ moduleName +'_RPCS'];
1745 for(var k=0;rpcValues[moduleName] != undefined && k<rpcValues[moduleName].length;k++){
1746 var rpcName = rpcValues[moduleName][k];
1747 reqInputValues[moduleName + "_" + rpcName] = data.sliValuesObj[i][rpcName +"-input"];
1751 //close the yang upload dialogog box and open the load dialogbox
1752 $('#yang-upload-dialog').dialog("close");
1753 $("#btn-available-yang-modules").trigger("click");
1755 error:function (xhr, desc, err){
1756 $("#yang-upload-status").html(err);
1757 $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable");
1765 $("#yang-upload-dialog").dialog("close");
1769 }).dialog("open").html(htmlStr);
1772 showDgNumberDialog: function showDgNumberDialog(){
1774 var isLoopDetected = detectLoop();
1775 console.log("isLoopDetected:" + isLoopDetected);
1780 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>";
1781 $("#dgnumber-find-dialog").dialog({
1784 title: "Find Node By DGNumber",
1793 var dgnumVal = $("#dgnumber-val-id").val();
1794 $("#find-dgnumber-status").text("");
1795 if(dgnumVal != undefined && dgnumVal != '' && dgnumVal != ''){
1796 dgnumVal = dgnumVal.trim();
1801 var dgNumberFound = false;
1802 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
1803 node.each(function(d,i) {
1804 var thisNode = d3.select(this);
1805 var dgn = d.dgnumber;
1807 if(dgn != undefined && typeof dgn == 'object'){
1809 for(var k=0;k<dgn.length;k++){
1810 if(dgn[k] == dgnumVal){
1816 //thisNode.select("rect").style({"stroke":"blue","stroke-width" :"3","stroke-dasharray":"5,1"});
1817 //$("#" + d.id).find("rect").attr("class","node-found-selected");
1818 //$("#" + d.id).find("rect").attr("class","node node_selected");
1819 //thisNode.select("rect").attr("class","node node_selected");
1820 thisNode.select("rect").attr("class","node node_found");
1821 document.getElementById( d.id ).scrollIntoView();
1822 $("#dgnumber-find-dialog").dialog("close");
1824 //display the node edit dialogbox
1826 dgNumberFound = true;
1828 //thisNode.select("rect").style({"stroke":"#999","stroke-width" :"2","stroke-dasharray":"none"});
1829 //$("#" + d.id ).find("rect").attr("class","node-found-clear");
1830 thisNode.select("rect").attr("class","node");
1831 //$("#" + d.id ).find("rect").attr("class","node");
1832 //$("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found");
1838 $("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found");
1845 $("#dgnumber-find-dialog").dialog("close");
1850 //Bind the Enter key to Find button
1851 $('#dgnumber-find-dialog').keypress(function(e) {
1852 if (e.keyCode == $.ui.keyCode.ENTER) {
1853 $('#dgnumber-find-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
1858 //set focus on the input box
1859 $("#dgnumber-val-id").focus();
1862 }).dialog("open").html(htmlStr);
1865 showSearchTextDialog: function showSearchTextDialog(){
1867 var isLoopDetected = detectLoop();
1868 console.log("isLoopDetected:" + isLoopDetected);
1873 //console.log("In the showSearchTextDialog.");
1874 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>";
1875 //console.log("setting up search-text-dialog.");
1876 $("#search-text-dialog").dialog({
1879 title: "Search text in DG",
1889 var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id});
1890 var searchText = $("#search-text-val-id").val();
1891 $("#search-text-status").text("");
1892 if(searchText != undefined && searchText != '' && searchText != ''){
1893 searchText = searchText.trim();
1896 node.each(function(d,i) {
1897 var thisNode = d3.select(this);
1898 thisNode.select("rect").attr("class","node");
1903 var foundSearchText = false;
1904 var foundInDgNumArr = [];
1905 //console.log("In search function");
1906 node.each(function(d,i) {
1907 var thisNode = d3.select(this);
1908 var dgn = d.dgnumber;
1912 var ignoreCase = $('#ignore-case-id').prop('checked')
1917 var searchPattern = new RegExp(searchText, options );
1918 if(xml == undefined || xml == null){
1921 if(nName == undefined || nName == null){
1924 //console.log(searchPattern);
1925 var count1 = (xml.match(searchPattern) || []).length;
1926 //console.log(count1);
1927 var count2 = (nName.match(searchPattern) || []).length;
1928 //console.log(count2);
1930 if(count1 >0 || count2 > 0){
1931 thisNode.select("rect").attr("class","node text_found");
1932 var dgn = d.dgnumber;
1935 if(dgn != undefined && typeof dgn == 'object'){
1936 console.log("DGNUMBERS:" + dgn);
1939 if(dgn != undefined ){
1940 foundInDgNumArr.push(dgNumber);
1942 foundInDgNumArr.push(d.type);
1944 foundSearchText=true;
1946 thisNode.select("rect").attr("class","node");
1949 if(!foundSearchText){
1950 $("#search-text-status").text("Search Text :" + searchText + " Not found");
1952 //console.log("closing dialog");
1953 //$("#search-text-dialog").dialog("close");
1954 console.log(foundInDgNumArr);
1955 $("#search-text-status").text("Found in DG numbers :" + foundInDgNumArr);
1962 //console.log("closing dialog");
1963 $("#search-text-dialog").dialog("close");
1968 //console.log("called open.");
1969 //Bind the Enter key to Find button
1970 $('#search-text-dialog').keypress(function(e) {
1971 if (e.keyCode == $.ui.keyCode.ENTER) {
1972 $('#search-text-dialog').parent().find('.ui-dialog-buttonpane button:first').click();
1977 //set focus on the input box
1978 $("#search-text-id").focus();
1980 //console.log("done open call.");
1982 }).dialog('open').html(htmlStr);
1985 showRequestTemplateDialog: function showRequestTemplateDialog(){
1987 var currNodes = RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace })
1988 var moduleName = "";
1990 if(currNodes != null && currNodes.length > 1){
1991 currNodes.forEach(function(n){
1992 if(n.type == 'service-logic'){
1993 moduleName = getAttributeValue(n.xml,"module");
1994 }else if(n.type == 'method'){
1995 rpcName = getAttributeValue(n.xml,"rpc");
1999 console.log("moduleName:" + moduleName);
2000 console.log("rpcName:" + rpcName);
2001 var inputValObj = reqInputValues[moduleName + "_" + rpcName];
2002 var inputValStr = "Not found. Please make sure that the Module is loaded and the rpc has input.";
2003 if(inputValObj != undefined && inputValObj != null){
2004 inputValStr = "{\n\"input\" : " + JSON.stringify(inputValObj,null,4)+ "\n}";
2007 //var htmlStr="<div id='request-template-div' style='width:875px;height:575px'><textarea style='width:875px;height:575px'>" + inputValStr + "</textarea></div>"
2008 //var htmlStr="<div id='request-template-div' style='width:750px;height:550px;font-weight:bold;font-size:1em'><pre>" + inputValStr + "</pre></div>"
2009 var htmlStr="<textarea readonly='1' id='request-template-textarea' style='width:750px;height:550px;font-weight:bold;font-size:1em'>" + inputValStr + "</textarea>"
2010 $("#request-input-dialog").dialog({
2011 dialogClass :"no-close",
2014 title: "Request Template for Module:" + moduleName + " RPC:" + rpcName,
2021 $("#request-input-dialog").dialog("close");
2026 $('#request-input-dialog').css('overflow', 'hidden');
2028 }).dialog("open").html(htmlStr);
2031 showNumbers: function(s) {
2032 console.log("showNumbers:" + s);
2034 RED.nodes.eachNode(function(n) { n.dirty = true;});
2037 showNodePalette: function(s) {
2040 $("#main-container").addClass("palette-bar-closed");
2041 //RED.menu.setSelected("btn-node-panel",true);
2043 $("#main-container").removeClass("palette-bar-closed");
2045 //console.log("showNodePalette:" + showNodePalette);
2048 //TODO: should these move to an import/export module?
2049 showImportNodesDialog: showImportNodesDialog,
2050 showExportNodesDialog: showExportNodesDialog,
2051 showExportNodesLibraryDialog: showExportNodesLibraryDialog