diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 0814892be..66af9c083 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -108,6 +108,168 @@ RED.view.tools = (function() { } } + function layoutFlow() { + + var selection = RED.view.selection(); + if (!selection.nodes || selection.nodes.length !== 1) { + RED.notify("Select exactly one node"); + return; + } + var ns = RED.nodes.getAllFlowNodes(selection.nodes[0]); + + // Find Input node + + var nodes = {}; + var minRank = 0; + var stack = []; + var candidateInputs = {}; + var candidateOutputs = {}; + ns.forEach(function(n) { + candidateInputs[n.id] = n; + candidateOutputs[n.id] = n; + nodes[n.id] = { + n:n, + i:[], + o:[], + d:-1, // depth from start + r:-1, // rank order at that depth + downstream: 0 + } + }); + RED.nodes.eachLink(function(link) { + if (nodes[link.source.id] || nodes[link.target.id]) { + nodes[link.source.id].o.push(link.target.id); + nodes[link.target.id].i.push(link.source.id); + delete candidateInputs[link.target.id] + delete candidateOutputs[link.source.id] + } + }) + + var inputs = Object.keys(candidateInputs); + var outputs = Object.keys(candidateOutputs); + + if (inputs.length > 1) { + RED.notify("Multiple start points - bailing") + return; + } + + if (outputs.length === 0) { + RED.notify("No outputs - is this a big loop? Bailing"); + return; + } + + function applyDepth(id,d) { + if (nodes[id].d < d) { + nodes[id].d = d; + nodes[id].o.forEach(function(nid) { + applyDepth(nid,d+1); + }) + } + } + applyDepth(inputs[0],0) + + function calculateDownstream(id,downstream) { + nodes[id].downstream += downstream; + nodes[id].i.forEach(function(nid) { + calculateDownstream(nid, nodes[id].downstream+1); + }) + } + outputs.forEach(function(id) { + calculateDownstream(id, 0) + }) + + var ranks = {}; + function rankNodes(node) { + if (node.r === -1) { + ranks[node.d] = ranks[node.d] || []; + node.r = ranks[node.d].length; + ranks[node.d].push(node); + node.o.sort(function(a,b) { + return nodes[b].downstream - nodes[a].downstream + }) + node.o.forEach(function(nid) { + rankNodes(nodes[nid]) + }) + } + } + rankNodes(nodes[inputs[0]]); + function shuffleRanks(node) { + var pushed = false; + if (node.o.length > 1) { + var outputs = node.o.slice(0); + outputs.sort(function(a,b) { + if (nodes[a].d === nodes[b].d) { + return nodes[a].r - nodes[b].r; + } else { + return nodes[b].d - nodes[a].d; + } + }) + // outputs.forEach(function(o,i) { console.log(" ",i," + "+nodes[o].n.type," d:",nodes[o].d," r:",nodes[o].r)}); + var rank = nodes[outputs[0]].r; + var depth = nodes[outputs[0]].d; + for (var i=1;i= n.d && nn.d < depth && nn.r >= r) { + pushed = true; + nn.r++; + } + }) + } + depth = n.d; + rank = n.r; + } + } + node.o.forEach(function(n) { + pushed = pushed || shuffleRanks(nodes[n]) + }) + return pushed; + } + var shuffle = function() { + if (shuffleRanks(nodes[inputs[0]])) { + shuffle(); + } + } + shuffle(); + + + var x = nodes[inputs[0]].n.x; + var y = nodes[inputs[0]].n.y; + var changedNodes = []; + ns.forEach(function(n) { + var d = nodes[n.id].d; + var r = nodes[n.id].r; + + changedNodes.push({ + n:n, + ox: n.x, + oy: n.y, + moved: n.moved + }); + + n.x = x + d*200; + n.y = y + r*50; + n.dirty = true; + // n.dirtyStatus = true; + // n.status = { + // text:"d"+d+" : r"+r+" : ds"+nodes[n.id].downstream + // } + }); + + if (changedNodes.length > 0) { + RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); + RED.nodes.dirty(true); + RED.view.redraw(true); + } + + } + + return { init: function() { RED.actions.add("core:align-selection-to-grid", alignToGrid); @@ -121,6 +283,8 @@ RED.view.tools = (function() { RED.actions.add("core:step-selection-right", function() { moveSelection(RED.view.gridSize(),0);}); RED.actions.add("core:step-selection-down", function() { moveSelection(0,RED.view.gridSize());}); RED.actions.add("core:step-selection-left", function() { moveSelection(-RED.view.gridSize(),0);}); + + RED.actions.add("core:layout-flow", function() { layoutFlow() }) }, /** * Aligns all selected nodes to the current grid