1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

[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) {
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<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();
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];

View File

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

View File

@ -1390,13 +1390,15 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); }
if (!d3.event.shiftKey) {
clearSelection();
}
var selectedGroups = [];
activeNodes.forEach(function(n) {
if (n.z == RED.workspaces.active() && !n.selected) {
if (n.x > 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 }
};
})();

View File

@ -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