diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index 04f84e5be..165a58d2b 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -622,7 +622,6 @@ RED.editor = (function() { } editing_node.dirty = true; validateNode(editing_node); - RED.view.redraw(true); RED.tray.close(); } } @@ -676,6 +675,7 @@ RED.editor = (function() { RED.sidebar.info.refresh(editing_node); } RED.workspaces.refresh(); + RED.view.redraw(true); editStack.pop(); if (editStack.length === 0) { RED.view.focus(); @@ -1010,7 +1010,7 @@ RED.editor = (function() { validateNode(user); } RED.nodes.dirty(true); - RED.view.redraw(); + RED.view.redraw(true); RED.history.push(historyEvent); RED.tray.close(function() { updateConfigNodeSelect(configProperty,configType,"",prefix); diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index 45b0a926d..a0705f02b 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -41,6 +41,7 @@ RED.view = (function() { var activeSubflow = null; var activeNodes = []; var activeLinks = []; + var activeFlowLinks = []; var selected_link = null, mousedown_link = null, @@ -81,9 +82,9 @@ RED.view = (function() { }); var vis = outer - .append('svg:g') + .append("svg:g") .on("dblclick.zoom", null) - .append('svg:g') + .append("svg:g") .on("mousemove", canvasMouseMove) .on("mousedown", canvasMouseDown) .on("mouseup", canvasMouseUp) @@ -107,18 +108,18 @@ RED.view = (function() { d3.event.preventDefault(); touch0 = d3.event.touches.item(0); var touch1 = d3.event.touches.item(1); - var a = touch0['pageY']-touch1['pageY']; - var b = touch0['pageX']-touch1['pageX']; + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; var offset = $("#chart").offset(); var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; startTouchCenter = [ - (touch1['pageX']+(b/2)-offset.left+scrollPos[0])/scaleFactor, - (touch1['pageY']+(a/2)-offset.top+scrollPos[1])/scaleFactor + (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, + (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor ]; moveTouchCenter = [ - touch1['pageX']+(b/2), - touch1['pageY']+(a/2) + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) ] startTouchDistance = Math.sqrt((a*a)+(b*b)); } else { @@ -131,7 +132,7 @@ RED.view = (function() { touchStartTime = setTimeout(function() { touchStartTime = null; showTouchMenu(obj,pos); - //lasso = vis.append('rect') + //lasso = vis.append("rect") // .attr("ox",point[0]) // .attr("oy",point[1]) // .attr("rx",2) @@ -168,14 +169,14 @@ RED.view = (function() { } else { touch0 = d3.event.touches.item(0); var touch1 = d3.event.touches.item(1); - var a = touch0['pageY']-touch1['pageY']; - var b = touch0['pageX']-touch1['pageX']; + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; var offset = $("#chart").offset(); var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; var moveTouchDistance = Math.sqrt((a*a)+(b*b)); var touchCenter = [ - touch1['pageX']+(b/2), - touch1['pageY']+(a/2) + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) ]; if (!isNaN(moveTouchDistance)) { @@ -197,13 +198,13 @@ RED.view = (function() { } }); - var outer_background = vis.append('svg:rect') - .attr('width', space_width) - .attr('height', space_height) - .attr('fill','#fff'); + var outer_background = vis.append("svg:rect") + .attr("width", space_width) + .attr("height", space_height) + .attr("fill","#fff"); var gridScale = d3.scale.linear().range([0,space_width]).domain([0,space_width]); - var grid = vis.append('g'); + var grid = vis.append("g"); grid.selectAll("line.horizontal").data(gridScale.ticks(space_width/gridSize)).enter() .append("line") @@ -235,7 +236,7 @@ RED.view = (function() { }); grid.style("visibility","hidden"); - var dragGroup = vis.append('g'); + var dragGroup = vis.append("g"); var drag_lines = []; function showDragLines(nodes) { @@ -301,10 +302,10 @@ RED.view = (function() { redraw(); }); - $('#btn-zoom-out').click(function() {zoomOut();}); - $('#btn-zoom-zero').click(function() {zoomZero();}); - $('#btn-zoom-in').click(function() {zoomIn();}); - $("#chart").on('DOMMouseScroll mousewheel', function (evt) { + $("#btn-zoom-out").click(function() {zoomOut();}); + $("#btn-zoom-zero").click(function() {zoomZero();}); + $("#btn-zoom-in").click(function() {zoomIn();}); + $("#chart").on("DOMMouseScroll mousewheel", function (evt) { if ( evt.altKey ) { evt.preventDefault(); evt.stopPropagation(); @@ -368,7 +369,7 @@ RED.view = (function() { nn.h = Math.max(node_height,(nn.outputs||0) * 15); var historyEvent = { - t:'add', + t:"add", nodes:[nn.id], dirty:RED.nodes.dirty() } @@ -398,7 +399,7 @@ RED.view = (function() { nn.x = mousePos[0]; nn.y = mousePos[1]; - var spliceLink = $(ui.helper).data('splice'); + var spliceLink = $(ui.helper).data("splice"); if (spliceLink) { // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp RED.nodes.removeLink(spliceLink); @@ -471,7 +472,7 @@ RED.view = (function() { if (!touchStartTime) { var point = d3.mouse(this); - lasso = vis.append('rect') + lasso = vis.append("rect") .attr("ox",point[0]) .attr("oy",point[1]) .attr("rx",1) @@ -678,12 +679,12 @@ RED.view = (function() { svgRect.height = 1; nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]); } else { - // Firefox doesn't do getIntersectionList and that + // Firefox doesn"t do getIntersectionList and that // makes us sad nodes = RED.view.getLinksAtPoint(mouseX,mouseY); } for (var i=0;i 0) { + activeFlowLinks.push({ + refresh: Math.floor(Math.random()*10000), + node: linkNode, + links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) + }); + } + } + } + + RED.events.emit("view:selection-changed",selection); } @@ -900,7 +965,7 @@ RED.view = (function() { delete moving_set[i].ox; delete moving_set[i].oy; } - RED.history.push({t:'move',nodes:ns,dirty:RED.nodes.dirty()}); + RED.history.push({t:"move",nodes:ns,dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); } } @@ -994,7 +1059,7 @@ RED.view = (function() { RED.nodes.dirty(true); } var historyEvent = { - t:'delete', + t:"delete", nodes:removedNodes, links:removedLinks, subflowOutputs:removedSubflowOutputs, @@ -1019,7 +1084,7 @@ RED.view = (function() { for (var n=0;n 0 || removedLinks.length > 0) { var historyEvent = { - t:'add', + t:"add", links:addedLinks, removedLinks: removedLinks, dirty:RED.nodes.dirty() @@ -1357,8 +1422,8 @@ RED.view = (function() { .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 1)));}) .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); - outGroup.append("svg:text").attr('class','port_label').attr('x',20).attr('y',8).style("font-size","10px").text("output"); - outGroup.append("svg:text").attr('class','port_label port_index').attr('x',20).attr('y',24).text(function(d,i){ return i+1}); + outGroup.append("svg:text").attr("class","port_label").attr("x",20).attr("y",8).style("font-size","10px").text("output"); + outGroup.append("svg:text").attr("class","port_label port_index").attr("x",20).attr("y",24).text(function(d,i){ return i+1}); var subflowInputs = vis.selectAll(".subflowinput").data(activeSubflow.in,function(d,i){ return d.id;}); subflowInputs.exit().remove(); @@ -1399,7 +1464,7 @@ RED.view = (function() { .on("touchend",function(d,i){portMouseUp(d,0,i);} ) .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 0) ));}) .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); - inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input"); + inGroup.append("svg:text").attr("class","port_label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); @@ -1432,10 +1497,12 @@ RED.view = (function() { var nodeEnter = node.enter().insert("svg:g") .attr("class", "node nodegroup") - .classed("node_subflow",function(d) { return activeSubflow != null; }); + .classed("node_subflow",function(d) { return activeSubflow != null; }) + .classed("node_link",function(d) { return d.type === "link in" || d.type === "link out" }); nodeEnter.each(function(d,i) { var node = d3.select(this); + var isLink = d.type === "link in" || d.type === "link out"; node.attr("id",d.id); var l = d._def.label; try { @@ -1444,13 +1511,18 @@ RED.view = (function() { console.log("Definition error: "+d.type+".label",err); l = d.type; } - d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) ); + + if (isLink) { + d.w = node_height; + } else { + d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) ); + } d.h = Math.max(node_height,(d.outputs||0) * 15); if (d._def.badge) { var badge = node.append("svg:g").attr("class","node_badge_group"); var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); - badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr('text-anchor','end').text(d._def.badge()); + badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr("text-anchor","end").text(d._def.badge()); if (d._def.onbadgeclick) { badgeRect.attr("cursor","pointer") .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); @@ -1458,16 +1530,16 @@ RED.view = (function() { } if (d._def.button) { - var nodeButtonGroup = node.append('svg:g') + var nodeButtonGroup = node.append("svg:g") .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) .attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); }); - nodeButtonGroup.append('rect') + nodeButtonGroup.append("rect") .attr("rx",5) .attr("ry",5) .attr("width",32) .attr("height",node_height-4) .attr("fill","#eee");//function(d) { return d._def.color;}) - nodeButtonGroup.append('rect') + nodeButtonGroup.append("rect") .attr("class","node_button_button") .attr("x",function(d) { return d._def.align == "right"? 11:5}) .attr("y",4) @@ -1563,11 +1635,11 @@ RED.view = (function() { .attr("stroke-width","1"); if ("right" == d._def.align) { - icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align); + icon_group.attr("class","node_icon_group node_icon_group_"+d._def.align); icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)}) - //icon.attr('class','node_icon node_icon_'+d._def.align); - //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align); - //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align); + //icon.attr("class","node_icon node_icon_"+d._def.align); + //icon.attr("class","node_icon_shade node_icon_shade_"+d._def.align); + //icon.attr("class","node_icon_shade_border node_icon_shade_border_"+d._def.align); } //if (d.inputs > 0 && d._def.align == null) { @@ -1595,24 +1667,25 @@ RED.view = (function() { //icon.style("pointer-events","none"); icon_group.style("pointer-events","none"); } - var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start'); - if (d._def.align) { - text.attr('class','node_label node_label_'+d._def.align); - if (d._def.align === "right") { - text.attr('text-anchor','end'); + if (!isLink) { + var text = node.append("svg:text").attr("class","node_label").attr("x", 38).attr("dy", ".35em").attr("text-anchor","start"); + if (d._def.align) { + text.attr("class","node_label node_label_"+d._def.align); + if (d._def.align === "right") { + text.attr("text-anchor","end"); + } } + + var status = node.append("svg:g").attr("class","node_status_group").style("display","none"); + + var statusRect = status.append("rect").attr("class","node_status") + .attr("x",6).attr("y",1).attr("width",9).attr("height",9) + .attr("rx",2).attr("ry",2).attr("stroke-width","3"); + + var statusLabel = status.append("svg:text") + .attr("class","node_status_label") + .attr("x",20).attr("y",9); } - - var status = node.append("svg:g").attr("class","node_status_group").style("display","none"); - - var statusRect = status.append("rect").attr("class","node_status") - .attr("x",6).attr("y",1).attr("width",9).attr("height",9) - .attr("rx",2).attr("ry",2).attr("stroke-width","3"); - - var statusLabel = status.append("svg:text") - .attr("class","node_status_label") - .attr('x',20).attr('y',9); - //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5}); //node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z"); @@ -1622,9 +1695,10 @@ RED.view = (function() { node.each(function(d,i) { if (d.dirty) { + var isLink = d.type === "link in" || d.type === "link out"; dirtyNodes[d.id] = d; //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette - if (d.resize) { + if (!isLink && d.resize) { var l = d._def.label; try { l = (typeof l === "function" ? l.call(d) : l)||""; @@ -1652,8 +1726,8 @@ RED.view = (function() { //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w}); //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30}); - thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"}); - thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38}); + thisNode.selectAll(".node_icon_group_right").attr("transform", function(d){return "translate("+(d.w-30)+",0)"}); + thisNode.selectAll(".node_label_right").attr("x", function(d){return d.w-38}); //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -1698,7 +1772,7 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll('text.node_label').text(function(d,i){ + thisNode.selectAll("text.node_label").text(function(d,i){ var l = ""; if (d._def.label) { l = d._def.label; @@ -1711,8 +1785,8 @@ RED.view = (function() { } return l; }) - .attr('y', function(d){return (d.h/2)-1;}) - .attr('class',function(d){ + .attr("y", function(d){return (d.h/2)-1;}) + .attr("class",function(d){ var s = ""; if (d._def.labelStyle) { s = d._def.labelStyle; @@ -1724,8 +1798,8 @@ RED.view = (function() { } s = " "+s; } - return 'node_label'+ - (d._def.align?' node_label_'+d._def.align:'')+s; + return "node_label"+ + (d._def.align?" node_label_"+d._def.align:"")+s; }); if (d._def.icon) { @@ -1774,32 +1848,32 @@ RED.view = (function() { thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;}); thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)}); - thisNode.selectAll('.node_button').attr("opacity",function(d) { + thisNode.selectAll(".node_button").attr("opacity",function(d) { return (activeSubflow||d.changed)?0.4:1 }); - thisNode.selectAll('.node_button_button').attr("cursor",function(d) { + thisNode.selectAll(".node_button_button").attr("cursor",function(d) { return (activeSubflow||d.changed)?"":"pointer"; }); - thisNode.selectAll('.node_right_button').attr("transform",function(d){ + thisNode.selectAll(".node_right_button").attr("transform",function(d){ var x = d.w-6; if (d._def.button.toggle && !d[d._def.button.toggle]) { x = x - 8; } return "translate("+x+",2)"; }); - thisNode.selectAll('.node_right_button rect').attr("fill-opacity",function(d){ + thisNode.selectAll(".node_right_button rect").attr("fill-opacity",function(d){ if (d._def.button.toggle) { return d[d._def.button.toggle]?1:0.2; } return 1; }); - //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) { + //thisNode.selectAll(".node_right_button").attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) { // return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color) //}); - thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); - thisNode.selectAll('text.node_badge_label').text(function(d,i) { + thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + thisNode.selectAll("text.node_badge_label").text(function(d,i) { if (d._def.badge) { if (typeof d._def.badge == "function") { try { @@ -1817,12 +1891,12 @@ RED.view = (function() { } if (!showStatus || !d.status) { - thisNode.selectAll('.node_status_group').style("display","none"); + thisNode.selectAll(".node_status_group").style("display","none"); } else { - thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")"); + thisNode.selectAll(".node_status_group").style("display","inline").attr("transform","translate(3,"+(d.h+3)+")"); var fill = status_colours[d.status.fill]; // Only allow our colours for now if (d.status.shape == null && fill == null) { - thisNode.selectAll('.node_status').style("display","none"); + thisNode.selectAll(".node_status").style("display","none"); } else { var style; if (d.status.shape == null || d.status.shape == "dot") { @@ -1834,22 +1908,23 @@ RED.view = (function() { } else if (d.status.shape == "ring" ){ style = { display: "inline", - fill: '#fff', + fill: "#fff", stroke: fill } } - thisNode.selectAll('.node_status').style(style); + thisNode.selectAll(".node_status").style(style); } if (d.status.text) { - thisNode.selectAll('.node_status_label').text(d.status.text); + thisNode.selectAll(".node_status_label").text(d.status.text); } else { - thisNode.selectAll('.node_status_label').text(""); + thisNode.selectAll(".node_status_label").text(""); } } d.dirty = false; } }); + var link = vis.selectAll(".link").data( activeLinks, function(d) { @@ -1890,7 +1965,8 @@ RED.view = (function() { }) l.append("svg:path").attr("class","link_outline link_path"); l.append("svg:path").attr("class","link_line link_path") - .classed("link_subflow", function(d) { return activeSubflow }); + .classed("link_link", function(d) { return d.link }) + .classed("link_subflow", function(d) { return !d.link && activeSubflow }); }); link.exit().remove(); @@ -1937,6 +2013,123 @@ RED.view = (function() { delete d.added; return d.target.type == "unknown" || d.source.type == "unknown" }); + var offLinks = vis.selectAll(".link_flow_link_g").data( + activeFlowLinks, + function(d) { + return d.node.id+":"+d.refresh + } + ); + + var offLinksEnter = offLinks.enter().insert("g",".node").attr("class","link_flow_link_g"); + offLinksEnter.each(function(d,i) { + var g = d3.select(this); + var s = 1; + var labelAnchor = "start"; + if (d.node.type === "link in") { + s = -1; + labelAnchor = "end"; + } + var stemLength = s*30; + var branchLength = s*20; + var l = g.append("svg:path").attr("class","link_flow_link") + .attr("class","link_link").attr("d","M 0 0 h "+stemLength); + var links = d.links; + var flows = Object.keys(links); + var tabOrder = RED.nodes.getWorkspaceOrder(); + flows.sort(function(A,B) { + return tabOrder.indexOf(A) - tabOrder.indexOf(B); + }); + var linkWidth = 10; + var h = node_height; + var y = -(flows.length-1)*h/2; + var linkGroups = g.selectAll(".link_group").data(flows); + var enterLinkGroups = linkGroups.enter().append("g").attr("class","link_group") + .on('mouseover', function() { d3.select(this).classed('link_group_active',true)}) + .on('mouseout', function() { d3.select(this).classed('link_group_active',false)}) + .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) + .on('mouseup', function(f) { + d3.event.stopPropagation(); + var targets = d.links[f]; + RED.workspaces.show(f); + targets.forEach(function(n) { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + }); + updateSelection(); + redraw(); + }); + enterLinkGroups.each(function(f) { + var linkG = d3.select(this); + linkG.append("svg:path").attr("class","link_flow_link") + .attr("class","link_link") + .attr("d", + "M "+stemLength+" 0 "+ + "C "+(stemLength+(1.7*branchLength))+" "+0+ + " "+(stemLength+(0.1*branchLength))+" "+y+" "+ + (stemLength+branchLength*1.5)+" "+y+" " + ); + linkG.append("svg:path") + .attr("class","link_port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+7))+" "+(y-12)+" "+ + "h "+(-s*linkWidth)+" "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*-3)+" 3 "+ + "v 18 "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*3)+" 3 "+ + "h "+(s*linkWidth) + ); + linkG.append("svg:path") + .attr("class","link_port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y-12)+" "+ + "h "+(s*(linkWidth*3))+" "+ + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y+12)+" "+ + "h "+(s*(linkWidth*3)) + ).style("stroke-dasharray","12 3 8 4 3"); + linkG.append("rect").attr("class","port link_port") + .attr("x",stemLength+branchLength*1.5-4+(s*4)) + .attr("y",y-4) + .attr("rx",2) + .attr("ry",2) + .attr("width",8) + .attr("height",8); + linkG.append("rect") + .attr("x",stemLength+branchLength*1.5-(s===-1?node_width:0)) + .attr("y",y-12) + .attr("width",node_width) + .attr("height",24) + .style("stroke","none") + .style("fill","transparent") + var tab = RED.nodes.workspace(f); + var label; + if (tab) { + label = tab.label || tab.id; + } + linkG.append("svg:text") + .attr("class","port_label") + .attr("x",stemLength+branchLength*1.5+(s*15)) + .attr("y",y+1) + .style("font-size","10px") + .style("text-anchor",labelAnchor) + .text(label); + + y += h; + }); + linkGroups.exit().remove(); + }); + offLinks.exit().remove(); + offLinks = vis.selectAll(".link_flow_link_g"); + offLinks.each(function(d) { + var s = 1; + if (d.node.type === "link in") { + s = -1; + } + var link = d3.select(this); + link.attr("transform", function(d) { return "translate(" + (d.node.x+(s*d.node.w/2)) + "," + (d.node.y) + ")"; }); + + }) + } else { // JOINING - unselect any selected links vis.selectAll(".link_selected").data( @@ -1961,8 +2154,8 @@ RED.view = (function() { /** * Imports a new collection of nodes from a JSON String. * - all get new IDs assigned - * - all 'selected' - * - attached to mouse for placing - 'IMPORT_DRAGGING' + * - all "selected" + * - attached to mouse for placing - "IMPORT_DRAGGING" */ function importNodes(newNodesStr,touchImport) { try { @@ -1977,7 +2170,7 @@ RED.view = (function() { var new_workspaces = result[2]; var new_subflows = result[3]; - var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty('x') && n.hasOwnProperty('y') && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); + var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); var new_node_ids = new_nodes.map(function(n){ return n.id; }); // TODO: pick a more sensible root node @@ -2034,7 +2227,7 @@ RED.view = (function() { } var historyEvent = { - t:'add', + t:"add", nodes:new_node_ids, links:new_links, workspaces:new_workspaces, @@ -2079,6 +2272,7 @@ RED.view = (function() { redraw: function(updateActive) { if (updateActive) { updateActiveNodes(); + updateSelection(); } redraw(); }, diff --git a/editor/sass/colors.scss b/editor/sass/colors.scss index 3d9e2ff44..fe0cea38f 100644 --- a/editor/sass/colors.scss +++ b/editor/sass/colors.scss @@ -25,6 +25,8 @@ $form-input-border-color: #ccc; $node-selected-color: #ff7f0e; $port-selected-color: #ff7f0e; $link-color: #888; +$link-link-color: #ccc; +$link-link-active-color: #ff7f0e; $link-subflow-color: #bbb; $link-unknown-color: #f00; diff --git a/editor/sass/flow.scss b/editor/sass/flow.scss index cf56abee6..42f1a473b 100644 --- a/editor/sass/flow.scss +++ b/editor/sass/flow.scss @@ -207,7 +207,25 @@ fill: none; pointer-events: none; } - +.link_link { + stroke-width: 2; + stroke-dasharray: 10,5; + stroke: $link-link-color; + fill: none; + stroke-dasharray: 15,2; + pointer-events: none; +} +.link_port { + fill: #fff; + stroke: $link-link-color; + stroke-width: 1; +} +.link_group_active .link_port { + stroke: $link-link-active-color; +} +.link_group:hover { + cursor: pointer; +} .link_subflow { stroke: $link-subflow-color; stroke-dasharray: 10,5; diff --git a/editor/sass/tabs.scss b/editor/sass/tabs.scss index c415091e8..b441d1550 100644 --- a/editor/sass/tabs.scss +++ b/editor/sass/tabs.scss @@ -55,7 +55,29 @@ ul.red-ui-tabs li a.red-ui-tab-label { ul.red-ui-tabs li { position: relative; } +.red-ui-tabs-badges { + position: absolute; + top:2px; + right:2px; +} +.red-ui-tab-closeable .red-ui-tabs-badges { + right: 22px; +} +.red-ui-tab.node_changed img.node_changed { + display: inline-block; +} +.red-ui-tab.node_error img.node_error { + display: inline-block; +} + +.red-ui-tabs-badges img { + width: 10px; + height: 10px; + margin-right: 2px; + vertical-align: top; + +} .red-ui-tab-close { background: $tab-background-inactive; opacity: 0.8; @@ -110,7 +132,7 @@ ul.red-ui-tabs li.active a { color: #333; } -ul.red-ui-tabs li img { +.red-ui-tab-icon { margin-left: -8px; margin-right: 3px; margin-top: -2px; @@ -119,6 +141,6 @@ ul.red-ui-tabs li img { height: 20px; vertical-align: middle; } -ul.red-ui-tabs li.active img { +ul.red-ui-tabs li.active .red-ui-tab-icon { opacity: 0.2; } diff --git a/nodes/core/core/60-link.html b/nodes/core/core/60-link.html index 5f934fe6f..4963645ba 100644 --- a/nodes/core/core/60-link.html +++ b/nodes/core/core/60-link.html @@ -16,59 +16,292 @@ + + + + - - - - - diff --git a/nodes/core/core/60-link.js b/nodes/core/core/60-link.js index b98d28570..68bd122f6 100644 --- a/nodes/core/core/60-link.js +++ b/nodes/core/core/60-link.js @@ -20,20 +20,18 @@ module.exports = function(RED) { function LinkInNode(n) { RED.nodes.createNode(this,n); var node = this; - - if (n.event) { - var handler = function(msg) { - msg._event = n.event; - node.receive(msg); - } - RED.events.on("node:"+n.event,handler); - this.on("input", function(msg) { - this.send(msg); - }); - this.on("close",function() { - RED.events.removeListener("node:"+n.event,handler); - }) + var event = "node:"+n.id; + var handler = function(msg) { + msg._event = n.event; + node.receive(msg); } + RED.events.on(event,handler); + this.on("input", function(msg) { + this.send(msg); + }); + this.on("close",function() { + RED.events.removeListener(event,handler); + }); } RED.nodes.registerType("link in",LinkInNode); @@ -41,12 +39,12 @@ module.exports = function(RED) { function LinkOutNode(n) { RED.nodes.createNode(this,n); var node = this; - if (n.event) { - this.on("input", function(msg) { - msg._event = n.event; - RED.events.emit("node:"+n.event,msg) - }); - } + var event = "node:"+n.id; + this.on("input", function(msg) { + msg._event = event; + RED.events.emit(event,msg) + this.send(msg); + }); } RED.nodes.registerType("link out",LinkOutNode); } diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 46e643a96..f95e72555 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -457,7 +457,11 @@ function getFlow(id) { var nodeIds = Object.keys(flow.nodes); if (nodeIds.length > 0) { result.nodes = nodeIds.map(function(nodeId) { - return clone(flow.nodes[nodeId]); + var node = clone(flow.nodes[nodeId]); + if (node.type === 'link out') { + delete node.wires; + } + return node; }) } } diff --git a/red/runtime/nodes/flows/util.js b/red/runtime/nodes/flows/util.js index b90a0bf32..9803679a9 100644 --- a/red/runtime/nodes/flows/util.js +++ b/red/runtime/nodes/flows/util.js @@ -67,7 +67,8 @@ module.exports = { flow.subflows[n.id].instances = []; } }); - + var linkWires = {}; + var linkOutNodes = []; config.forEach(function(n) { if (n.type !== 'subflow' && n.type !== 'tab') { var subflowDetails = subflowInstanceRE.exec(n.type); @@ -100,8 +101,28 @@ module.exports = { flow.configs[n.id]._users = []; } } + if (n.type === 'link in' && n.links) { + // Ensure wires are present in corresponding link out nodes + n.links.forEach(function(id) { + linkWires[id] = linkWires[id]||{}; + linkWires[id][n.id] = true; + }) + } else if (n.type === 'link out' && n.links) { + linkWires[n.id] = linkWires[n.id]||{}; + n.links.forEach(function(id) { + linkWires[n.id][id] = true; + }) + linkOutNodes.push(n); + } } }); + linkOutNodes.forEach(function(n) { + var links = linkWires[n.id]; + var targets = Object.keys(links); + n.wires = [targets]; + }); + + var addedTabs = {}; config.forEach(function(n) { if (n.type !== 'subflow' && n.type !== 'tab') {