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 59531c48a..f900276a0 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 @@ -106,6 +106,23 @@ RED.history = (function() { RED.nodes.removeLink(ev.links[i]); } } + if (ev.junctions) { + inverseEv.junctions = []; + for (i=0;i=0;i--) { @@ -272,6 +289,21 @@ RED.history = (function() { } } } + if (ev.junctions) { + inverseEv.junctions = []; + for (i=0;i { + if (n.type === 'junction') { + removedJunctions.push(n) + return false + } else { + return true + } + }) } for (i in configNodes) { if (configNodes.hasOwnProperty(i)) { @@ -842,6 +853,10 @@ RED.nodes = (function() { var result = removeNode(removedNodes[i].id); removedLinks = removedLinks.concat(result.links); } + for (i=0;i 0) { $('
').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts); } - - + } else if (node.type === 'junction') { + propertiesPanelHeaderHelp.hide(); } else { propertiesPanelHeaderHelp.show(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index 183c807b8..443513f87 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -1028,6 +1028,8 @@ RED.utils = (function() { return "font-awesome/fa-object-ungroup"; } else if (node && node.type === 'group') { return "font-awesome/fa-object-group" + } else if (node && node.type === 'junction') { + return "font-awesome/fa-circle-o" } else if (def.category === 'config') { return RED.settings.apiRootUrl+"icons/node-red/cog.svg" } else if (node && node.type === 'tab') { @@ -1093,6 +1095,8 @@ RED.utils = (function() { l = node.label || defaultLabel } else if (node.type === 'group') { l = node.name || defaultLabel + } else if (node.type === 'junction') { + l = 'junction' } else { l = node._def.label; try { @@ -1247,6 +1251,8 @@ RED.utils = (function() { nodeDiv.addClass("red-ui-palette-icon-selection"); } else if (node.type === "group") { nodeDiv.addClass("red-ui-palette-icon-group"); + } else if (node.type === "junction") { + nodeDiv.addClass("red-ui-palette-icon-junction"); } else if (node.type === 'tab') { nodeDiv.addClass("red-ui-palette-icon-flow"); } else { 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 263e1880e..4af09bb55 100755 --- 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 @@ -24,6 +24,7 @@ * |- "groupLayer" * |- "groupSelectLayer" * |- "linkLayer" + * |- "junctionLayer" * |- "dragGroupLayer" * |- "nodeLayer" */ @@ -56,6 +57,7 @@ RED.view = (function() { var activeSubflow = null; var activeNodes = []; var activeLinks = []; + var activeJunctions = []; var activeFlowLinks = []; var activeLinkNodes = {}; var activeGroup = null; @@ -111,6 +113,7 @@ RED.view = (function() { var eventLayer; var gridLayer; var linkLayer; + var junctionLayer; var dragGroupLayer; var groupSelectLayer; var nodeLayer; @@ -199,6 +202,11 @@ RED.view = (function() { function init() { +// setTimeout(function() { +// function snap(p) { return RED.view.gridSize() * Math.round(p/RED.view.gridSize())}; for (var i = 0;i<10;i++) { +// RED.nodes.addJunction({_def:{defaults:{}}, type:'junction', z:"0ccdc1d81f2729cc",id:RED.nodes.id(),x:snap(Math.floor(Math.random()*600)),y:snap(Math.floor(Math.random()*600)), w:0,h:0}) +// } ; RED.view.redraw(true) +// },2000) chart = $("#red-ui-workspace-chart"); outer = d3.select("#red-ui-workspace-chart") @@ -373,6 +381,7 @@ RED.view = (function() { groupSelectLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); + junctionLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); drag_lines = []; @@ -785,7 +794,7 @@ RED.view = (function() { source:{z:activeWorkspace}, target:{z:activeWorkspace} }); - + activeJunctions = RED.nodes.junctions(activeWorkspace) || []; activeGroups = RED.nodes.groups(activeWorkspace)||[]; activeGroups.forEach(function(g, i) { g._index = i; @@ -800,6 +809,7 @@ RED.view = (function() { } else { activeNodes = []; activeLinks = []; + activeJunctions = []; activeGroups = []; } @@ -970,9 +980,9 @@ RED.view = (function() { .attr("class","nr-ui-view-lasso"); d3.event.preventDefault(); } - } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) { + } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey || d3.event.shiftKey)) { clearSelection(); - mouse_mode = RED.state.SLICING; + mouse_mode = (d3.event.metaKey || d3.event.ctrlKey)?RED.state.SLICING : RED.state.SLICING_JUNCTION; point = d3.mouse(this); slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`) slicePathLast = point; @@ -1373,7 +1383,7 @@ RED.view = (function() { .attr("height",h) ; return; - } else if (mouse_mode === RED.state.SLICING) { + } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { if (slicePath) { var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1])) if (delta > 20) { @@ -1739,6 +1749,15 @@ RED.view = (function() { } } }); + activeJunctions.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + } + }) @@ -1782,11 +1801,92 @@ RED.view = (function() { } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { clearSelection(); updateSelection(); - } else if (slicePath) { + } else if (mouse_mode == RED.state.SLICING) { deleteSelection(); slicePath.remove(); slicePath = null; RED.view.redraw(true); + } else if (mouse_mode == RED.state.SLICING_JUNCTION) { + var removedLinks = [] + var addedLinks = [] + var addedJunctions = [] + + var groupedLinks = {} + selectedLinks.forEach(function(l) { + var sourceId = l.source.id+":"+l.sourcePort + groupedLinks[sourceId] = groupedLinks[sourceId] || [] + groupedLinks[sourceId].push(l) + }); + var linkGroups = Object.keys(groupedLinks) + linkGroups.forEach(function(gid) { + var links = groupedLinks[gid] + var junction = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + links.forEach(function(l) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + }) + junction.x = Math.round(junction.x/links.length) + junction.y = Math.round(junction.y/links.length) + if (snapGrid) { + junction.x = (gridSize*Math.round(junction.x/gridSize)); + junction.y = (gridSize*Math.round(junction.y/gridSize)); + } + + var nodeGroups = new Set() + + RED.nodes.addJunction(junction) + addedJunctions.push(junction) + var newLink = { + source: links[0].source, + sourcePort: links[0].sourcePort, + target: junction + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + links.forEach(function(l) { + removedLinks.push(l) + RED.nodes.removeLink(l) + var newLink = { + source: junction, + sourcePort: 0, + target: l.target + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + nodeGroups.add(l.source.g || "__NONE__") + nodeGroups.add(l.target.g || "__NONE__") + }) + if (nodeGroups.size === 1) { + var group = nodeGroups.values().next().value + if (group !== "__NONE__") { + RED.group.addToGroup(RED.nodes.group(group), junction) + } + } + }) + slicePath.remove(); + slicePath = null; + + if (addedJunctions.length > 0) { + RED.history.push({ + t: 'add', + links: addedLinks, + junctions: addedJunctions, + removedLinks: removedLinks + }) + RED.nodes.dirty(true) + } + RED.view.redraw(true); } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (movingSet.length() > 0) { @@ -1943,7 +2043,7 @@ RED.view = (function() { clearSelection(); RED.history.pop(); mouse_mode = 0; - } else if (mouse_mode === RED.state.SLICING) { + } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { if (slicePath) { slicePath.remove(); slicePath = null; @@ -2012,6 +2112,14 @@ RED.view = (function() { } }); + activeJunctions.forEach(function(n) { + if (!n.selected) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + }) + if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) { activeSubflow.in.forEach(function(n) { if (!n.selected) { @@ -2211,6 +2319,7 @@ RED.view = (function() { nodes: [], links: [], groups: [], + junctions: [], workspaces: [], subflows: [] } @@ -2231,6 +2340,7 @@ RED.view = (function() { historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes); historyEvent.links = historyEvent.links.concat(subEvent.links); historyEvent.groups = historyEvent.groups.concat(subEvent.groups); + historyEvent.junctions = historyEvent.junctions.concat(subEvent.junctions); } RED.history.push(historyEvent); RED.nodes.dirty(true); @@ -2243,6 +2353,7 @@ RED.view = (function() { var removedNodes = []; var removedLinks = []; var removedGroups = []; + var removedJunctions = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; var removedSubflowStatus; @@ -2287,7 +2398,7 @@ RED.view = (function() { for (var i=0;i 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) { RED.nodes.dirty(true); } } @@ -2396,6 +2511,7 @@ RED.view = (function() { nodes:removedNodes, links:removedLinks, groups: removedGroups, + junctions: removedJunctions, subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { @@ -2455,6 +2571,7 @@ RED.view = (function() { var nns = []; var nodeCount = 0; var groupCount = 0; + var junctionCount = 0; var handled = {}; for (var n=0;n d.id + ) + var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction") + junctionEnter.each(function(d,i) { + var junction = d3.select(this); + var contents = document.createDocumentFragment(); + // d.added = true; + var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionBack.setAttribute("class","red-ui-flow-junction-background"); + junctionBack.setAttribute("x",-5); + junctionBack.setAttribute("y",-5); + junctionBack.setAttribute("width",10); + junctionBack.setAttribute("height",10); + junctionBack.setAttribute("rx",5); + junctionBack.setAttribute("ry",5); + junctionBack.__data__ = d; + this.__junctionBack__ = junctionBack; + contents.appendChild(junctionBack); + + junctionBack.addEventListener("mouseover", junctionMouseOverProxy); + junctionBack.addEventListener("mouseout", junctionMouseOutProxy); + junctionBack.addEventListener("mouseup", portMouseUpProxy); + junctionBack.addEventListener("mousedown", junctionMouseDownProxy); + + // d3.select(junctionBack).on("mousedown", nodeMouseDown); + + this.__portType__ = PORT_TYPE_INPUT + this.__portIndex__ = 0 +// function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } + + junction[0][0].appendChild(contents); + }) + junction.exit().remove(); + junction.each(function(d) { + var junction = d3.select(this); + this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")"); + if (d.dirty) { + junction.classed("selected", !!d.selected) + dirtyNodes[d.id] = d; + + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } + + } + + }) + var link = linkLayer.selectAll(".red-ui-flow-link").data( activeLinks, function(d) { @@ -4657,6 +4882,27 @@ RED.view = (function() { selectedLinks.add(d) l.classed("red-ui-flow-link-splice",true) redraw() + } else if (mouse_mode === RED.state.SLICING_JUNCTION) { + if (!l.classed("red-ui-flow-link-splice")) { + // Find intersection point + var lineLength = pathLine.getTotalLength(); + var pos; + var delta = Infinity; + for (var i = 0; i < lineLength; i++) { + var linePos = pathLine.getPointAtLength(i); + var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) + var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) + var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY + if (posDelta < delta) { + pos = linePos + delta = posDelta + } + } + d._sliceLocation = pos + selectedLinks.add(d) + l.classed("red-ui-flow-link-splice",true) + redraw() + } } }) @@ -4683,9 +4929,9 @@ RED.view = (function() { var numOutputs = d.source.outputs || 1; var sourcePort = d.sourcePort || 0; var y = -((numOutputs-1)/2)*13 +13*sourcePort; - d.x1 = d.source.x+d.source.w/2; + d.x1 = d.source.x+(d.source.w/2||0); d.y1 = d.source.y+y; - d.x2 = d.target.x-d.target.w/2; + d.x2 = d.target.x-(d.target.w/2||0); d.y2 = d.target.y; // return "M "+d.x1+" "+d.y1+ @@ -5144,6 +5390,7 @@ RED.view = (function() { var new_nodes = result.nodes; var new_links = result.links; var new_groups = result.groups; + var new_junctions = result.junctions; var new_workspaces = result.workspaces; var new_subflows = result.subflows; var removedNodes = result.removedNodes; @@ -5153,6 +5400,7 @@ RED.view = (function() { } var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) + new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); clearSelection(); @@ -5186,9 +5434,11 @@ RED.view = (function() { node.n.moved = true; node.n.x -= dx - mouse_position[0]; node.n.y -= dy - mouse_position[1]; - node.n.w = node_width; - node.n.h = node_height; - node.n.resize = true; + if (node.n.type !== 'junction') { + node.n.w = node_width; + node.n.h = node_height; + node.n.resize = true; + } node.dx = node.n.x - mouse_position[0]; node.dy = node.n.y - mouse_position[1]; if (node.n.type === "group") { @@ -5235,6 +5485,7 @@ RED.view = (function() { nodes:new_node_ids, links:new_links, groups:new_groups, + junctions: new_junctions, workspaces:new_workspaces, subflows:new_subflows, dirty:RED.nodes.dirty() @@ -5282,6 +5533,7 @@ RED.view = (function() { } }) var newGroupCount = new_groups.length; + var newJunctionCount = new_junctions.length; if (new_workspaces.length > 0) { counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); } 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 f3dd1ca44..91f80ac0c 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 @@ -379,3 +379,17 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { white-space: pre; @include disable-selection; } +.red-ui-flow-junction-background { + stroke: $node-border; + stroke-width: 1; + fill: $node-port-background; + cursor: crosshair; +} +.red-ui-flow-junction-hovered { + stroke: $port-selected-color; + fill: $port-selected-color; +} +.red-ui-flow-junction.selected .red-ui-flow-junction-background { + stroke: $port-selected-color; + // fill: $port-selected-color; +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 4d3eee26e..397dd4765 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -189,6 +189,7 @@ .red-ui-search-result-node { &.red-ui-palette-icon-flow, &.red-ui-palette-icon-group, + &.red-ui-palette-icon-junction, &.red-ui-palette-icon-selection { background: none; border-color: transparent; @@ -268,6 +269,7 @@ &.red-ui-palette-icon-flow, &.red-ui-palette-icon-group, + &.red-ui-palette-icon-junction, &.red-ui-palette-icon-selection { background: none; border-color: transparent; @@ -303,6 +305,7 @@ &.red-ui-palette-icon-flow { margin-top: -2px; } + &.red-ui-palette-icon-junction .red-ui-palette-icon-fa, &.red-ui-palette-icon-group .red-ui-palette-icon-fa { font-size: 14px; } diff --git a/packages/node_modules/@node-red/nodes/core/common/05-junction.html b/packages/node_modules/@node-red/nodes/core/common/05-junction.html new file mode 100644 index 000000000..3fd69a85c --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/common/05-junction.html @@ -0,0 +1,5 @@ + diff --git a/packages/node_modules/@node-red/nodes/core/common/05-junction.js b/packages/node_modules/@node-red/nodes/core/common/05-junction.js new file mode 100644 index 000000000..792dac829 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/common/05-junction.js @@ -0,0 +1,12 @@ +module.exports = function(RED) { + "use strict"; + function JunctionNode(n) { + RED.nodes.createNode(this,n); + this.on("input",function(msg, send, done) { + send(msg); + done(); + }); + } + + RED.nodes.registerType("junction",JunctionNode); +}