diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 5c0004d8c..5afd10529 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -500,6 +500,9 @@ RED.nodes = (function() { if (n.d === true) { node.d = true; } + if (n.g) { + node.g = n.g; + } if (node.type == "unknown") { for (var p in n._orig) { if (n._orig.hasOwnProperty(p)) { @@ -547,6 +550,9 @@ RED.nodes = (function() { } } } + if (n.type === "group") { + node.nodes = node.nodes.map(function(n) { return n.id }); + } if (n._def.category != "config") { node.x = n.x; node.y = n.y; @@ -735,6 +741,11 @@ RED.nodes = (function() { nns.push(convertSubflow(subflows[i], exportCredentials)); } } + for (i in groups) { + if (groups.hasOwnProperty(i)) { + nns.push(convertNode(groups[i])); + } + } for (i in configNodes) { if (configNodes.hasOwnProperty(i)) { nns.push(convertNode(configNodes[i], exportCredentials)); @@ -861,6 +872,7 @@ RED.nodes = (function() { if (n.type != "workspace" && n.type != "tab" && n.type != "subflow" && + n.type != "group" && !registry.getNodeType(n.type) && n.type.substring(0,8) != "subflow:" && unknownTypes.indexOf(n.type)==-1) { @@ -910,6 +922,7 @@ RED.nodes = (function() { var node_map = {}; var new_nodes = []; var new_links = []; + var new_groups = []; var nid; var def; var configNode; @@ -1077,20 +1090,25 @@ RED.nodes = (function() { y:parseFloat(n.y || 0), z:n.z, type:0, - wires:n.wires||[], - inputLabels: n.inputLabels, - outputLabels: n.outputLabels, - icon: n.icon, info: n.info, changed:false, _config:{} - }; + } + if (n.type !== "group") { + node.wires = n.wires||[]; + node.inputLabels = n.inputLabels; + node.outputLabels = n.outputLabels; + node.icon = n.icon; + } if (n.hasOwnProperty('l')) { node.l = n.l; } if (n.hasOwnProperty('d')) { node.d = n.d; } + if (n.hasOwnProperty('g')) { + node.g = n.g; + } if (createNewIds) { if (subflow_blacklist[n.z]) { continue; @@ -1127,7 +1145,17 @@ RED.nodes = (function() { } node.type = n.type; node._def = def; - if (n.type.substring(0,7) === "subflow") { + if (node.type === "group") { + node._def = RED.group.def; + for (d in node._def.defaults) { + if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') { + node[d] = n[d]; + node._config[d] = JSON.stringify(n[d]); + } + } + node._config.x = node.x; + node._config.y = node.y; + } else if (n.type.substring(0,7) === "subflow") { var parentId = n.type.split(":")[1]; var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId); if (createNewIds) { @@ -1217,13 +1245,19 @@ RED.nodes = (function() { } } } - addNode(node); - RED.editor.validateNode(node); + if (node.type !== "group") { + addNode(node); + RED.editor.validateNode(node); + } else { + addGroup(node); + } node_map[n.id] = node; // If an 'unknown' config node, it will not have been caught by the // proper config node handling, so needs adding to new_nodes here if (node.type === "unknown" || node._def.category !== "config") { new_nodes.push(node); + } else if (node.type === "group") { + new_groups.push(node); } } } @@ -1258,6 +1292,11 @@ RED.nodes = (function() { } delete n.wires; } + if (n.g && node_map[n.g]) { + n.g = node_map[n.g].id; + } else { + delete n.g + } for (var d3 in n._def.defaults) { if (n._def.defaults.hasOwnProperty(d3)) { if (n._def.defaults[d3].type && node_map[n[d3]]) { @@ -1326,6 +1365,17 @@ RED.nodes = (function() { delete n.status.wires; } } + for (i=0;i x && n.x < x2 && n.y > y && n.y < y2) { if (n.g) { var group = RED.nodes.group(n.g); if (!group.selected) { - selectGroup(RED.nodes.group(n.g),true); + selectGroup(group,true); + selectedGroups.push(group) } } else { n.selected = true; @@ -1406,6 +1408,17 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } } } }); + var selectionChanged = false; + do { + selectionChanged = false; + selectedGroups.forEach(function(g) { + if (g.g && g.selected && RED.nodes.group(g.g).selected) { + g.selected = false; + selectionChanged = true; + } + }) + } while(selectionChanged); + if (activeSubflow) { activeSubflow.in.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); @@ -1550,11 +1563,16 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) { return; } - + exitActiveGroup(); activeGroups.forEach(function(g) { - selectGroup(g, true); - if (!g.selected) { - g.selected = true; + if (!g.g) { + selectGroup(g, true); + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + } else { + g.selected = false; g.dirty = true; } }) @@ -1940,8 +1958,16 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); } }); - } else if (moving_set.length > 0) { - nodes = moving_set.map(function(n) { return n.n }); + } else { + if (moving_set.length > 0) { + nodes = moving_set.map(function(n) { return n.n }); + } + var groups = activeGroups.filter(function(g) { return g.selected && !g.active }); + while (groups.length > 0) { + var group = groups.shift(); + nodes.push(group); + groups = groups.concat(group.nodes.filter(function(n) { return n.type === "group" })) + } } if (nodes.length > 0) { @@ -1966,6 +1992,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } } } clipboard = JSON.stringify(nns); + console.log(nns); RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } @@ -4536,5 +4563,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } chart.scrollLeft(chart.scrollLeft()+x); chart.scrollTop(chart.scrollTop()+y) } + ,ms: function() { return moving_set } }; })(); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js index b6074d792..e06cc5ede 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js @@ -85,6 +85,7 @@ module.exports = { flow.subflows = {}; flow.configs = {}; flow.flows = {}; + flow.groups = {}; flow.missingTypes = []; config.forEach(function(n) { @@ -95,8 +96,12 @@ module.exports = { flow.flows[n.id].configs = {}; flow.flows[n.id].nodes = {}; } + if (n.type === 'group') { + flow.groups[n.id] = n; + } }); + // TODO: why a separate forEach? this can be merged with above config.forEach(function(n) { if (n.type === 'subflow') { flow.subflows[n.id] = n; @@ -108,7 +113,7 @@ module.exports = { var linkWires = {}; var linkOutNodes = []; config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab') { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { var subflowDetails = subflowInstanceRE.exec(n.type); if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { @@ -163,7 +168,7 @@ module.exports = { var addedTabs = {}; config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab') { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { for (var prop in n) { if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { // This property references a global config node