[groups] Support copy/paste/import/export of groups

This commit is contained in:
Nick O'Leary 2020-03-09 11:14:18 +00:00
parent 9a0c843f29
commit d1dd7d1d51
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
4 changed files with 102 additions and 18 deletions

View File

@ -500,6 +500,9 @@ RED.nodes = (function() {
if (n.d === true) { if (n.d === true) {
node.d = true; node.d = true;
} }
if (n.g) {
node.g = n.g;
}
if (node.type == "unknown") { if (node.type == "unknown") {
for (var p in n._orig) { for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) { 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") { if (n._def.category != "config") {
node.x = n.x; node.x = n.x;
node.y = n.y; node.y = n.y;
@ -735,6 +741,11 @@ RED.nodes = (function() {
nns.push(convertSubflow(subflows[i], exportCredentials)); nns.push(convertSubflow(subflows[i], exportCredentials));
} }
} }
for (i in groups) {
if (groups.hasOwnProperty(i)) {
nns.push(convertNode(groups[i]));
}
}
for (i in configNodes) { for (i in configNodes) {
if (configNodes.hasOwnProperty(i)) { if (configNodes.hasOwnProperty(i)) {
nns.push(convertNode(configNodes[i], exportCredentials)); nns.push(convertNode(configNodes[i], exportCredentials));
@ -861,6 +872,7 @@ RED.nodes = (function() {
if (n.type != "workspace" && if (n.type != "workspace" &&
n.type != "tab" && n.type != "tab" &&
n.type != "subflow" && n.type != "subflow" &&
n.type != "group" &&
!registry.getNodeType(n.type) && !registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" && n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) { unknownTypes.indexOf(n.type)==-1) {
@ -910,6 +922,7 @@ RED.nodes = (function() {
var node_map = {}; var node_map = {};
var new_nodes = []; var new_nodes = [];
var new_links = []; var new_links = [];
var new_groups = [];
var nid; var nid;
var def; var def;
var configNode; var configNode;
@ -1077,20 +1090,25 @@ RED.nodes = (function() {
y:parseFloat(n.y || 0), y:parseFloat(n.y || 0),
z:n.z, z:n.z,
type:0, type:0,
wires:n.wires||[],
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
info: n.info, info: n.info,
changed:false, changed:false,
_config:{} _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')) { if (n.hasOwnProperty('l')) {
node.l = n.l; node.l = n.l;
} }
if (n.hasOwnProperty('d')) { if (n.hasOwnProperty('d')) {
node.d = n.d; node.d = n.d;
} }
if (n.hasOwnProperty('g')) {
node.g = n.g;
}
if (createNewIds) { if (createNewIds) {
if (subflow_blacklist[n.z]) { if (subflow_blacklist[n.z]) {
continue; continue;
@ -1127,7 +1145,17 @@ RED.nodes = (function() {
} }
node.type = n.type; node.type = n.type;
node._def = def; 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 parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId); var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) { if (createNewIds) {
@ -1217,13 +1245,19 @@ RED.nodes = (function() {
} }
} }
} }
addNode(node); if (node.type !== "group") {
RED.editor.validateNode(node); addNode(node);
RED.editor.validateNode(node);
} else {
addGroup(node);
}
node_map[n.id] = node; node_map[n.id] = node;
// If an 'unknown' config node, it will not have been caught by the // If an 'unknown' config node, it will not have been caught by the
// proper config node handling, so needs adding to new_nodes here // proper config node handling, so needs adding to new_nodes here
if (node.type === "unknown" || node._def.category !== "config") { if (node.type === "unknown" || node._def.category !== "config") {
new_nodes.push(node); new_nodes.push(node);
} else if (node.type === "group") {
new_groups.push(node);
} }
} }
} }
@ -1258,6 +1292,11 @@ RED.nodes = (function() {
} }
delete n.wires; 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) { for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) { if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) { if (n._def.defaults[d3].type && node_map[n[d3]]) {
@ -1326,6 +1365,17 @@ RED.nodes = (function() {
delete n.status.wires; delete n.status.wires;
} }
} }
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g;
}
n.nodes = n.nodes.map(function(id) {
return node_map[id];
})
}
RED.workspaces.refresh(); RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace]; return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];

View File

@ -37,7 +37,8 @@ RED.group = (function() {
var groupDef = { var groupDef = {
defaults:{ defaults:{
name:{value:""}, name:{value:""},
style:{value:{}} style:{value:{}},
nodes:{value:[]}
}, },
category: "config", category: "config",
oneditprepare: function() { oneditprepare: function() {

View File

@ -1390,13 +1390,15 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); }
if (!d3.event.shiftKey) { if (!d3.event.shiftKey) {
clearSelection(); clearSelection();
} }
var selectedGroups = [];
activeNodes.forEach(function(n) { activeNodes.forEach(function(n) {
if (n.z == RED.workspaces.active() && !n.selected) { if (n.z == RED.workspaces.active() && !n.selected) {
if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
if (n.g) { if (n.g) {
var group = RED.nodes.group(n.g); var group = RED.nodes.group(n.g);
if (!group.selected) { if (!group.selected) {
selectGroup(RED.nodes.group(n.g),true); selectGroup(group,true);
selectedGroups.push(group)
} }
} else { } else {
n.selected = true; 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) { if (activeSubflow) {
activeSubflow.in.forEach(function(n) { activeSubflow.in.forEach(function(n) {
n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); 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) { if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
return; return;
} }
exitActiveGroup();
activeGroups.forEach(function(g) { activeGroups.forEach(function(g) {
selectGroup(g, true); if (!g.g) {
if (!g.selected) { selectGroup(g, true);
g.selected = true; if (!g.selected) {
g.selected = true;
g.dirty = true;
}
} else {
g.selected = false;
g.dirty = true; g.dirty = true;
} }
}) })
@ -1940,8 +1958,16 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); }
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
} }
}); });
} else if (moving_set.length > 0) { } else {
nodes = moving_set.map(function(n) { return n.n }); 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) { if (nodes.length > 0) {
@ -1966,6 +1992,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); }
} }
} }
clipboard = JSON.stringify(nns); clipboard = JSON.stringify(nns);
console.log(nns);
RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); 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.scrollLeft(chart.scrollLeft()+x);
chart.scrollTop(chart.scrollTop()+y) chart.scrollTop(chart.scrollTop()+y)
} }
,ms: function() { return moving_set }
}; };
})(); })();

View File

@ -85,6 +85,7 @@ module.exports = {
flow.subflows = {}; flow.subflows = {};
flow.configs = {}; flow.configs = {};
flow.flows = {}; flow.flows = {};
flow.groups = {};
flow.missingTypes = []; flow.missingTypes = [];
config.forEach(function(n) { config.forEach(function(n) {
@ -95,8 +96,12 @@ module.exports = {
flow.flows[n.id].configs = {}; flow.flows[n.id].configs = {};
flow.flows[n.id].nodes = {}; 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) { config.forEach(function(n) {
if (n.type === 'subflow') { if (n.type === 'subflow') {
flow.subflows[n.id] = n; flow.subflows[n.id] = n;
@ -108,7 +113,7 @@ module.exports = {
var linkWires = {}; var linkWires = {};
var linkOutNodes = []; var linkOutNodes = [];
config.forEach(function(n) { 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); var subflowDetails = subflowInstanceRE.exec(n.type);
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
@ -163,7 +168,7 @@ module.exports = {
var addedTabs = {}; var addedTabs = {};
config.forEach(function(n) { 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) { for (var prop in n) {
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
// This property references a global config node // This property references a global config node