1
0
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:
Nick O'Leary 2020-09-21 18:30:15 +01:00 committed by GitHub
commit 576c528573
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 883 additions and 194 deletions

View File

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

View File

@ -37,6 +37,11 @@ RED.history = (function() {
inverseEv.events.push(r); inverseEv.events.push(r);
} }
} else if (ev.t == 'replace') { } else if (ev.t == 'replace') {
if (ev.complete) {
// This is a replace of everything. We can short-cut
// the logic by clearing everyting first, then importing
// the ev.config.
// Used by RED.diff.mergeDiff
inverseEv = { inverseEv = {
t: 'replace', t: 'replace',
config: RED.nodes.createCompleteNodeSet(), config: RED.nodes.createCompleteNodeSet(),
@ -45,7 +50,7 @@ RED.history = (function() {
}; };
RED.nodes.clear(); RED.nodes.clear();
var imported = RED.nodes.import(ev.config); var imported = RED.nodes.import(ev.config);
imported[0].forEach(function(n) { imported.nodes.forEach(function(n) {
if (ev.changed[n.id]) { if (ev.changed[n.id]) {
n.changed = true; n.changed = true;
inverseEv.changed[n.id] = true; inverseEv.changed[n.id] = true;
@ -53,6 +58,17 @@ RED.history = (function() {
}) })
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",

View File

@ -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,10 +249,9 @@ 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];
} }
@ -304,7 +300,6 @@ RED.nodes = (function() {
} }
RED.events.emit('nodes:remove',node); RED.events.emit('nodes:remove',node);
} }
}
@ -377,12 +372,15 @@ 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
for (i in nodes) {
if (nodes.hasOwnProperty(i)) {
node = nodes[i]; node = nodes[i];
if (node.z == id) { if (node.z == id) {
removedNodes.push(node); removedNodes.push(node);
} }
} }
}
for(i in configNodes) { for(i in configNodes) {
if (configNodes.hasOwnProperty(i)) { if (configNodes.hasOwnProperty(i)) {
node = configNodes[i]; node = configNodes[i];
@ -487,7 +485,8 @@ RED.nodes = (function() {
} }
function subflowContains(sfid,nodeid) { function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) { for (var i in nodes) {
if (nodes.hasOwnProperty(i)) {
var node = nodes[i]; var node = nodes[i];
if (node.z === sfid) { if (node.z === sfid) {
var m = /^subflow:(.+)$/.exec(node.type); var m = /^subflow:(.+)$/.exec(node.type);
@ -503,6 +502,7 @@ RED.nodes = (function() {
} }
} }
} }
}
return false; return false;
} }
@ -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,6 +1315,7 @@ RED.nodes = (function() {
} }
} }
} }
if (options.importMap[n.id] !== "copy") {
existingConfigNode = RED.nodes.node(n.id); existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) { if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) { if (n.z && existingConfigNode.z !== n.z) {
@ -1145,7 +1332,7 @@ RED.nodes = (function() {
} }
} }
} }
}
} }
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,11 +1989,13 @@ 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)) {
if (cb(nodes[id]) === false) {
break; break;
} }
} }
}
}, },
eachLink: function(cb) { eachLink: function(cb) {
for (var l=0;l<links.length;l++) { for (var l=0;l<links.length;l++) {
@ -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,

View File

@ -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,7 +803,8 @@ RED.clipboard = (function() {
function importNodes(nodesStr,addFlow) { function importNodes(nodesStr,addFlow) {
var newNodes; var newNodes = nodesStr;
if (typeof nodesStr === 'string') {
try { try {
nodesStr = nodesStr.trim(); nodesStr = nodesStr.trim();
if (nodesStr.length === 0) { if (nodesStr.length === 0) {
@ -764,9 +816,284 @@ RED.clipboard = (function() {
e.code = "NODE_RED"; e.code = "NODE_RED";
throw e; throw e;
} }
}
var importOptions = {generateIds: false, addFlow: addFlow};
try {
RED.view.importNodes(newNodes, importOptions);
} 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);
}
dialogContainer.empty();
dialogContainer.append($(importConflictsDialog));
RED.view.importNodes(newNodes,{addFlow: addFlow}); 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 {

View File

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

View File

@ -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');
} }
}) })

View File

@ -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");
if (this.buttonIcon) {
this.buttonIcon.addClass(enabledIcon); 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");
if (this.buttonIcon) {
this.buttonIcon.removeClass(enabledIcon); this.buttonIcon.removeClass(enabledIcon);
that.buttonIcon.addClass(disabledIcon); 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());
if (this.buttonIcon) {
this.buttonIcon.removeClass(disabledIcon); 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.state = true;
if (that.buttonIcon) {
that.buttonIcon.addClass(enabledIcon); that.buttonIcon.addClass(enabledIcon);
that.buttonIcon.removeClass(disabledIcon); that.buttonIcon.removeClass(disabledIcon);
}
that.buttonLabel.text(enabledLabel); that.buttonLabel.text(enabledLabel);
} else { } else {
that.button.removeClass("selected"); that.button.removeClass("selected");
that.state = false;
if (that.buttonIcon) {
that.buttonIcon.addClass(disabledIcon); that.buttonIcon.addClass(disabledIcon);
that.buttonIcon.removeClass(enabledIcon); that.buttonIcon.removeClass(enabledIcon);
}
that.buttonLabel.text(disabledLabel); that.buttonLabel.text(disabledLabel);
} }
}) })

View File

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

View File

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

View File

@ -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");

View File

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

View File

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

View File

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

View File

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