mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #2698 from node-red/import-dupes
Improved handling of importing duplicate subflow/config nodes
This commit is contained in:
commit
576c528573
@ -198,6 +198,8 @@
|
|||||||
"flow_plural": "__count__ flows",
|
"flow_plural": "__count__ flows",
|
||||||
"subflow": "__count__ subflow",
|
"subflow": "__count__ subflow",
|
||||||
"subflow_plural": "__count__ subflows",
|
"subflow_plural": "__count__ subflows",
|
||||||
|
"replacedNodes": "__count__ node replaced",
|
||||||
|
"replacedNodes_plural": "__count__ nodes replaced",
|
||||||
"pasteNodes": "Paste flow json or",
|
"pasteNodes": "Paste flow json or",
|
||||||
"selectFile": "select a file to import",
|
"selectFile": "select a file to import",
|
||||||
"importNodes": "Import nodes",
|
"importNodes": "Import nodes",
|
||||||
@ -230,13 +232,19 @@
|
|||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"import": "Import to",
|
"import": "Import to",
|
||||||
|
"importSelected": "Import selected",
|
||||||
|
"importCopy": "Import copy",
|
||||||
|
"viewNodes": "View nodes...",
|
||||||
"newFlow": "new flow",
|
"newFlow": "new flow",
|
||||||
|
"replace": "replace",
|
||||||
"errors": {
|
"errors": {
|
||||||
"notArray": "Input not a JSON Array",
|
"notArray": "Input not a JSON Array",
|
||||||
"itemNotObject": "Input not a valid flow - item __index__ not a node object",
|
"itemNotObject": "Input not a valid flow - item __index__ not a node object",
|
||||||
"missingId": "Input not a valid flow - item __index__ missing 'id' property",
|
"missingId": "Input not a valid flow - item __index__ missing 'id' property",
|
||||||
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
|
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
|
||||||
}
|
},
|
||||||
|
"conflictNotification1": "Some of the nodes you are importing already exist in your workspace.",
|
||||||
|
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them."
|
||||||
},
|
},
|
||||||
"copyMessagePath": "Path copied",
|
"copyMessagePath": "Path copied",
|
||||||
"copyMessageValue": "Value copied",
|
"copyMessageValue": "Value copied",
|
||||||
|
@ -37,22 +37,38 @@ RED.history = (function() {
|
|||||||
inverseEv.events.push(r);
|
inverseEv.events.push(r);
|
||||||
}
|
}
|
||||||
} else if (ev.t == 'replace') {
|
} else if (ev.t == 'replace') {
|
||||||
inverseEv = {
|
if (ev.complete) {
|
||||||
t: 'replace',
|
// This is a replace of everything. We can short-cut
|
||||||
config: RED.nodes.createCompleteNodeSet(),
|
// the logic by clearing everyting first, then importing
|
||||||
changed: {},
|
// the ev.config.
|
||||||
rev: RED.nodes.version()
|
// Used by RED.diff.mergeDiff
|
||||||
};
|
inverseEv = {
|
||||||
RED.nodes.clear();
|
t: 'replace',
|
||||||
var imported = RED.nodes.import(ev.config);
|
config: RED.nodes.createCompleteNodeSet(),
|
||||||
imported[0].forEach(function(n) {
|
changed: {},
|
||||||
if (ev.changed[n.id]) {
|
rev: RED.nodes.version()
|
||||||
n.changed = true;
|
};
|
||||||
inverseEv.changed[n.id] = true;
|
RED.nodes.clear();
|
||||||
}
|
var imported = RED.nodes.import(ev.config);
|
||||||
})
|
imported.nodes.forEach(function(n) {
|
||||||
|
if (ev.changed[n.id]) {
|
||||||
|
n.changed = true;
|
||||||
|
inverseEv.changed[n.id] = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
RED.nodes.version(ev.rev);
|
RED.nodes.version(ev.rev);
|
||||||
|
} else {
|
||||||
|
var importMap = {};
|
||||||
|
ev.config.forEach(function(n) {
|
||||||
|
importMap[n.id] = "replace";
|
||||||
|
})
|
||||||
|
var importedResult = RED.nodes.import(ev.config,{importMap: importMap})
|
||||||
|
inverseEv = {
|
||||||
|
t: 'replace',
|
||||||
|
config: importedResult.removedNodes
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (ev.t == 'add') {
|
} else if (ev.t == 'add') {
|
||||||
inverseEv = {
|
inverseEv = {
|
||||||
t: "delete",
|
t: "delete",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
RED.nodes = (function() {
|
RED.nodes = (function() {
|
||||||
|
|
||||||
var node_defs = {};
|
var node_defs = {};
|
||||||
var nodes = [];
|
var nodes = {};
|
||||||
var nodeTabMap = {};
|
var nodeTabMap = {};
|
||||||
|
|
||||||
var configNodes = {};
|
var configNodes = {};
|
||||||
@ -189,6 +189,7 @@ RED.nodes = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
function getID() {
|
function getID() {
|
||||||
|
// return Math.floor(Math.random()*15728640 + 1048576).toString(16)
|
||||||
return (1+Math.random()*4294967295).toString(16);
|
return (1+Math.random()*4294967295).toString(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +217,7 @@ RED.nodes = (function() {
|
|||||||
});
|
});
|
||||||
n.i = nextId+1;
|
n.i = nextId+1;
|
||||||
}
|
}
|
||||||
nodes.push(n);
|
nodes[n.id] = n;
|
||||||
if (nodeTabMap[n.z]) {
|
if (nodeTabMap[n.z]) {
|
||||||
nodeTabMap[n.z][n.id] = n;
|
nodeTabMap[n.z][n.id] = n;
|
||||||
} else {
|
} else {
|
||||||
@ -233,12 +234,8 @@ RED.nodes = (function() {
|
|||||||
function getNode(id) {
|
function getNode(id) {
|
||||||
if (id in configNodes) {
|
if (id in configNodes) {
|
||||||
return configNodes[id];
|
return configNodes[id];
|
||||||
} else {
|
} else if (id in nodes) {
|
||||||
for (var n in nodes) {
|
return nodes[id];
|
||||||
if (nodes[n].id == id) {
|
|
||||||
return nodes[n];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -252,58 +249,56 @@ RED.nodes = (function() {
|
|||||||
delete configNodes[id];
|
delete configNodes[id];
|
||||||
RED.events.emit('nodes:remove',node);
|
RED.events.emit('nodes:remove',node);
|
||||||
RED.workspaces.refresh();
|
RED.workspaces.refresh();
|
||||||
} else {
|
} else if (id in nodes) {
|
||||||
node = getNode(id);
|
node = nodes[id];
|
||||||
if (node) {
|
delete nodes[id]
|
||||||
nodes.splice(nodes.indexOf(node),1);
|
if (nodeTabMap[node.z]) {
|
||||||
if (nodeTabMap[node.z]) {
|
delete nodeTabMap[node.z][node.id];
|
||||||
delete nodeTabMap[node.z][node.id];
|
}
|
||||||
}
|
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
removedLinks.forEach(removeLink);
|
||||||
removedLinks.forEach(removeLink);
|
var updatedConfigNode = false;
|
||||||
var updatedConfigNode = false;
|
for (var d in node._def.defaults) {
|
||||||
for (var d in node._def.defaults) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
var property = node._def.defaults[d];
|
||||||
var property = node._def.defaults[d];
|
if (property.type) {
|
||||||
if (property.type) {
|
var type = registry.getNodeType(property.type);
|
||||||
var type = registry.getNodeType(property.type);
|
if (type && type.category == "config") {
|
||||||
if (type && type.category == "config") {
|
var configNode = configNodes[node[d]];
|
||||||
var configNode = configNodes[node[d]];
|
if (configNode) {
|
||||||
if (configNode) {
|
updatedConfigNode = true;
|
||||||
updatedConfigNode = true;
|
if (configNode._def.exclusive) {
|
||||||
if (configNode._def.exclusive) {
|
removeNode(node[d]);
|
||||||
removeNode(node[d]);
|
removedNodes.push(configNode);
|
||||||
removedNodes.push(configNode);
|
} else {
|
||||||
} else {
|
var users = configNode.users;
|
||||||
var users = configNode.users;
|
users.splice(users.indexOf(node),1);
|
||||||
users.splice(users.indexOf(node),1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type.indexOf("subflow:") === 0) {
|
|
||||||
var subflowId = node.type.substring(8);
|
|
||||||
var sf = RED.nodes.subflow(subflowId);
|
|
||||||
if (sf) {
|
|
||||||
sf.instances.splice(sf.instances.indexOf(node),1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedConfigNode) {
|
|
||||||
RED.workspaces.refresh();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (node._def.oneditdelete) {
|
|
||||||
node._def.oneditdelete.call(node);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
console.log("oneditdelete",node.id,node.type,err.toString());
|
|
||||||
}
|
|
||||||
RED.events.emit('nodes:remove',node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.type.indexOf("subflow:") === 0) {
|
||||||
|
var subflowId = node.type.substring(8);
|
||||||
|
var sf = RED.nodes.subflow(subflowId);
|
||||||
|
if (sf) {
|
||||||
|
sf.instances.splice(sf.instances.indexOf(node),1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedConfigNode) {
|
||||||
|
RED.workspaces.refresh();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (node._def.oneditdelete) {
|
||||||
|
node._def.oneditdelete.call(node);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log("oneditdelete",node.id,node.type,err.toString());
|
||||||
|
}
|
||||||
|
RED.events.emit('nodes:remove',node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -377,10 +372,13 @@ RED.nodes = (function() {
|
|||||||
workspacesOrder.splice(workspacesOrder.indexOf(id),1);
|
workspacesOrder.splice(workspacesOrder.indexOf(id),1);
|
||||||
var i;
|
var i;
|
||||||
var node;
|
var node;
|
||||||
for (i=0;i<nodes.length;i++) {
|
// TODO: this should use nodeTabMap
|
||||||
node = nodes[i];
|
for (i in nodes) {
|
||||||
if (node.z == id) {
|
if (nodes.hasOwnProperty(i)) {
|
||||||
removedNodes.push(node);
|
node = nodes[i];
|
||||||
|
if (node.z == id) {
|
||||||
|
removedNodes.push(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(i in configNodes) {
|
for(i in configNodes) {
|
||||||
@ -487,17 +485,19 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function subflowContains(sfid,nodeid) {
|
function subflowContains(sfid,nodeid) {
|
||||||
for (var i=0;i<nodes.length;i++) {
|
for (var i in nodes) {
|
||||||
var node = nodes[i];
|
if (nodes.hasOwnProperty(i)) {
|
||||||
if (node.z === sfid) {
|
var node = nodes[i];
|
||||||
var m = /^subflow:(.+)$/.exec(node.type);
|
if (node.z === sfid) {
|
||||||
if (m) {
|
var m = /^subflow:(.+)$/.exec(node.type);
|
||||||
if (m[1] === nodeid) {
|
if (m) {
|
||||||
return true;
|
if (m[1] === nodeid) {
|
||||||
} else {
|
|
||||||
var result = subflowContains(m[1],nodeid);
|
|
||||||
if (result) {
|
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
var result = subflowContains(m[1],nodeid);
|
||||||
|
if (result) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -737,6 +737,16 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createExportableSubflow(id) {
|
||||||
|
var sf = getSubflow(id);
|
||||||
|
var nodeSet = [sf];
|
||||||
|
var sfNodeIds = Object.keys(nodeTabMap[sf.id]||{});
|
||||||
|
for (var i=0, l=sfNodeIds.length; i<l; i++) {
|
||||||
|
nodeSet.push(nodeTabMap[sf.id][sfNodeIds[i]]);
|
||||||
|
}
|
||||||
|
return createExportableNodeSet(nodeSet);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Converts the current node selection to an exportable JSON Object
|
* Converts the current node selection to an exportable JSON Object
|
||||||
**/
|
**/
|
||||||
@ -826,9 +836,10 @@ RED.nodes = (function() {
|
|||||||
nns.push(convertNode(configNodes[i], exportCredentials));
|
nns.push(convertNode(configNodes[i], exportCredentials));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i=0;i<nodes.length;i++) {
|
for (i in nodes) {
|
||||||
var node = nodes[i];
|
if (nodes.hasOwnProperty(i)) {
|
||||||
nns.push(convertNode(node, exportCredentials));
|
nns.push(convertNode(nodes[i], exportCredentials));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nns;
|
return nns;
|
||||||
}
|
}
|
||||||
@ -897,16 +908,157 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function identifyImportConflicts(importedNodes) {
|
||||||
|
var imported = {
|
||||||
|
tabs: {},
|
||||||
|
subflows: {},
|
||||||
|
groups: {},
|
||||||
|
configs: {},
|
||||||
|
nodes: {},
|
||||||
|
all: [],
|
||||||
|
conflicted: {},
|
||||||
|
zMap: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
importedNodes.forEach(function(n) {
|
||||||
|
imported.all.push(n);
|
||||||
|
if (n.type === "tab") {
|
||||||
|
imported.tabs[n.id] = n;
|
||||||
|
} else if (n.type === "subflow") {
|
||||||
|
imported.subflows[n.id] = n;
|
||||||
|
} else if (n.type === "group") {
|
||||||
|
imported.groups[n.id] = n;
|
||||||
|
} else if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) {
|
||||||
|
imported.nodes[n.id] = n;
|
||||||
|
} else {
|
||||||
|
imported.configs[n.id] = n;
|
||||||
|
}
|
||||||
|
var nodeZ = n.z || "__global__";
|
||||||
|
imported.zMap[nodeZ] = imported.zMap[nodeZ] || [];
|
||||||
|
imported.zMap[nodeZ].push(n)
|
||||||
|
if (nodes[n.id] || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id]) {
|
||||||
|
imported.conflicted[n.id] = n;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return imported;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the provided nodes.
|
||||||
|
* This must contain complete Subflow defs or complete Flow Tabs.
|
||||||
|
* It does not replace an individual node in the middle of a flow.
|
||||||
|
*/
|
||||||
|
function replaceNodes(newNodes) {
|
||||||
|
var zMap = {};
|
||||||
|
var newSubflows = {};
|
||||||
|
var newConfigNodes = {};
|
||||||
|
var removedNodes = [];
|
||||||
|
// Figure out what we're being asked to replace - subflows/configNodes
|
||||||
|
// TODO: config nodes
|
||||||
|
newNodes.forEach(function(n) {
|
||||||
|
if (n.type === "subflow") {
|
||||||
|
newSubflows[n.id] = n;
|
||||||
|
} else if (!n.hasOwnProperty('x') && !n.hasOwnProperty('y')) {
|
||||||
|
newConfigNodes[n.id] = n;
|
||||||
|
}
|
||||||
|
if (n.z) {
|
||||||
|
zMap[n.z] = zMap[n.z] || [];
|
||||||
|
zMap[n.z].push(n);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter out config nodes inside a subflow def that is being replaced
|
||||||
|
var configNodeIds = Object.keys(newConfigNodes);
|
||||||
|
configNodeIds.forEach(function(id) {
|
||||||
|
var n = newConfigNodes[id];
|
||||||
|
if (newSubflows[n.z]) {
|
||||||
|
// This config node is in a subflow to be replaced.
|
||||||
|
// - remove from the list as it'll get handled with the subflow
|
||||||
|
delete newConfigNodes[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Rebuild the list of ids
|
||||||
|
configNodeIds = Object.keys(newConfigNodes);
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Replace subflow definitions
|
||||||
|
//
|
||||||
|
// For each of the subflows to be replaced:
|
||||||
|
var newSubflowIds = Object.keys(newSubflows);
|
||||||
|
newSubflowIds.forEach(function(id) {
|
||||||
|
var n = newSubflows[id];
|
||||||
|
// Get a snapshot of the existing subflow definition
|
||||||
|
removedNodes = removedNodes.concat(createExportableSubflow(id));
|
||||||
|
// Remove the old subflow definition - but leave the instances in place
|
||||||
|
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
||||||
|
// Create the list of nodes for the new subflow def
|
||||||
|
var subflowNodes = [n].concat(zMap[n.id]);
|
||||||
|
// Import the new subflow - no clashes should occur as we've removed
|
||||||
|
// the old version
|
||||||
|
var result = importNodes(subflowNodes);
|
||||||
|
newSubflows[id] = getSubflow(id);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Having replaced the subflow definitions, now need to update the
|
||||||
|
// instance nodes.
|
||||||
|
RED.nodes.eachNode(function(n) {
|
||||||
|
if (/^subflow:/.test(n.type)) {
|
||||||
|
var sfId = n.type.substring(8);
|
||||||
|
if (newSubflows[sfId]) {
|
||||||
|
// This is an instance of one of the replaced subflows
|
||||||
|
// - update the new def's instances array to include this one
|
||||||
|
newSubflows[sfId].instances.push(n);
|
||||||
|
// - update the instance's _def to point to the new def
|
||||||
|
n._def = RED.nodes.getType(n.type);
|
||||||
|
// - set all the flags so the view refreshes properly
|
||||||
|
n.dirty = true;
|
||||||
|
n.changed = true;
|
||||||
|
n._colorChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
newSubflowIds.forEach(function(id) {
|
||||||
|
var n = newSubflows[id];
|
||||||
|
RED.events.emit("subflows:change",n);
|
||||||
|
})
|
||||||
|
// Just in case the imported subflow changed color.
|
||||||
|
RED.utils.clearNodeColorCache();
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Replace config nodes
|
||||||
|
//
|
||||||
|
configNodeIds.forEach(function(id) {
|
||||||
|
removedNodes = removedNodes.concat(convertNode(getNode(id)));
|
||||||
|
removeNode(id);
|
||||||
|
importNodes([newConfigNodes[id]])
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
removedNodes: removedNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options:
|
* Options:
|
||||||
* - generateIds - whether to replace all node ids
|
* - generateIds - whether to replace all node ids
|
||||||
* - addFlow - whether to import nodes to a new tab
|
* - addFlow - whether to import nodes to a new tab
|
||||||
|
* - importMap - how to resolve any conflicts.
|
||||||
|
* - id:import - import as-is
|
||||||
|
* - id:copy - import with new id
|
||||||
|
* - id:replace - import over the top of existing
|
||||||
*/
|
*/
|
||||||
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
|
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
|
||||||
options = options || {
|
options = options || {
|
||||||
generateIds: false,
|
generateIds: false,
|
||||||
addFlow: false
|
addFlow: false,
|
||||||
}
|
}
|
||||||
|
options.importMap = options.importMap || {};
|
||||||
|
|
||||||
var createNewIds = options.generateIds;
|
var createNewIds = options.generateIds;
|
||||||
var createMissingWorkspace = options.addFlow;
|
var createMissingWorkspace = options.addFlow;
|
||||||
var i;
|
var i;
|
||||||
@ -938,14 +1090,44 @@ RED.nodes = (function() {
|
|||||||
// copies of the flow would get loaded at the same time.
|
// copies of the flow would get loaded at the same time.
|
||||||
// If the user hit deploy they would have saved those duplicates.
|
// If the user hit deploy they would have saved those duplicates.
|
||||||
var seenIds = {};
|
var seenIds = {};
|
||||||
|
var existingNodes = [];
|
||||||
|
var nodesToReplace = [];
|
||||||
|
|
||||||
newNodes = newNodes.filter(function(n) {
|
newNodes = newNodes.filter(function(n) {
|
||||||
|
var id = n.id;
|
||||||
if (seenIds[n.id]) {
|
if (seenIds[n.id]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
seenIds[n.id] = true;
|
seenIds[n.id] = true;
|
||||||
|
|
||||||
|
if (!options.generateIds) {
|
||||||
|
if (!options.importMap[id]) {
|
||||||
|
// No conflict resolution for this node
|
||||||
|
if (nodes[id] || configNodes[id] || workspaces[id] || subflows[id] || groups[id]) {
|
||||||
|
existingNodes.push(id);
|
||||||
|
}
|
||||||
|
} else if (options.importMap[id] === "replace") {
|
||||||
|
nodesToReplace.push(n);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (existingNodes.length > 0) {
|
||||||
|
var existingNodesError = new Error();
|
||||||
|
existingNodesError.code = "import_conflict";
|
||||||
|
existingNodesError.importConfig = identifyImportConflicts(newNodes);
|
||||||
|
throw existingNodesError;
|
||||||
|
}
|
||||||
|
var removedNodes;
|
||||||
|
if (nodesToReplace.length > 0) {
|
||||||
|
var replaceResult = replaceNodes(nodesToReplace);
|
||||||
|
removedNodes = replaceResult.removedNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var isInitialLoad = false;
|
var isInitialLoad = false;
|
||||||
if (!initialLoad) {
|
if (!initialLoad) {
|
||||||
isInitialLoad = true;
|
isInitialLoad = true;
|
||||||
@ -954,6 +1136,7 @@ RED.nodes = (function() {
|
|||||||
var unknownTypes = [];
|
var unknownTypes = [];
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
|
var id = n.id;
|
||||||
// TODO: remove workspace in next release+1
|
// TODO: remove workspace in next release+1
|
||||||
if (n.type != "workspace" &&
|
if (n.type != "workspace" &&
|
||||||
n.type != "tab" &&
|
n.type != "tab" &&
|
||||||
@ -1048,7 +1231,7 @@ RED.nodes = (function() {
|
|||||||
if (defaultWorkspace == null) {
|
if (defaultWorkspace == null) {
|
||||||
defaultWorkspace = n;
|
defaultWorkspace = n;
|
||||||
}
|
}
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
nid = getID();
|
nid = getID();
|
||||||
workspace_map[n.id] = nid;
|
workspace_map[n.id] = nid;
|
||||||
n.id = nid;
|
n.id = nid;
|
||||||
@ -1057,12 +1240,15 @@ RED.nodes = (function() {
|
|||||||
RED.workspaces.add(n);
|
RED.workspaces.add(n);
|
||||||
new_workspaces.push(n);
|
new_workspaces.push(n);
|
||||||
} else if (n.type === "subflow") {
|
} else if (n.type === "subflow") {
|
||||||
var matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
|
var matchingSubflow;
|
||||||
|
if (!options.importMap[n.id]) {
|
||||||
|
matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
|
||||||
|
}
|
||||||
if (matchingSubflow) {
|
if (matchingSubflow) {
|
||||||
subflow_denylist[n.id] = matchingSubflow;
|
subflow_denylist[n.id] = matchingSubflow;
|
||||||
} else {
|
} else {
|
||||||
subflow_map[n.id] = n;
|
subflow_map[n.id] = n;
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
nid = getID();
|
nid = getID();
|
||||||
n.id = nid;
|
n.id = nid;
|
||||||
}
|
}
|
||||||
@ -1088,7 +1274,7 @@ RED.nodes = (function() {
|
|||||||
n.status.id = getID();
|
n.status.id = getID();
|
||||||
}
|
}
|
||||||
new_subflows.push(n);
|
new_subflows.push(n);
|
||||||
addSubflow(n,createNewIds);
|
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1108,7 +1294,7 @@ RED.nodes = (function() {
|
|||||||
def = registry.getNodeType(n.type);
|
def = registry.getNodeType(n.type);
|
||||||
if (def && def.category == "config") {
|
if (def && def.category == "config") {
|
||||||
var existingConfigNode = null;
|
var existingConfigNode = null;
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
if (n.z) {
|
if (n.z) {
|
||||||
if (subflow_denylist[n.z]) {
|
if (subflow_denylist[n.z]) {
|
||||||
continue;
|
continue;
|
||||||
@ -1129,23 +1315,24 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
existingConfigNode = RED.nodes.node(n.id);
|
if (options.importMap[n.id] !== "copy") {
|
||||||
if (existingConfigNode) {
|
existingConfigNode = RED.nodes.node(n.id);
|
||||||
if (n.z && existingConfigNode.z !== n.z) {
|
if (existingConfigNode) {
|
||||||
existingConfigNode = null;
|
if (n.z && existingConfigNode.z !== n.z) {
|
||||||
// Check the config nodes on n.z
|
existingConfigNode = null;
|
||||||
for (var cn in configNodes) {
|
// Check the config nodes on n.z
|
||||||
if (configNodes.hasOwnProperty(cn)) {
|
for (var cn in configNodes) {
|
||||||
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
|
if (configNodes.hasOwnProperty(cn)) {
|
||||||
existingConfigNode = configNodes[cn];
|
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
|
||||||
node_map[n.id] = configNodes[cn];
|
existingConfigNode = configNodes[cn];
|
||||||
break;
|
node_map[n.id] = configNodes[cn];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
|
if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
|
||||||
@ -1176,7 +1363,7 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
configNode.label = def.label;
|
configNode.label = def.label;
|
||||||
configNode._def = def;
|
configNode._def = def;
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
configNode.id = getID();
|
configNode.id = getID();
|
||||||
}
|
}
|
||||||
node_map[n.id] = configNode;
|
node_map[n.id] = configNode;
|
||||||
@ -1216,7 +1403,7 @@ RED.nodes = (function() {
|
|||||||
if (n.hasOwnProperty('g')) {
|
if (n.hasOwnProperty('g')) {
|
||||||
node.g = n.g;
|
node.g = n.g;
|
||||||
}
|
}
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
if (subflow_denylist[n.z]) {
|
if (subflow_denylist[n.z]) {
|
||||||
continue;
|
continue;
|
||||||
} else if (subflow_map[node.z]) {
|
} else if (subflow_map[node.z]) {
|
||||||
@ -1265,7 +1452,7 @@ RED.nodes = (function() {
|
|||||||
} else if (n.type.substring(0,7) === "subflow") {
|
} else if (n.type.substring(0,7) === "subflow") {
|
||||||
var parentId = n.type.split(":")[1];
|
var parentId = n.type.split(":")[1];
|
||||||
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
|
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
|
||||||
if (createNewIds) {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
parentId = subflow.id;
|
parentId = subflow.id;
|
||||||
node.type = "subflow:"+parentId;
|
node.type = "subflow:"+parentId;
|
||||||
node._def = registry.getNodeType(node.type);
|
node._def = registry.getNodeType(node.type);
|
||||||
@ -1536,24 +1723,35 @@ RED.nodes = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [new_nodes,new_links,new_groups,new_workspaces,new_subflows,missingWorkspace];
|
return {
|
||||||
|
nodes:new_nodes,
|
||||||
|
links:new_links,
|
||||||
|
groups:new_groups,
|
||||||
|
workspaces:new_workspaces,
|
||||||
|
subflows:new_subflows,
|
||||||
|
missingWorkspace: missingWorkspace,
|
||||||
|
removedNodes: removedNodes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: supports filter.z|type
|
// TODO: supports filter.z|type
|
||||||
function filterNodes(filter) {
|
function filterNodes(filter) {
|
||||||
var result = [];
|
var result = [];
|
||||||
var searchSet = nodes;
|
var searchSet = null;
|
||||||
var doZFilter = false;
|
var doZFilter = false;
|
||||||
if (filter.hasOwnProperty("z")) {
|
if (filter.hasOwnProperty("z")) {
|
||||||
if (Object.hasOwnProperty("values") && nodeTabMap.hasOwnProperty(filter.z) ) {
|
if (nodeTabMap.hasOwnProperty(filter.z)) {
|
||||||
searchSet = Object.values(nodeTabMap[filter.z]);
|
searchSet = Object.keys(nodeTabMap[filter.z]);
|
||||||
} else {
|
} else {
|
||||||
doZFilter = true;
|
doZFilter = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (searchSet === null) {
|
||||||
|
searchSet = Object.keys(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
for (var n=0;n<searchSet.length;n++) {
|
for (var n=0;n<searchSet.length;n++) {
|
||||||
var node = searchSet[n];
|
var node = nodes[searchSet[n]];
|
||||||
if (filter.hasOwnProperty("type") && node.type !== filter.type) {
|
if (filter.hasOwnProperty("type") && node.type !== filter.type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1622,7 +1820,7 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
nodes = [];
|
nodes = {};
|
||||||
links = [];
|
links = [];
|
||||||
nodeTabMap = {};
|
nodeTabMap = {};
|
||||||
configNodes = {};
|
configNodes = {};
|
||||||
@ -1650,7 +1848,7 @@ RED.nodes = (function() {
|
|||||||
RED.events.emit("workspace:clear");
|
RED.events.emit("workspace:clear");
|
||||||
|
|
||||||
// var node_defs = {};
|
// var node_defs = {};
|
||||||
// var nodes = [];
|
// var nodes = {};
|
||||||
// var configNodes = {};
|
// var configNodes = {};
|
||||||
// var links = [];
|
// var links = [];
|
||||||
// var defaultWorkspace;
|
// var defaultWorkspace;
|
||||||
@ -1710,7 +1908,7 @@ RED.nodes = (function() {
|
|||||||
if (configNodes.hasOwnProperty(n.id)) {
|
if (configNodes.hasOwnProperty(n.id)) {
|
||||||
delete configNodes[n.id];
|
delete configNodes[n.id];
|
||||||
} else {
|
} else {
|
||||||
nodes.splice(nodes.indexOf(n),1);
|
delete nodes[n.id];
|
||||||
if (nodeTabMap[n.z]) {
|
if (nodeTabMap[n.z]) {
|
||||||
delete nodeTabMap[n.z][n.id];
|
delete nodeTabMap[n.z][n.id];
|
||||||
}
|
}
|
||||||
@ -1734,7 +1932,7 @@ RED.nodes = (function() {
|
|||||||
RED.view.redraw(true, true);
|
RED.view.redraw(true, true);
|
||||||
var result = importNodes(reimportList,{generateIds:false});
|
var result = importNodes(reimportList,{generateIds:false});
|
||||||
var newNodeMap = {};
|
var newNodeMap = {};
|
||||||
result[0].forEach(function(n) {
|
result.nodes.forEach(function(n) {
|
||||||
newNodeMap[n.id] = n;
|
newNodeMap[n.id] = n;
|
||||||
});
|
});
|
||||||
RED.nodes.eachLink(function(l) {
|
RED.nodes.eachLink(function(l) {
|
||||||
@ -1791,9 +1989,11 @@ RED.nodes = (function() {
|
|||||||
groups: function(z) { return groupsByZ[z]||[] },
|
groups: function(z) { return groupsByZ[z]||[] },
|
||||||
|
|
||||||
eachNode: function(cb) {
|
eachNode: function(cb) {
|
||||||
for (var n=0;n<nodes.length;n++) {
|
for (var id in nodes) {
|
||||||
if (cb(nodes[n]) === false) {
|
if (nodes.hasOwnProperty(id)) {
|
||||||
break;
|
if (cb(nodes[id]) === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1846,6 +2046,8 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
import: importNodes,
|
import: importNodes,
|
||||||
|
|
||||||
|
identifyImportConflicts: identifyImportConflicts,
|
||||||
|
|
||||||
getAllFlowNodes: getAllFlowNodes,
|
getAllFlowNodes: getAllFlowNodes,
|
||||||
createExportableNodeSet: createExportableNodeSet,
|
createExportableNodeSet: createExportableNodeSet,
|
||||||
createCompleteNodeSet: createCompleteNodeSet,
|
createCompleteNodeSet: createCompleteNodeSet,
|
||||||
|
@ -28,6 +28,8 @@ RED.clipboard = (function() {
|
|||||||
var libraryBrowser;
|
var libraryBrowser;
|
||||||
var examplesBrowser;
|
var examplesBrowser;
|
||||||
|
|
||||||
|
var pendingImportConfig;
|
||||||
|
|
||||||
function setupDialogs() {
|
function setupDialogs() {
|
||||||
dialog = $('<div id="red-ui-clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
dialog = $('<div id="red-ui-clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||||
.appendTo("#red-ui-editor")
|
.appendTo("#red-ui-editor")
|
||||||
@ -42,14 +44,14 @@ RED.clipboard = (function() {
|
|||||||
"ui-widget-overlay": "red-ui-editor-dialog"
|
"ui-widget-overlay": "red-ui-editor-dialog"
|
||||||
},
|
},
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{ // red-ui-clipboard-dialog-cancel
|
||||||
id: "red-ui-clipboard-dialog-cancel",
|
id: "red-ui-clipboard-dialog-cancel",
|
||||||
text: RED._("common.label.cancel"),
|
text: RED._("common.label.cancel"),
|
||||||
click: function() {
|
click: function() {
|
||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{ // red-ui-clipboard-dialog-download
|
||||||
id: "red-ui-clipboard-dialog-download",
|
id: "red-ui-clipboard-dialog-download",
|
||||||
class: "primary",
|
class: "primary",
|
||||||
text: RED._("clipboard.download"),
|
text: RED._("clipboard.download"),
|
||||||
@ -64,7 +66,7 @@ RED.clipboard = (function() {
|
|||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{ // red-ui-clipboard-dialog-export
|
||||||
id: "red-ui-clipboard-dialog-export",
|
id: "red-ui-clipboard-dialog-export",
|
||||||
class: "primary",
|
class: "primary",
|
||||||
text: RED._("clipboard.export.copy"),
|
text: RED._("clipboard.export.copy"),
|
||||||
@ -134,7 +136,7 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{ // red-ui-clipboard-dialog-ok
|
||||||
id: "red-ui-clipboard-dialog-ok",
|
id: "red-ui-clipboard-dialog-ok",
|
||||||
class: "primary",
|
class: "primary",
|
||||||
text: RED._("common.label.import"),
|
text: RED._("common.label.import"),
|
||||||
@ -157,6 +159,38 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ // red-ui-clipboard-dialog-import-conflict
|
||||||
|
id: "red-ui-clipboard-dialog-import-conflict",
|
||||||
|
class: "primary",
|
||||||
|
text: RED._("clipboard.import.importSelected"),
|
||||||
|
click: function() {
|
||||||
|
var importMap = {};
|
||||||
|
$('#red-ui-clipboard-dialog-import-conflicts-list input[type="checkbox"]').each(function() {
|
||||||
|
importMap[$(this).attr("data-node-id")] = this.checked?"import":"skip";
|
||||||
|
})
|
||||||
|
|
||||||
|
$('.red-ui-clipboard-dialog-import-conflicts-controls input[type="checkbox"]').each(function() {
|
||||||
|
if (!$(this).attr("disabled")) {
|
||||||
|
importMap[$(this).attr("data-node-id")] = this.checked?"replace":"copy"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// skip - don't import
|
||||||
|
// import - import as-is
|
||||||
|
// copy - import with new id
|
||||||
|
// replace - import over the top of existing
|
||||||
|
pendingImportConfig.importOptions.importMap = importMap;
|
||||||
|
|
||||||
|
var newNodes = pendingImportConfig.importNodes.filter(function(n) {
|
||||||
|
if (!importMap[n.id] || importMap[n.z]) {
|
||||||
|
importMap[n.id] = importMap[n.z];
|
||||||
|
}
|
||||||
|
return importMap[n.id] !== "skip"
|
||||||
|
})
|
||||||
|
// console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
|
||||||
|
RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
open: function( event, ui ) {
|
open: function( event, ui ) {
|
||||||
@ -236,6 +270,14 @@ RED.clipboard = (function() {
|
|||||||
'</span>'+
|
'</span>'+
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
|
importConflictsDialog =
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<div class="form-row"><p data-i18n="clipboard.import.conflictNotification1"></p><p data-i18n="clipboard.import.conflictNotification2"></p></div>'+
|
||||||
|
'<div class="red-ui-clipboard-dialog-import-conflicts-list-container">'+
|
||||||
|
'<div id="red-ui-clipboard-dialog-import-conflicts-list"></div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var validateExportFilenameTimeout
|
var validateExportFilenameTimeout
|
||||||
@ -445,6 +487,8 @@ RED.clipboard = (function() {
|
|||||||
$("#red-ui-clipboard-dialog-cancel").show();
|
$("#red-ui-clipboard-dialog-cancel").show();
|
||||||
$("#red-ui-clipboard-dialog-export").hide();
|
$("#red-ui-clipboard-dialog-export").hide();
|
||||||
$("#red-ui-clipboard-dialog-download").hide();
|
$("#red-ui-clipboard-dialog-download").hide();
|
||||||
|
$("#red-ui-clipboard-dialog-import-conflict").hide();
|
||||||
|
|
||||||
$("#red-ui-clipboard-dialog-ok").button("disable");
|
$("#red-ui-clipboard-dialog-ok").button("disable");
|
||||||
$("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
|
$("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
|
||||||
$("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
|
$("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
|
||||||
@ -485,7 +529,9 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
|
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
|
||||||
|
|
||||||
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
|
dialog.dialog("option","title",RED._("clipboard.importNodes"))
|
||||||
|
.dialog("option","width",700)
|
||||||
|
.dialog("open");
|
||||||
popover = RED.popover.create({
|
popover = RED.popover.create({
|
||||||
target: $("#red-ui-clipboard-dialog-import-text"),
|
target: $("#red-ui-clipboard-dialog-import-text"),
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
@ -631,6 +677,8 @@ RED.clipboard = (function() {
|
|||||||
$("#red-ui-clipboard-dialog-ok").hide();
|
$("#red-ui-clipboard-dialog-ok").hide();
|
||||||
$("#red-ui-clipboard-dialog-cancel").hide();
|
$("#red-ui-clipboard-dialog-cancel").hide();
|
||||||
$("#red-ui-clipboard-dialog-export").hide();
|
$("#red-ui-clipboard-dialog-export").hide();
|
||||||
|
$("#red-ui-clipboard-dialog-import-conflict").hide();
|
||||||
|
|
||||||
var selection = RED.workspaces.selection();
|
var selection = RED.workspaces.selection();
|
||||||
if (selection.length > 0) {
|
if (selection.length > 0) {
|
||||||
$("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
|
$("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
|
||||||
@ -657,12 +705,15 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
|
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
|
||||||
|
|
||||||
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
dialog.dialog("option","title",RED._("clipboard.exportNodes"))
|
||||||
|
.dialog("option","width",700)
|
||||||
|
.dialog("open");
|
||||||
|
|
||||||
$("#red-ui-clipboard-dialog-export-text").trigger("focus");
|
$("#red-ui-clipboard-dialog-export-text").trigger("focus");
|
||||||
$("#red-ui-clipboard-dialog-cancel").show();
|
$("#red-ui-clipboard-dialog-cancel").show();
|
||||||
$("#red-ui-clipboard-dialog-export").show();
|
$("#red-ui-clipboard-dialog-export").show();
|
||||||
$("#red-ui-clipboard-dialog-download").show();
|
$("#red-ui-clipboard-dialog-download").show();
|
||||||
|
$("#red-ui-clipboard-dialog-import-conflict").hide();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,21 +803,297 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
|
|
||||||
function importNodes(nodesStr,addFlow) {
|
function importNodes(nodesStr,addFlow) {
|
||||||
var newNodes;
|
var newNodes = nodesStr;
|
||||||
try {
|
if (typeof nodesStr === 'string') {
|
||||||
nodesStr = nodesStr.trim();
|
try {
|
||||||
if (nodesStr.length === 0) {
|
nodesStr = nodesStr.trim();
|
||||||
return;
|
if (nodesStr.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newNodes = JSON.parse(nodesStr);
|
||||||
|
} catch(err) {
|
||||||
|
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||||
|
e.code = "NODE_RED";
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
newNodes = JSON.parse(nodesStr);
|
}
|
||||||
} catch(err) {
|
var importOptions = {generateIds: false, addFlow: addFlow};
|
||||||
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
try {
|
||||||
e.code = "NODE_RED";
|
RED.view.importNodes(newNodes, importOptions);
|
||||||
throw e;
|
} catch(error) {
|
||||||
|
// Thrown for import_conflict
|
||||||
|
confirmImport(error.importConfig, newNodes, importOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmImport(importConfig,importNodes,importOptions) {
|
||||||
|
var notification = RED.notify("<p>"+RED._("clipboard.import.conflictNotification1")+"</p>",{
|
||||||
|
type: "info",
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{text: RED._("common.label.cancel"), click: function() { notification.close(); }},
|
||||||
|
{text: RED._("clipboard.import.viewNodes"), click: function() {
|
||||||
|
notification.close();
|
||||||
|
showImportConflicts(importConfig,importNodes,importOptions);
|
||||||
|
}},
|
||||||
|
{text: RED._("clipboard.import.importCopy"), click: function() {
|
||||||
|
notification.close();
|
||||||
|
// generateIds=true to avoid conflicts
|
||||||
|
// and default to the 'old' behaviour around matching
|
||||||
|
// config nodes and subflows
|
||||||
|
importOptions.generateIds = true;
|
||||||
|
RED.view.importNodes(importNodes, importOptions);
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function showImportConflicts(importConfig,importNodes,importOptions) {
|
||||||
|
|
||||||
|
pendingImportConfig = {
|
||||||
|
importConfig: importConfig,
|
||||||
|
importNodes: importNodes,
|
||||||
|
importOptions: importOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var id,node;
|
||||||
|
var treeData = [];
|
||||||
|
var container;
|
||||||
|
var addedHeader = false;
|
||||||
|
for (id in importConfig.subflows) {
|
||||||
|
if (importConfig.subflows.hasOwnProperty(id)) {
|
||||||
|
if (!addedHeader) {
|
||||||
|
treeData.push({gutter:$('<span data-i18n="menu.label.subflows"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
|
||||||
|
addedHeader = true;
|
||||||
|
}
|
||||||
|
node = importConfig.subflows[id];
|
||||||
|
var isConflicted = importConfig.conflicted[node.id];
|
||||||
|
var isSelected = !isConflicted;
|
||||||
|
var elements = getNodeElement(node, isConflicted, isSelected );
|
||||||
|
container = {
|
||||||
|
id: node.id,
|
||||||
|
gutter: elements.gutter.element,
|
||||||
|
element: elements.element,
|
||||||
|
class: isSelected?"":"disabled",
|
||||||
|
deferBuild: true,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
treeData.push(container);
|
||||||
|
if (importConfig.zMap[id]) {
|
||||||
|
importConfig.zMap[id].forEach(function(node) {
|
||||||
|
var childElements = getNodeElement(node, importConfig.conflicted[node.id], isSelected, elements.gutter.cb);
|
||||||
|
container.children.push({
|
||||||
|
id: node.id,
|
||||||
|
gutter: childElements.gutter.element,
|
||||||
|
element: childElements.element,
|
||||||
|
class: isSelected?"":"disabled"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addedHeader = false;
|
||||||
|
for (id in importConfig.tabs) {
|
||||||
|
if (importConfig.tabs.hasOwnProperty(id)) {
|
||||||
|
if (!addedHeader) {
|
||||||
|
treeData.push({gutter:$('<span data-i18n="menu.label.flows"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
|
||||||
|
addedHeader = true;
|
||||||
|
}
|
||||||
|
node = importConfig.tabs[id];
|
||||||
|
var isConflicted = importConfig.conflicted[node.id];
|
||||||
|
var isSelected = true;
|
||||||
|
var elements = getNodeElement(node, isConflicted, isSelected);
|
||||||
|
container = {
|
||||||
|
id: node.id,
|
||||||
|
gutter: elements.gutter.element,
|
||||||
|
element: elements.element,
|
||||||
|
icon: "red-ui-icons red-ui-icons-flow",
|
||||||
|
deferBuild: true,
|
||||||
|
class: isSelected?"":"disabled",
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
treeData.push(container);
|
||||||
|
if (importConfig.zMap[id]) {
|
||||||
|
importConfig.zMap[id].forEach(function(node) {
|
||||||
|
var childElements = getNodeElement(node, importConfig.conflicted[node.id], isSelected, elements.gutter.cb);
|
||||||
|
container.children.push({
|
||||||
|
id: node.id,
|
||||||
|
gutter: childElements.gutter.element,
|
||||||
|
element: childElements.element,
|
||||||
|
class: isSelected?"":"disabled"
|
||||||
|
})
|
||||||
|
// console.log(" ["+(importConfig.conflicted[node.id]?"*":" ")+"] "+node.type+" "+node.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addedHeader = false;
|
||||||
|
var extraNodes = [];
|
||||||
|
importConfig.all.forEach(function(node) {
|
||||||
|
if (node.type !== "tab" && node.type !== "subflow" && !importConfig.tabs[node.z] && !importConfig.subflows[node.z]) {
|
||||||
|
var isConflicted = importConfig.conflicted[node.id];
|
||||||
|
var isSelected = !isConflicted || !importConfig.configs[node.id];
|
||||||
|
var elements = getNodeElement(node, isConflicted, isSelected);
|
||||||
|
var item = {
|
||||||
|
id: node.id,
|
||||||
|
gutter: elements.gutter.element,
|
||||||
|
element: elements.element,
|
||||||
|
class: isSelected?"":"disabled"
|
||||||
|
}
|
||||||
|
if (importConfig.configs[node.id]) {
|
||||||
|
extraNodes.push(item);
|
||||||
|
} else {
|
||||||
|
if (!addedHeader) {
|
||||||
|
treeData.push({gutter:$('<span data-i18n="menu.label.nodes"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
|
||||||
|
addedHeader = true;
|
||||||
|
}
|
||||||
|
treeData.push(item);
|
||||||
|
}
|
||||||
|
// console.log("["+(importConfig.conflicted[node.id]?"*":" ")+"] "+node.type+" "+node.id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (extraNodes.length > 0) {
|
||||||
|
treeData.push({gutter:$('<span data-i18n="menu.label.displayConfig"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
|
||||||
|
addedHeader = true;
|
||||||
|
treeData = treeData.concat(extraNodes);
|
||||||
|
|
||||||
RED.view.importNodes(newNodes,{addFlow: addFlow});
|
}
|
||||||
|
dialogContainer.empty();
|
||||||
|
dialogContainer.append($(importConflictsDialog));
|
||||||
|
|
||||||
|
|
||||||
|
var nodeList = $("#red-ui-clipboard-dialog-import-conflicts-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
|
||||||
|
data: treeData
|
||||||
|
})
|
||||||
|
|
||||||
|
dialogContainer.i18n();
|
||||||
|
var dialogHeight = 400;
|
||||||
|
var winHeight = $(window).height();
|
||||||
|
if (winHeight < 600) {
|
||||||
|
dialogHeight = 400 - (600 - winHeight);
|
||||||
|
}
|
||||||
|
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
|
||||||
|
|
||||||
|
$("#red-ui-clipboard-dialog-ok").hide();
|
||||||
|
$("#red-ui-clipboard-dialog-cancel").show();
|
||||||
|
$("#red-ui-clipboard-dialog-export").hide();
|
||||||
|
$("#red-ui-clipboard-dialog-download").hide();
|
||||||
|
$("#red-ui-clipboard-dialog-import-conflict").show();
|
||||||
|
|
||||||
|
|
||||||
|
dialog.dialog("option","title",RED._("clipboard.importNodes"))
|
||||||
|
.dialog("option","width",500)
|
||||||
|
.dialog( "open" );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeElement(n, isConflicted, isSelected, parent) {
|
||||||
|
var element;
|
||||||
|
if (n.type === "tab") {
|
||||||
|
element = getFlowLabel(n, isSelected);
|
||||||
|
} else {
|
||||||
|
element = getNodeLabel(n, isConflicted, isSelected);
|
||||||
|
}
|
||||||
|
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
|
||||||
|
controls.on("click", function(evt) { evt.stopPropagation(); });
|
||||||
|
if (isConflicted && !parent) {
|
||||||
|
var cb = $('<label><input '+(isSelected?'':'disabled ')+'type="checkbox" data-node-id="'+n.id+'"> <span data-i18n="clipboard.import.replace"></span></label>').appendTo(controls);
|
||||||
|
if (n.type === "tab" || (n.type !== "subflow" && n.hasOwnProperty("x") && n.hasOwnProperty("y"))) {
|
||||||
|
cb.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
element: element,
|
||||||
|
gutter: getGutter(n, isSelected, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGutter(n, isSelected, parent) {
|
||||||
|
var span = $("<label>",{class:"red-ui-clipboard-dialog-import-conflicts-gutter"});
|
||||||
|
var cb = $('<input data-node-id="'+n.id+'" type="checkbox" '+(isSelected?"checked":"")+'>').appendTo(span);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
cb.attr("disabled",true);
|
||||||
|
parent.addChild(cb);
|
||||||
|
}
|
||||||
|
span.on("click", function(evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
})
|
||||||
|
cb.on("change", function(evt) {
|
||||||
|
var state = this.checked;
|
||||||
|
span.parent().toggleClass("disabled",!!!state);
|
||||||
|
span.parent().find('.red-ui-clipboard-dialog-import-conflicts-controls input[type="checkbox"]').attr("disabled",!!!state);
|
||||||
|
childItems.forEach(function(c) {
|
||||||
|
c.attr("checked",state);
|
||||||
|
c.trigger("change");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
var childItems = [];
|
||||||
|
|
||||||
|
var checkbox = {
|
||||||
|
addChild: function(c) {
|
||||||
|
childItems.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cb: checkbox,
|
||||||
|
element: span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeLabelText(n) {
|
||||||
|
var label = n.name || n.type+": "+n.id;
|
||||||
|
if (n._def.label) {
|
||||||
|
try {
|
||||||
|
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
|
||||||
|
} catch(err) {
|
||||||
|
console.log("Definition error: "+n.type+".label",err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newlineIndex = label.indexOf("\\n");
|
||||||
|
if (newlineIndex > -1) {
|
||||||
|
label = label.substring(0,newlineIndex)+"...";
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowLabel(n) {
|
||||||
|
n = JSON.parse(JSON.stringify(n));
|
||||||
|
n._def = RED.nodes.getType(n.type) || {};
|
||||||
|
if (n._def) {
|
||||||
|
n._ = n._def._;
|
||||||
|
}
|
||||||
|
|
||||||
|
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
|
||||||
|
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
|
||||||
|
var label = (typeof n === "string")? n : n.label;
|
||||||
|
var newlineIndex = label.indexOf("\\n");
|
||||||
|
if (newlineIndex > -1) {
|
||||||
|
label = label.substring(0,newlineIndex)+"...";
|
||||||
|
}
|
||||||
|
contentDiv.text(label);
|
||||||
|
// A conflicted flow should not be imported by default.
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeLabel(n, isConflicted) {
|
||||||
|
n = JSON.parse(JSON.stringify(n));
|
||||||
|
n._def = RED.nodes.getType(n.type) || {};
|
||||||
|
if (n._def) {
|
||||||
|
n._ = n._def._;
|
||||||
|
}
|
||||||
|
var div = $('<div>',{class:"red-ui-info-outline-item"});
|
||||||
|
RED.utils.createNodeIcon(n).appendTo(div);
|
||||||
|
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
|
||||||
|
var labelText = getNodeLabelText(n);
|
||||||
|
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
|
||||||
|
if (labelText) {
|
||||||
|
label.text(labelText)
|
||||||
|
} else {
|
||||||
|
label.html(n.type)
|
||||||
|
}
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -27,26 +27,26 @@
|
|||||||
this.partialFlag = false;
|
this.partialFlag = false;
|
||||||
this.stateValue = 0;
|
this.stateValue = 0;
|
||||||
var initialState = this.element.prop('checked');
|
var initialState = this.element.prop('checked');
|
||||||
this.options = [
|
this.states = [
|
||||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
|
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
|
||||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
|
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
|
||||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
|
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
|
||||||
];
|
];
|
||||||
if (initialState) {
|
if (initialState) {
|
||||||
this.options[1].show();
|
this.states[1].show();
|
||||||
} else {
|
} else {
|
||||||
this.options[0].show();
|
this.states[0].show();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.element.on("change", function() {
|
this.element.on("change", function() {
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
that.options[0].hide();
|
that.states[0].hide();
|
||||||
that.options[1].show();
|
that.states[1].show();
|
||||||
that.options[2].hide();
|
that.states[2].hide();
|
||||||
} else {
|
} else {
|
||||||
that.options[1].hide();
|
that.states[1].hide();
|
||||||
that.options[0].show();
|
that.states[0].show();
|
||||||
that.options[2].hide();
|
that.states[2].hide();
|
||||||
}
|
}
|
||||||
var isChecked = this.checked;
|
var isChecked = this.checked;
|
||||||
that.children.forEach(function(child) {
|
that.children.forEach(function(child) {
|
||||||
@ -106,17 +106,17 @@
|
|||||||
var trueState = this.partialFlag||state;
|
var trueState = this.partialFlag||state;
|
||||||
this.element.prop('checked',trueState);
|
this.element.prop('checked',trueState);
|
||||||
if (state === true) {
|
if (state === true) {
|
||||||
this.options[0].hide();
|
this.states[0].hide();
|
||||||
this.options[1].show();
|
this.states[1].show();
|
||||||
this.options[2].hide();
|
this.states[2].hide();
|
||||||
} else if (state === false) {
|
} else if (state === false) {
|
||||||
this.options[2].hide();
|
this.states[2].hide();
|
||||||
this.options[1].hide();
|
this.states[1].hide();
|
||||||
this.options[0].show();
|
this.states[0].show();
|
||||||
} else if (state === null) {
|
} else if (state === null) {
|
||||||
this.options[0].hide();
|
this.states[0].hide();
|
||||||
this.options[1].hide();
|
this.states[1].hide();
|
||||||
this.options[2].show();
|
this.states[2].show();
|
||||||
}
|
}
|
||||||
if (!suppressEvent) {
|
if (!suppressEvent) {
|
||||||
this.element.trigger('change',null);
|
this.element.trigger('change',null);
|
||||||
|
@ -91,6 +91,9 @@
|
|||||||
if (v!=="auto" && v!=="") {
|
if (v!=="auto" && v!=="") {
|
||||||
that.topContainer.css(s,v);
|
that.topContainer.css(s,v);
|
||||||
that.uiContainer.css(s,"0");
|
that.uiContainer.css(s,"0");
|
||||||
|
if (s === "top" && that.options.header) {
|
||||||
|
that.uiContainer.css(s,"20px")
|
||||||
|
}
|
||||||
that.element.css(s,'auto');
|
that.element.css(s,'auto');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
invertState = this.options.invertState;
|
invertState = this.options.invertState;
|
||||||
}
|
}
|
||||||
var baseClass = this.options.baseClass || "red-ui-button";
|
var baseClass = this.options.baseClass || "red-ui-button";
|
||||||
var enabledIcon = this.options.enabledIcon || "fa-check-square-o";
|
var enabledIcon = this.options.hasOwnProperty('enabledIcon')?this.options.enabledIcon : "fa-check-square-o";
|
||||||
var disabledIcon = this.options.disabledIcon || "fa-square-o";
|
var disabledIcon = this.options.hasOwnProperty('disabledIcon')?this.options.disabledIcon : "fa-square-o";
|
||||||
var enabledLabel = this.options.hasOwnProperty('enabledLabel') ? this.options.enabledLabel : RED._("editor:workspace.enabled");
|
var enabledLabel = this.options.hasOwnProperty('enabledLabel') ? this.options.enabledLabel : RED._("editor:workspace.enabled");
|
||||||
var disabledLabel = this.options.hasOwnProperty('disabledLabel') ? this.options.disabledLabel : RED._("editor:workspace.disabled");
|
var disabledLabel = this.options.hasOwnProperty('disabledLabel') ? this.options.disabledLabel : RED._("editor:workspace.disabled");
|
||||||
|
|
||||||
@ -46,25 +46,35 @@
|
|||||||
this.element.on("focus", function() {
|
this.element.on("focus", function() {
|
||||||
that.button.focus();
|
that.button.focus();
|
||||||
});
|
});
|
||||||
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"><i class="fa"></i> <span></span></button>');
|
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"><span></span></button>');
|
||||||
|
this.buttonLabel = $("<span>").appendTo(this.button);
|
||||||
|
|
||||||
if (this.options.class) {
|
if (this.options.class) {
|
||||||
this.button.addClass(this.options.class)
|
this.button.addClass(this.options.class)
|
||||||
}
|
}
|
||||||
this.element.after(this.button);
|
this.element.after(this.button);
|
||||||
this.buttonIcon = this.button.find("i");
|
|
||||||
this.buttonLabel = this.button.find("span");
|
if (enabledIcon && disabledIcon) {
|
||||||
|
this.buttonIcon = $('<i class="fa"></i>').prependTo(this.button);
|
||||||
|
}
|
||||||
|
|
||||||
// Quick hack to find the maximum width of the button
|
// Quick hack to find the maximum width of the button
|
||||||
this.button.addClass("selected");
|
this.button.addClass("selected");
|
||||||
this.buttonIcon.addClass(enabledIcon);
|
if (this.buttonIcon) {
|
||||||
|
this.buttonIcon.addClass(enabledIcon);
|
||||||
|
}
|
||||||
this.buttonLabel.text(enabledLabel);
|
this.buttonLabel.text(enabledLabel);
|
||||||
var width = this.button.width();
|
var width = this.button.width();
|
||||||
this.button.removeClass("selected");
|
this.button.removeClass("selected");
|
||||||
this.buttonIcon.removeClass(enabledIcon);
|
if (this.buttonIcon) {
|
||||||
that.buttonIcon.addClass(disabledIcon);
|
this.buttonIcon.removeClass(enabledIcon);
|
||||||
|
that.buttonIcon.addClass(disabledIcon);
|
||||||
|
}
|
||||||
that.buttonLabel.text(disabledLabel);
|
that.buttonLabel.text(disabledLabel);
|
||||||
width = Math.max(width,this.button.width());
|
width = Math.max(width,this.button.width());
|
||||||
this.buttonIcon.removeClass(disabledIcon);
|
if (this.buttonIcon) {
|
||||||
|
this.buttonIcon.removeClass(disabledIcon);
|
||||||
|
}
|
||||||
|
|
||||||
// Fix the width of the button so it doesn't jump around when toggled
|
// Fix the width of the button so it doesn't jump around when toggled
|
||||||
if (width > 0) {
|
if (width > 0) {
|
||||||
@ -73,7 +83,7 @@
|
|||||||
|
|
||||||
this.button.on("click",function(e) {
|
this.button.on("click",function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (that.buttonIcon.hasClass(disabledIcon)) {
|
if (!that.state) {
|
||||||
that.element.prop("checked",!invertState);
|
that.element.prop("checked",!invertState);
|
||||||
} else {
|
} else {
|
||||||
that.element.prop("checked",invertState);
|
that.element.prop("checked",invertState);
|
||||||
@ -84,13 +94,19 @@
|
|||||||
this.element.on("change", function(e) {
|
this.element.on("change", function(e) {
|
||||||
if ($(this).prop("checked") !== invertState) {
|
if ($(this).prop("checked") !== invertState) {
|
||||||
that.button.addClass("selected");
|
that.button.addClass("selected");
|
||||||
that.buttonIcon.addClass(enabledIcon);
|
that.state = true;
|
||||||
that.buttonIcon.removeClass(disabledIcon);
|
if (that.buttonIcon) {
|
||||||
|
that.buttonIcon.addClass(enabledIcon);
|
||||||
|
that.buttonIcon.removeClass(disabledIcon);
|
||||||
|
}
|
||||||
that.buttonLabel.text(enabledLabel);
|
that.buttonLabel.text(enabledLabel);
|
||||||
} else {
|
} else {
|
||||||
that.button.removeClass("selected");
|
that.button.removeClass("selected");
|
||||||
that.buttonIcon.addClass(disabledIcon);
|
that.state = false;
|
||||||
that.buttonIcon.removeClass(enabledIcon);
|
if (that.buttonIcon) {
|
||||||
|
that.buttonIcon.addClass(disabledIcon);
|
||||||
|
that.buttonIcon.removeClass(enabledIcon);
|
||||||
|
}
|
||||||
that.buttonLabel.text(disabledLabel);
|
that.buttonLabel.text(disabledLabel);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -167,7 +167,7 @@
|
|||||||
this._selected = new Set();
|
this._selected = new Set();
|
||||||
this._topList = $('<ol class="red-ui-treeList-list">').css({
|
this._topList = $('<ol class="red-ui-treeList-list">').css({
|
||||||
position:'absolute',
|
position:'absolute',
|
||||||
top: 0,
|
top:0,
|
||||||
left:0,
|
left:0,
|
||||||
right:0,
|
right:0,
|
||||||
bottom:0
|
bottom:0
|
||||||
@ -181,6 +181,9 @@
|
|||||||
that._addSubtree(that._topList,container,item,0);
|
that._addSubtree(that._topList,container,item,0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (this.options.header) {
|
||||||
|
topListOptions.header = this.options.header;
|
||||||
|
}
|
||||||
if (this.options.rootSortable !== false && !!this.options.sortable) {
|
if (this.options.rootSortable !== false && !!this.options.sortable) {
|
||||||
topListOptions.sortable = this.options.sortable;
|
topListOptions.sortable = this.options.sortable;
|
||||||
topListOptions.connectWith = '.red-ui-treeList-sortable';
|
topListOptions.connectWith = '.red-ui-treeList-sortable';
|
||||||
|
@ -453,7 +453,7 @@ RED.subflow = (function() {
|
|||||||
$("#red-ui-workspace-chart").css({"margin-top": "0"});
|
$("#red-ui-workspace-chart").css({"margin-top": "0"});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSubflow(id) {
|
function removeSubflow(id, keepInstanceNodes) {
|
||||||
// TODO: A lot of this logic is common with RED.nodes.removeWorkspace
|
// TODO: A lot of this logic is common with RED.nodes.removeWorkspace
|
||||||
var removedNodes = [];
|
var removedNodes = [];
|
||||||
var removedLinks = [];
|
var removedLinks = [];
|
||||||
@ -462,7 +462,7 @@ RED.subflow = (function() {
|
|||||||
var activeSubflow = RED.nodes.subflow(id);
|
var activeSubflow = RED.nodes.subflow(id);
|
||||||
|
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type == "subflow:"+id) {
|
if (!keepInstanceNodes && n.type == "subflow:"+id) {
|
||||||
removedNodes.push(n);
|
removedNodes.push(n);
|
||||||
}
|
}
|
||||||
if (n.z == id) {
|
if (n.z == id) {
|
||||||
|
@ -79,7 +79,7 @@ RED.sidebar.info.outliner = (function() {
|
|||||||
try {
|
try {
|
||||||
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
|
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log("Definition error: "+type+".label",err);
|
console.log("Definition error: "+n.type+".label",err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var newlineIndex = label.indexOf("\\n");
|
var newlineIndex = label.indexOf("\\n");
|
||||||
|
@ -499,7 +499,7 @@ RED.view = (function() {
|
|||||||
|
|
||||||
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
||||||
RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
|
RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
|
||||||
RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard);});
|
RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
|
||||||
RED.actions.add("core:delete-selection",deleteSelection);
|
RED.actions.add("core:delete-selection",deleteSelection);
|
||||||
RED.actions.add("core:edit-selected-node",editSelection);
|
RED.actions.add("core:edit-selected-node",editSelection);
|
||||||
RED.actions.add("core:undo",RED.history.pop);
|
RED.actions.add("core:undo",RED.history.pop);
|
||||||
@ -3479,7 +3479,7 @@ RED.view = (function() {
|
|||||||
options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}});
|
options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}});
|
||||||
options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
|
options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
|
||||||
options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
|
options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
|
||||||
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,{touchImport:true});}});
|
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
|
||||||
options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
|
options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
|
||||||
options.push({name:"select",onselect:function() {selectAll();}});
|
options.push({name:"select",onselect:function() {selectAll();}});
|
||||||
options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
|
options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
|
||||||
@ -3948,7 +3948,14 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
d.resize = false;
|
d.resize = false;
|
||||||
}
|
}
|
||||||
|
if (d._colorChanged) {
|
||||||
|
var newColor = RED.utils.getNodeColor(d.type,d._def);
|
||||||
|
this.__mainRect__.setAttribute("fill",newColor);
|
||||||
|
if (this.__buttonGroupButton__) {
|
||||||
|
this.__buttonGroupButton__.settAttribute("fill",newColor);
|
||||||
|
}
|
||||||
|
delete d._colorChanged;
|
||||||
|
}
|
||||||
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
|
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
|
||||||
this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
|
this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
|
||||||
// This might be the first redraw after a node has been click-dragged to start a move.
|
// This might be the first redraw after a node has been click-dragged to start a move.
|
||||||
@ -4628,13 +4635,14 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports a new collection of nodes from a JSON String.
|
* Imports a new collection of nodes from a JSON String.
|
||||||
*
|
*
|
||||||
* - all get new IDs assigned
|
* - all get new IDs assigned
|
||||||
* - all "selected"
|
* - all "selected"
|
||||||
* - attached to mouse for placing - "IMPORT_DRAGGING"
|
* - attached to mouse for placing - "IMPORT_DRAGGING"
|
||||||
* @param {String/Array} newNodesStr nodes to import
|
* @param {String/Array} newNodesObj nodes to import
|
||||||
* @param {Object} options options object
|
* @param {Object} options options object
|
||||||
*
|
*
|
||||||
* Options:
|
* Options:
|
||||||
@ -4642,10 +4650,11 @@ RED.view = (function() {
|
|||||||
* - touchImport - whether this is a touch import. If not, imported nodes are
|
* - touchImport - whether this is a touch import. If not, imported nodes are
|
||||||
* attachedto mouse for placing - "IMPORT_DRAGGING" state
|
* attachedto mouse for placing - "IMPORT_DRAGGING" state
|
||||||
*/
|
*/
|
||||||
function importNodes(newNodesStr,options) {
|
function importNodes(newNodesObj,options) {
|
||||||
options = options || {
|
options = options || {
|
||||||
addFlow: false,
|
addFlow: false,
|
||||||
touchImport: false
|
touchImport: false,
|
||||||
|
generateIds: false
|
||||||
}
|
}
|
||||||
var addNewFlow = options.addFlow
|
var addNewFlow = options.addFlow
|
||||||
var touchImport = options.touchImport;
|
var touchImport = options.touchImport;
|
||||||
@ -4653,19 +4662,42 @@ RED.view = (function() {
|
|||||||
if (mouse_mode === RED.state.SELECTING_NODE) {
|
if (mouse_mode === RED.state.SELECTING_NODE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nodesToImport;
|
||||||
|
if (typeof newNodesObj === "string") {
|
||||||
|
if (newNodesObj === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
nodesToImport = JSON.parse(newNodesObj);
|
||||||
|
} catch(err) {
|
||||||
|
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||||
|
e.code = "NODE_RED";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodesToImport = newNodesObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$.isArray(nodesToImport)) {
|
||||||
|
nodesToImport = [nodesToImport];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var activeSubflowChanged;
|
var activeSubflowChanged;
|
||||||
if (activeSubflow) {
|
if (activeSubflow) {
|
||||||
activeSubflowChanged = activeSubflow.changed;
|
activeSubflowChanged = activeSubflow.changed;
|
||||||
}
|
}
|
||||||
var result = RED.nodes.import(newNodesStr,{generateIds:true, addFlow: addNewFlow});
|
var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
|
||||||
if (result) {
|
if (result) {
|
||||||
var new_nodes = result[0];
|
var new_nodes = result.nodes;
|
||||||
var new_links = result[1];
|
var new_links = result.links;
|
||||||
var new_groups = result[2];
|
var new_groups = result.groups;
|
||||||
var new_workspaces = result[3];
|
var new_workspaces = result.workspaces;
|
||||||
var new_subflows = result[4];
|
var new_subflows = result.subflows;
|
||||||
var new_default_workspace = result[5];
|
var removedNodes = result.removedNodes;
|
||||||
|
var new_default_workspace = result.missingWorkspace;
|
||||||
if (addNewFlow && new_default_workspace) {
|
if (addNewFlow && new_default_workspace) {
|
||||||
RED.workspaces.show(new_default_workspace.id);
|
RED.workspaces.show(new_default_workspace.id);
|
||||||
}
|
}
|
||||||
@ -4775,6 +4807,20 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (removedNodes) {
|
||||||
|
var replaceEvent = {
|
||||||
|
t: "replace",
|
||||||
|
config: removedNodes
|
||||||
|
}
|
||||||
|
historyEvent = {
|
||||||
|
t:"multi",
|
||||||
|
events: [
|
||||||
|
replaceEvent,
|
||||||
|
historyEvent
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
|
|
||||||
updateActiveNodes();
|
updateActiveNodes();
|
||||||
@ -4806,6 +4852,9 @@ RED.view = (function() {
|
|||||||
if (new_subflows.length > 0) {
|
if (new_subflows.length > 0) {
|
||||||
counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
|
counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
|
||||||
}
|
}
|
||||||
|
if (removedNodes && removedNodes.length > 0) {
|
||||||
|
counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length}));
|
||||||
|
}
|
||||||
if (counts.length > 0) {
|
if (counts.length > 0) {
|
||||||
var countList = "<ul><li>"+counts.join("</li><li>")+"</li></ul>";
|
var countList = "<ul><li>"+counts.join("</li><li>")+"</li></ul>";
|
||||||
RED.notify("<p>"+RED._("clipboard.nodesImported")+"</p>"+countList,{id:"clipboard"});
|
RED.notify("<p>"+RED._("clipboard.nodesImported")+"</p>"+countList,{id:"clipboard"});
|
||||||
@ -4813,7 +4862,10 @@ RED.view = (function() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
if (error.code != "NODE_RED") {
|
if (error.code === "import_conflict") {
|
||||||
|
// Pass this up for the called to resolve
|
||||||
|
throw error;
|
||||||
|
} else if (error.code != "NODE_RED") {
|
||||||
console.log(error.stack);
|
console.log(error.stack);
|
||||||
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
|
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
|
||||||
} else {
|
} else {
|
||||||
|
@ -153,3 +153,61 @@
|
|||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.red-ui-clipboard-dialog-import-conflicts-list-container {
|
||||||
|
min-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
li:not(:first-child) .red-ui-clipboard-dialog-import-conflicts-item-header {
|
||||||
|
// border-top: 1px solid $secondary-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-clipboard-dialog-import-conflicts-item-header {
|
||||||
|
background: $tertiary-background;
|
||||||
|
& > span:first-child {
|
||||||
|
color: $header-text-color;
|
||||||
|
padding-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-clipboard-dialog-import-conflicts-controls {
|
||||||
|
position: absolute;
|
||||||
|
top:0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0px;
|
||||||
|
text-align: center;
|
||||||
|
color: $form-text-color;
|
||||||
|
.form-row & label {
|
||||||
|
padding: 2px 0;
|
||||||
|
line-height: 23px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 80px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
border-left: 1px solid $secondary-border-color;
|
||||||
|
}
|
||||||
|
input[type="checkbox"] {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#red-ui-clipboard-dialog-import-conflicts-list .disabled .red-ui-info-outline-item {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
.form-row label.red-ui-clipboard-dialog-import-conflicts-gutter {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 22px;
|
||||||
|
text-align: center;
|
||||||
|
.red-ui-editor-dialog & input[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -321,15 +321,12 @@ div.red-ui-info-table {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
.red-ui-treeList-label {
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 2px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.red-ui-info-outline-project {
|
.red-ui-info-outline-project {
|
||||||
border-bottom: 1px solid $secondary-border-color;
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list {
|
||||||
.red-ui-info-outline-item {
|
.red-ui-info-outline-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -366,6 +363,12 @@ div.red-ui-info-table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.red-ui-treeList-label {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 2px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.red-ui-search-result-node {
|
.red-ui-search-result-node {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@ -387,6 +390,7 @@ div.red-ui-info-table {
|
|||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.red-ui-info-outline-item-control-spacer {
|
.red-ui-info-outline-item-control-spacer {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 23px;
|
width: 23px;
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
input {
|
input {
|
||||||
display:none;
|
display:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
Loading…
Reference in New Issue
Block a user