diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 6adfb6d7f..a85c31cf1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -296,6 +296,7 @@ RED.history = (function() { }); RED.nodes.dirty(ev.dirty); + RED.view.select(null); RED.view.redraw(true); RED.palette.refresh(); RED.workspaces.refresh(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 82ec77c63..c4ed62d48 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -42,6 +42,7 @@ RED.view = (function() { var activeNodes = []; var activeLinks = []; var activeFlowLinks = []; + var activeLinkNodes = {}; var selected_link = null, mousedown_link = null, @@ -61,7 +62,8 @@ RED.view = (function() { clickElapsed = 0, scroll_position = [], quickAddActive = false, - quickAddLink = null; + quickAddLink = null, + showAllLinkPorts = -1; var clipboard = ""; @@ -263,19 +265,41 @@ RED.view = (function() { "stroke-width" : "1px" }); } - + var linkLayer = vis.append("g"); var dragGroup = vis.append("g"); + var nodeLayer = vis.append("g"); var drag_lines = []; function showDragLines(nodes) { + showAllLinkPorts = -1; for (var i=0;i 0) { + if (drag_lines[0].virtualLink) { + filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'} + } else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) { + filter = {input:true} + } else { + filter = {output:true} + } + } quickAddActive = true; RED.typeSearch.show({ x:d3.event.clientX-mainPos.left-node_width/2, y:d3.event.clientY-mainPos.top-node_height/2, + filter: filter, cancel: function() { quickAddActive = false; resetMouseVars(); @@ -685,19 +720,54 @@ RED.view = (function() { if (quickAddLink || drag_lines.length > 0) { var drag_line = quickAddLink||drag_lines[0]; var src = null,dst,src_port; - if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) { + if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { src = drag_line.node; src_port = drag_line.port; dst = nn; - } else if (drag_line.portType === PORT_TYPE_INPUT && nn.outputs > 0) { + } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { src = nn; dst = drag_line.node; src_port = 0; } + if (src !== null) { - var link = {source: src, sourcePort:src_port, target: dst}; - RED.nodes.addLink(link); - historyEvent.links = [link]; + if (drag_line.virtualLink) { + historyEvent = { + t:'multi', + events: [historyEvent] + } + var oldSrcLinks = $.extend(true,{},{v:src.links}).v + var oldDstLinks = $.extend(true,{},{v:dst.links}).v + src.links.push(dst.id); + dst.links.push(src.id); + src.dirty = true; + dst.dirty = true; + + historyEvent.events.push({ + t:'edit', + node: src, + dirty: RED.nodes.dirty(), + changed: src.changed, + changes: { + links:oldSrcLinks + } + }); + historyEvent.events.push({ + t:'edit', + node: dst, + dirty: RED.nodes.dirty(), + changed: dst.changed, + changes: { + links:oldDstLinks + } + }); + src.changed = true; + dst.changed = true; + } else { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + historyEvent.links = [link]; + } hideDragLines(); if (!quickAddLink && drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); @@ -1216,9 +1286,15 @@ RED.view = (function() { var currentLinks = activeLinks; var addedLinkLinks = {}; activeFlowLinks = []; + var activeLinkNodeIds = Object.keys(activeLinkNodes); + activeLinkNodeIds.forEach(function(n) { + activeLinkNodes[n].dirty = true; + }) + activeLinkNodes = {}; for (var i=0;i 0) { - if (drag_lines[0].node===d) { + if (drag_lines[0].node === d) { + // Cannot quick-join to self + return + } + if (drag_lines[0].virtualLink && + ( + (drag_lines[0].node.type === 'link in' && d.type !== 'link out') || + (drag_lines[0].node.type === 'link out' && d.type !== 'link in') + ) + ) { return } } @@ -1608,12 +1747,17 @@ RED.view = (function() { } var addedLinks = []; var removedLinks = []; + var modifiedNodes = []; // joining link nodes + + var select_link = null; for (i=0;i 0 || removedLinks.length > 0) { - var historyEvent = { - t:"add", - links:addedLinks, - removedLinks: removedLinks, - dirty:RED.nodes.dirty() - }; + if (addedLinks.length > 0 || removedLinks.length > 0 || modifiedNodes.length > 0) { + // console.log(addedLinks); + // console.log(removedLinks); + // console.log(modifiedNodes); + var historyEvent; + if (modifiedNodes.length > 0) { + historyEvent = { + t:"multi", + events: linkEditEvents, + dirty:RED.nodes.dirty() + }; + } else { + historyEvent = { + t:"add", + links:addedLinks, + removedLinks: removedLinks, + dirty:RED.nodes.dirty() + }; + } if (activeSubflow) { var subflowRefresh = RED.subflow.refresh(true); if (subflowRefresh) { @@ -1657,7 +1855,7 @@ RED.view = (function() { RED.nodes.dirty(true); } if (mouse_mode === RED.state.QUICK_JOINING) { - if (addedLinks.length > 0) { + if (addedLinks.length > 0 || modifiedNodes.length > 0) { hideDragLines(); if (portType === PORT_TYPE_INPUT && d.outputs > 0) { showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]); @@ -1666,6 +1864,11 @@ RED.view = (function() { } else { resetMouseVars(); } + selected_link = select_link; + mousedown_link = select_link; + if (select_link) { + updateSelection(); + } } redraw(); return; @@ -1673,7 +1876,11 @@ RED.view = (function() { resetMouseVars(); hideDragLines(); - selected_link = null; + selected_link = select_link; + mousedown_link = select_link; + if (select_link) { + updateSelection(); + } redraw(); } } @@ -1772,9 +1979,20 @@ RED.view = (function() { }); return tooltip; } + function portMouseOver(port,d,portType,portIndex) { clearTimeout(portLabelHoverTimeout); - var active = (mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== portType)); + var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active + ( + drag_lines.length > 0 && // Currently joining + drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT + ( + !drag_lines[0].virtualLink || // Not a link wire + (drag_lines[0].node.type === 'link in' && d.type === 'link out') || + (drag_lines[0].node.type === 'link out' && d.type === 'link in') + ) + ) + if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { portLabelHoverTimeout = setTimeout(function() { var tooltip = getPortLabel(d,portType,portIndex); @@ -1815,6 +2033,18 @@ RED.view = (function() { return; } var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) + + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines.length > 0) { + if (drag_lines[0].virtualLink) { + if (d.type === 'link in') { + direction = 1; + } else if (d.type === 'link out') { + direction = 0; + } + } + } + } portMouseUp(d, direction, 0); } @@ -2033,12 +2263,12 @@ RED.view = (function() { outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); // Don't bother redrawing nodes if we're drawing links - if (mouse_mode != RED.state.JOINING) { + if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { var dirtyNodes = {}; if (activeSubflow) { - var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;}); + var subflowOutputs = nodeLayer.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;}); subflowOutputs.exit().remove(); var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","node subflowoutput").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); outGroup.each(function(d,i) { @@ -2081,7 +2311,7 @@ RED.view = (function() { 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;}); + var subflowInputs = nodeLayer.selectAll(".subflowinput").data(activeSubflow.in,function(d,i){ return d.id;}); subflowInputs.exit().remove(); var inGroup = subflowInputs.enter().insert("svg:g").attr("class","node subflowinput").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); inGroup.each(function(d,i) { @@ -2143,11 +2373,11 @@ RED.view = (function() { } }); } else { - vis.selectAll(".subflowoutput").remove(); - vis.selectAll(".subflowinput").remove(); + nodeLayer.selectAll(".subflowoutput").remove(); + nodeLayer.selectAll(".subflowinput").remove(); } - var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id}); + var node = nodeLayer.selectAll(".nodegroup").data(activeNodes,function(d){return d.id}); node.exit().remove(); var nodeEnter = node.enter().insert("svg:g") @@ -2157,10 +2387,11 @@ RED.view = (function() { nodeEnter.each(function(d,i) { var node = d3.select(this); - var isLink = d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out") + var isLink = (d.type === "link in" || d.type === "link out") + var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); var l = RED.utils.getNodeLabel(d); - if (isLink) { + if (hideLabel) { d.w = node_height; } else { d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/20)) ); @@ -2332,7 +2563,7 @@ RED.view = (function() { .attr("x", 38) .attr("dy", ".35em") .attr("text-anchor","start") - .classed("hidden",isLink); + .classed("hidden",hideLabel); if (d._def.align) { text.attr("class","node_label node_label_"+d._def.align); @@ -2360,13 +2591,14 @@ RED.view = (function() { node.each(function(d,i) { if (d.dirty) { - var isLink = d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out") + var isLink = (d.type === "link in" || d.type === "link out") + var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; dirtyNodes[d.id] = d; //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette - if (/*!isLink &&*/ d.resize) { + if (d.resize) { var l = RED.utils.getNodeLabel(d); var ow = d.w; - if (isLink) { + if (hideLabel) { d.w = node_height; } else { d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/20)) ); @@ -2396,13 +2628,24 @@ RED.view = (function() { //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); var inputPorts = thisNode.selectAll(".port_input"); - if (d.inputs === 0 && !inputPorts.empty()) { + if (isLink && showAllLinkPorts === -1 && !activeLinkNodes[d.id] && d.inputs === 0 && !inputPorts.empty()) { inputPorts.remove(); - //nodeLabel.attr("x",30); - } else if (d.inputs === 1 && inputPorts.empty()) { + } else if (((isLink && (showAllLinkPorts===PORT_TYPE_INPUT||activeLinkNodes[d.id]))|| d.inputs === 1) && inputPorts.empty()) { var inputGroup = thisNode.append("g").attr("class","port_input"); - inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);}) + var inputGroupPorts; + + if (d.type === "link in") { + inputGroupPorts = inputGroup.append("circle") + .attr("cx",-1).attr("cy",5) + .attr("r",5) + .attr("class","port link_port") + // inputGroupPorts = inputGroup.append("path") + // .attr("d","M 4 -1 h -3 a 6 6 0 1 0 0 12 h 3") + // .attr("class","port link_port") + } else { + inputGroupPorts = inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + } + inputGroupPorts.on("mousedown",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);}) .on("touchstart",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);}) .on("mouseup",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);} ) .on("touchend",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);} ) @@ -2411,13 +2654,38 @@ RED.view = (function() { } var numOutputs = d.outputs; + if (isLink && d.type === "link out") { + if (showAllLinkPorts===PORT_TYPE_OUTPUT || activeLinkNodes[d.id]) { + d.ports = [0]; + numOutputs = 1; + } else { + d.ports = []; + numOutputs = 0; + } + } var y = (d.h/2)-((numOutputs-1)/2)*13; d.ports = d.ports || d3.range(numOutputs); d._ports = thisNode.selectAll(".port_output").data(d.ports); var output_group = d._ports.enter().append("g").attr("class","port_output"); + var output_group_ports; - output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() ) + if (d.type === "link out") { + output_group_ports = output_group.append("circle") + .attr("cx",11).attr("cy",5) + .attr("r",5) + .attr("class","port link_port") + // output_group_ports = output_group.append("path") + // .attr("d","M 6 -1 h 3 a 6 6 0 1 1 0 12 h -3") + // .attr("class","port link_port") + } else { + output_group_ports = output_group.append("rect") + .attr("class","port") + .attr("rx",3).attr("ry",3) + .attr("width",10) + .attr("height",10) + } + + output_group_ports.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() ) .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() ) .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);}})() ) .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);}})() ) @@ -2464,7 +2732,7 @@ RED.view = (function() { } return "node_label"+ (d._def.align?" node_label_"+d._def.align:"")+s; - }).classed("hidden",isLink); + }).classed("hidden",hideLabel); if (d._def.icon) { var icon = thisNode.select(".node_icon"); var faIcon = thisNode.select(".fa-lg"); @@ -2592,7 +2860,7 @@ RED.view = (function() { } }); - var link = vis.selectAll(".link").data( + var link = linkLayer.selectAll(".link").data( activeLinks, function(d) { return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; @@ -2638,7 +2906,7 @@ RED.view = (function() { }); link.exit().remove(); - var links = vis.selectAll(".link_path"); + var links = linkLayer.selectAll(".link_path"); links.each(function(d) { var link = d3.select(this); if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { @@ -2666,7 +2934,7 @@ RED.view = (function() { delete d.added; return d.target.type == "unknown" || d.source.type == "unknown" }); - var offLinks = vis.selectAll(".link_flow_link_g").data( + var offLinks = linkLayer.selectAll(".link_flow_link_g").data( activeFlowLinks, function(d) { return d.node.id+":"+d.refresh @@ -2697,10 +2965,13 @@ RED.view = (function() { 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('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('link_group_active',true)}) + .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('link_group_active',false)}) .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) .on('mouseup', function(f) { + if (mouse_mode !== 0) { + return + } d3.event.stopPropagation(); var targets = d.links[f]; RED.workspaces.show(f); @@ -2772,7 +3043,7 @@ RED.view = (function() { linkGroups.exit().remove(); }); offLinks.exit().remove(); - offLinks = vis.selectAll(".link_flow_link_g"); + offLinks = linkLayer.selectAll(".link_flow_link_g"); offLinks.each(function(d) { var s = 1; if (d.node.type === "link in") { @@ -2785,7 +3056,7 @@ RED.view = (function() { } else { // JOINING - unselect any selected links - vis.selectAll(".link_selected").data( + linkLayer.selectAll(".link_selected").data( activeLinks, function(d) { return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index de86a3f4c..ecd95847f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -194,8 +194,8 @@ } .port_hovered { - stroke: $port-selected-color; - fill: $port-selected-color; + stroke: $port-selected-color !important; + fill: $port-selected-color !important; } .port_quick_link { @@ -211,7 +211,7 @@ } .drag_line { - stroke: $node-selected-color; + stroke: $node-selected-color !important; stroke-width: 3; fill: none; pointer-events: none; @@ -236,10 +236,10 @@ stroke: $link-link-color; fill: none; stroke-dasharray: 15,2; - pointer-events: none; + // pointer-events: none; } .link_port { - fill: #fff; + fill: #eee; stroke: $link-link-color; stroke-width: 1; }