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

1390 lines
52 KiB
JavaScript
Raw Normal View History

2013-09-05 16:02:48 +02:00
/**
* Copyright JS Foundation and other contributors, http://js.foundation
2013-09-05 16:02:48 +02:00
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
2014-08-08 01:01:35 +02:00
RED.nodes = (function() {
2013-09-05 16:02:48 +02:00
var node_defs = {};
var nodes = [];
var configNodes = {};
var links = [];
2013-10-25 22:34:00 +02:00
var defaultWorkspace;
var workspaces = {};
2016-05-04 16:22:30 +02:00
var workspacesOrder =[];
2014-02-25 00:35:11 +01:00
var subflows = {};
var loadedFlowVersion = null;
2015-06-29 17:12:18 +02:00
2016-12-06 23:37:21 +01:00
var initialLoad;
var dirty = false;
2015-06-29 17:12:18 +02:00
function setDirty(d) {
dirty = d;
RED.events.emit("nodes:change",{dirty:dirty});
}
2015-06-29 17:12:18 +02:00
var registry = (function() {
2016-08-04 17:49:36 +02:00
var moduleList = {};
var nodeList = [];
var nodeSets = {};
var typeToId = {};
var nodeDefinitions = {};
var iconSets = {};
2015-06-29 17:12:18 +02:00
nodeDefinitions['tab'] = {
defaults: {
label: {value:""},
disabled: {value: false},
info: {value: ""}
}
};
var exports = {
setModulePendingUpdated: function(module,version) {
moduleList[module].pending_version = version;
RED.events.emit("registry:module-updated",{module:module,version:version});
},
2016-08-04 17:49:36 +02:00
getModule: function(module) {
return moduleList[module];
},
getNodeSetForType: function(nodeType) {
return exports.getNodeSet(typeToId[nodeType]);
},
getModuleList: function() {
return moduleList;
},
getNodeList: function() {
return nodeList;
},
getNodeTypes: function() {
return Object.keys(nodeDefinitions);
},
setNodeList: function(list) {
nodeList = [];
for(var i=0;i<list.length;i++) {
var ns = list[i];
exports.addNodeSet(ns);
}
},
addNodeSet: function(ns) {
ns.added = false;
nodeSets[ns.id] = ns;
for (var j=0;j<ns.types.length;j++) {
typeToId[ns.types[j]] = ns.id;
}
nodeList.push(ns);
2016-08-04 17:49:36 +02:00
moduleList[ns.module] = moduleList[ns.module] || {
name:ns.module,
version:ns.version,
local:ns.local,
sets:{}
};
if (ns.pending_version) {
moduleList[ns.module].pending_version = ns.pending_version;
}
2016-08-04 17:49:36 +02:00
moduleList[ns.module].sets[ns.name] = ns;
RED.events.emit("registry:node-set-added",ns);
},
removeNodeSet: function(id) {
var ns = nodeSets[id];
for (var j=0;j<ns.types.length;j++) {
delete typeToId[ns.types[j]];
}
delete nodeSets[id];
for (var i=0;i<nodeList.length;i++) {
2016-08-04 17:49:36 +02:00
if (nodeList[i].id === id) {
nodeList.splice(i,1);
break;
}
}
2016-08-05 14:39:14 +02:00
delete moduleList[ns.module].sets[ns.name];
if (Object.keys(moduleList[ns.module].sets).length === 0) {
2016-08-04 17:49:36 +02:00
delete moduleList[ns.module];
}
RED.events.emit("registry:node-set-removed",ns);
return ns;
},
getNodeSet: function(id) {
return nodeSets[id];
},
enableNodeSet: function(id) {
var ns = nodeSets[id];
ns.enabled = true;
2016-08-04 17:49:36 +02:00
RED.events.emit("registry:node-set-enabled",ns);
},
disableNodeSet: function(id) {
var ns = nodeSets[id];
ns.enabled = false;
2016-08-04 17:49:36 +02:00
RED.events.emit("registry:node-set-disabled",ns);
},
registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def;
def.type = nt;
2014-02-25 00:35:11 +01:00
if (def.category != "subflows") {
2015-05-06 23:14:00 +02:00
def.set = nodeSets[typeToId[nt]];
2014-02-25 00:35:11 +01:00
nodeSets[typeToId[nt]].added = true;
2016-08-04 17:49:36 +02:00
nodeSets[typeToId[nt]].enabled = true;
2015-06-29 17:12:18 +02:00
var ns;
if (def.set.module === "node-red") {
ns = "node-red";
} else {
ns = def.set.id;
}
def["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
2017-01-27 23:36:00 +01:00
var original = args[0];
if (args[0].indexOf(":") === -1) {
args[0] = ns+":"+args[0];
}
2017-01-27 23:36:00 +01:00
var result = RED._.apply(null,args);
if (result === args[0]) {
result = original;
}
return result;
}
2014-02-25 00:35:11 +01:00
// TODO: too tightly coupled into palette UI
}
2016-08-04 17:49:36 +02:00
RED.events.emit("registry:node-type-added",nt);
},
2014-02-25 00:35:11 +01:00
removeNodeType: function(nt) {
if (nt.substring(0,8) != "subflow:") {
2015-07-01 00:42:03 +02:00
// NON-NLS - internal debug message
throw new Error("this api is subflow only. called with:",nt);
2014-02-25 00:35:11 +01:00
}
delete nodeDefinitions[nt];
2016-08-04 17:49:36 +02:00
RED.events.emit("registry:node-type-removed",nt);
2014-02-25 00:35:11 +01:00
},
getNodeType: function(nt) {
return nodeDefinitions[nt];
},
setIconSets: function(sets) {
iconSets = sets;
},
getIconSets: function() {
return iconSets;
}
};
return exports;
})();
2015-06-29 17:12:18 +02:00
2013-10-25 22:34:00 +02:00
function getID() {
return (1+Math.random()*4294967295).toString(16);
}
2013-09-05 16:02:48 +02:00
function addNode(n) {
2015-05-14 20:57:39 +02:00
if (n.type.indexOf("subflow") !== 0) {
n["_"] = n._def._;
2017-02-08 11:25:58 +01:00
} else {
n["_"] = RED._;
2015-05-06 23:14:00 +02:00
}
2013-09-05 16:02:48 +02:00
if (n._def.category == "config") {
configNodes[n.id] = n;
} else {
n.ports = [];
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
if (n.outputs) {
for (var i=0;i<n.outputs;i++) {
n.ports.push(i);
}
}
2013-09-05 16:02:48 +02:00
n.dirty = true;
updateConfigNodeUsers(n);
2014-02-25 00:35:11 +01:00
if (n._def.category == "subflows" && typeof n.i === "undefined") {
var nextId = 0;
RED.nodes.eachNode(function(node) {
nextId = Math.max(nextId,node.i||0);
});
n.i = nextId+1;
}
nodes.push(n);
2013-09-05 16:02:48 +02:00
}
2016-05-30 00:51:20 +02:00
RED.events.emit('nodes:add',n);
2013-09-05 16:02:48 +02:00
}
function addLink(l) {
links.push(l);
}
2013-09-05 16:02:48 +02:00
function getNode(id) {
if (id in configNodes) {
return configNodes[id];
} else {
for (var n in nodes) {
if (nodes[n].id == id) {
return nodes[n];
}
}
}
return null;
}
2013-09-05 16:02:48 +02:00
function removeNode(id) {
var removedLinks = [];
var removedNodes = [];
var node;
2013-09-05 16:02:48 +02:00
if (id in configNodes) {
node = configNodes[id];
2013-09-05 16:02:48 +02:00
delete configNodes[id];
2016-05-30 00:51:20 +02:00
RED.events.emit('nodes:remove',node);
RED.workspaces.refresh();
2013-09-05 16:02:48 +02:00
} else {
node = getNode(id);
2013-09-05 16:02:48 +02:00
if (node) {
nodes.splice(nodes.indexOf(node),1);
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
2014-02-25 00:35:11 +01:00
removedLinks.forEach(function(l) {links.splice(links.indexOf(l), 1); });
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
if (configNode._def.exclusive) {
removeNode(node[d]);
removedNodes.push(configNode);
} else {
var users = configNode.users;
users.splice(users.indexOf(node),1);
}
2014-02-25 00:35:11 +01:00
}
2014-08-08 01:01:35 +02:00
}
}
}
}
2014-02-25 00:35:11 +01:00
if (updatedConfigNode) {
RED.workspaces.refresh();
2014-02-25 00:35:11 +01:00
}
try {
if (node._def.oneditdelete) {
node._def.oneditdelete.call(node);
}
} catch(err) {
console.log("oneditdelete",node.id,node.type,err.toString());
}
2016-05-30 00:51:20 +02:00
RED.events.emit('nodes:remove',node);
}
2013-09-05 16:02:48 +02:00
}
if (node && node._def.onremove) {
// Deprecated: never documented but used by some early nodes
console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report");
node._def.onremove.call(n);
}
return {links:removedLinks,nodes:removedNodes};
2013-09-05 16:02:48 +02:00
}
2013-09-05 16:02:48 +02:00
function removeLink(l) {
var index = links.indexOf(l);
if (index != -1) {
links.splice(index,1);
}
}
2013-10-25 22:34:00 +02:00
function addWorkspace(ws) {
workspaces[ws.id] = ws;
ws._def = RED.nodes.getType('tab');
2016-05-04 16:22:30 +02:00
workspacesOrder.push(ws.id);
2013-10-25 22:34:00 +02:00
}
2013-10-26 23:29:24 +02:00
function getWorkspace(id) {
return workspaces[id];
}
function removeWorkspace(id) {
delete workspaces[id];
2016-05-04 16:22:30 +02:00
workspacesOrder.splice(workspacesOrder.indexOf(id),1);
2013-10-26 23:29:24 +02:00
var removedNodes = [];
var removedLinks = [];
2014-08-08 01:01:35 +02:00
var n;
var node;
2014-08-08 01:01:35 +02:00
for (n=0;n<nodes.length;n++) {
node = nodes[n];
2013-10-26 23:29:24 +02:00
if (node.z == id) {
removedNodes.push(node);
}
}
for(n in configNodes) {
if (configNodes.hasOwnProperty(n)) {
node = configNodes[n];
if (node.z == id) {
removedNodes.push(node);
}
}
}
2014-08-08 01:01:35 +02:00
for (n=0;n<removedNodes.length;n++) {
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
2013-10-26 23:29:24 +02:00
}
return {nodes:removedNodes,links:removedLinks};
}
function addSubflow(sf, createNewIds) {
if (createNewIds) {
var subflowNames = Object.keys(subflows).map(function(sfid) {
return subflows[sfid].name;
});
2015-06-29 17:12:18 +02:00
subflowNames.sort();
var copyNumber = 1;
var subflowName = sf.name;
subflowNames.forEach(function(name) {
if (subflowName == name) {
copyNumber++;
subflowName = sf.name+" ("+copyNumber+")";
}
});
sf.name = subflowName;
}
2014-02-25 00:35:11 +01:00
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
2015-10-23 23:14:21 +02:00
info: sf.info,
2018-03-14 05:51:50 +01:00
icon: function() { return sf.icon||"subflow.png" },
2014-02-25 00:35:11 +01:00
category: "subflows",
inputs: sf.in.length,
outputs: sf.out.length,
color: "#da9",
label: function() { return this.name||RED.nodes.subflow(sf.id).name },
labelStyle: function() { return this.name?"node_label_italic":""; },
2015-06-29 17:12:18 +02:00
paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
2017-02-08 11:25:58 +01:00
inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
2015-06-29 17:12:18 +02:00
set:{
module: "node-red"
}
2014-02-25 00:35:11 +01:00
});
2017-02-08 11:25:58 +01:00
sf._def = RED.nodes.getType("subflow:"+sf.id);
2014-02-25 00:35:11 +01:00
}
function getSubflow(id) {
return subflows[id];
}
function removeSubflow(sf) {
delete subflows[sf.id];
registry.removeNodeType("subflow:"+sf.id);
}
2015-06-29 17:12:18 +02:00
2014-02-25 00:35:11 +01:00
function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
if (node.z === sfid) {
var m = /^subflow:(.+)$/.exec(node.type);
if (m) {
if (m[1] === nodeid) {
return true;
} else {
var result = subflowContains(m[1],nodeid);
if (result) {
return true;
}
}
}
}
}
return false;
}
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
function getAllFlowNodes(node) {
var visited = {};
visited[node.id] = true;
var nns = [node];
var stack = [node];
2014-08-08 01:01:35 +02:00
while(stack.length !== 0) {
2013-09-05 16:02:48 +02:00
var n = stack.shift();
var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
2014-08-08 01:01:35 +02:00
for (var i=0;i<childLinks.length;i++) {
2013-09-05 16:02:48 +02:00
var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
2014-02-25 00:35:11 +01:00
var id = child.id;
if (!id) {
id = child.direction+":"+child.i;
}
if (!visited[id]) {
visited[id] = true;
2013-09-05 16:02:48 +02:00
nns.push(child);
stack.push(child);
}
}
}
return nns;
}
function convertWorkspace(n) {
var node = {};
node.id = n.id;
node.type = n.type;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
}
}
return node;
}
2013-09-05 16:02:48 +02:00
/**
* Converts a node to an exportable JSON Object
2013-09-05 16:02:48 +02:00
**/
function convertNode(n, exportCreds) {
if (n.type === 'tab') {
return convertWorkspace(n);
}
exportCreds = exportCreds || false;
2013-09-05 16:02:48 +02:00
var node = {};
node.id = n.id;
node.type = n.type;
node.z = n.z;
2017-02-06 14:23:42 +01:00
if (node.type == "unknown") {
for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) {
node[p] = n._orig[p];
}
2014-08-08 01:01:35 +02:00
}
} else {
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
}
}
if(exportCreds && n.credentials) {
2014-11-23 23:25:09 +01:00
var credentialSet = {};
node.credentials = {};
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
2014-11-23 23:25:09 +01:00
if (n._def.credentials[cred].type == 'password') {
if (!n.credentials._ ||
n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
2014-11-23 23:25:09 +01:00
(n.credentials["has_"+cred] && n.credentials[cred])) {
credentialSet[cred] = n.credentials[cred];
}
} else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) {
2014-11-23 23:25:09 +01:00
credentialSet[cred] = n.credentials[cred];
}
}
}
2014-11-23 23:25:09 +01:00
if (Object.keys(credentialSet).length > 0) {
node.credentials = credentialSet;
}
}
}
2013-09-05 16:02:48 +02:00
if (n._def.category != "config") {
node.x = n.x;
node.y = n.y;
node.wires = [];
for(var i=0;i<n.outputs;i++) {
node.wires.push([]);
}
var wires = links.filter(function(d){return d.source === n;});
2014-08-08 01:01:35 +02:00
for (var j=0;j<wires.length;j++) {
var w = wires[j];
2014-02-25 00:35:11 +01:00
if (w.target.type != "subflow") {
node.wires[w.sourcePort].push(w.target.id);
}
2013-09-05 16:02:48 +02:00
}
2017-02-06 14:23:42 +01:00
if (n.inputs > 0 && n.inputLabels && !/^\s*$/.test(n.inputLabels.join(""))) {
node.inputLabels = n.inputLabels.slice();
}
if (n.outputs > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) {
node.outputLabels = n.outputLabels.slice();
}
if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("icon")) && n.icon) {
var defIcon = RED.utils.getDefaultNodeIcon(n._def, n);
if (n.icon !== defIcon.module+"/"+defIcon.file) {
node.icon = n.icon;
}
}
2013-09-05 16:02:48 +02:00
}
return node;
}
2014-02-25 00:35:11 +01:00
function convertSubflow(n) {
var node = {};
node.id = n.id;
node.type = n.type;
node.name = n.name;
2015-10-23 23:14:21 +02:00
node.info = n.info;
2014-02-25 00:35:11 +01:00
node.in = [];
node.out = [];
2015-06-29 17:12:18 +02:00
2014-02-25 00:35:11 +01:00
n.in.forEach(function(p) {
var nIn = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.source === p });
for (var i=0;i<wires.length;i++) {
var w = wires[i];
if (w.target.type != "subflow") {
2014-02-25 00:35:11 +01:00
nIn.wires.push({id:w.target.id})
}
}
node.in.push(nIn);
});
n.out.forEach(function(p,c) {
var nOut = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.target === p });
for (i=0;i<wires.length;i++) {
if (wires[i].source.type != "subflow") {
nOut.wires.push({id:wires[i].source.id,port:wires[i].sourcePort})
} else {
nOut.wires.push({id:n.id,port:0})
}
}
node.out.push(nOut);
});
2015-06-29 17:12:18 +02:00
2017-02-08 11:25:58 +01:00
if (node.in.length > 0 && n.inputLabels && !/^\s*$/.test(n.inputLabels.join(""))) {
node.inputLabels = n.inputLabels.slice();
}
if (node.out.length > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) {
node.outputLabels = n.outputLabels.slice();
}
2018-03-14 05:51:50 +01:00
if (n.icon) {
if (n.icon !== "node-red/subflow.png") {
node.icon = n.icon;
}
}
2015-06-29 17:12:18 +02:00
2014-02-25 00:35:11 +01:00
return node;
}
2013-09-05 16:02:48 +02:00
/**
* Converts the current node selection to an exportable JSON Object
**/
function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) {
2013-09-05 16:02:48 +02:00
var nns = [];
exportedConfigNodes = exportedConfigNodes || {};
exportedSubflows = exportedSubflows || {};
2014-08-08 01:01:35 +02:00
for (var n=0;n<set.length;n++) {
var node = set[n];
2014-02-25 00:35:11 +01:00
if (node.type.substring(0,8) == "subflow:") {
var subflowId = node.type.substring(8);
if (!exportedSubflows[subflowId]) {
exportedSubflows[subflowId] = true;
var subflow = getSubflow(subflowId);
var subflowSet = [subflow];
2014-02-25 00:35:11 +01:00
RED.nodes.eachNode(function(n) {
if (n.z == subflowId) {
subflowSet.push(n);
2014-02-25 00:35:11 +01:00
}
});
var exportableSubflow = createExportableNodeSet(subflowSet, exportedSubflows, exportedConfigNodes);
2014-02-25 00:35:11 +01:00
nns = exportableSubflow.concat(nns);
}
}
if (node.type != "subflow") {
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
var confNode = configNodes[node[d]];
var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
if ((exportable == null || exportable)) {
if (!(node[d] in exportedConfigNodes)) {
exportedConfigNodes[node[d]] = true;
set.push(confNode);
2014-02-25 00:35:11 +01:00
}
} else {
convertedNode[d] = "";
2013-09-05 16:02:48 +02:00
}
}
}
2014-02-25 00:35:11 +01:00
nns.push(convertedNode);
} else {
var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow);
2013-09-05 16:02:48 +02:00
}
}
return nns;
}
2013-09-05 16:02:48 +02:00
//TODO: rename this (createCompleteNodeSet)
2016-09-19 14:54:23 +02:00
function createCompleteNodeSet(exportCredentials) {
if (exportCredentials === undefined) {
exportCredentials = true;
}
2013-09-05 16:02:48 +02:00
var nns = [];
2014-08-08 01:01:35 +02:00
var i;
2016-05-04 16:22:30 +02:00
for (i=0;i<workspacesOrder.length;i++) {
if (workspaces[workspacesOrder[i]].type == "tab") {
nns.push(convertWorkspace(workspaces[workspacesOrder[i]]));
2014-02-25 00:35:11 +01:00
}
}
for (i in subflows) {
if (subflows.hasOwnProperty(i)) {
nns.push(convertSubflow(subflows[i]));
2014-08-08 01:01:35 +02:00
}
2013-10-25 22:34:00 +02:00
}
2014-08-08 01:01:35 +02:00
for (i in configNodes) {
if (configNodes.hasOwnProperty(i)) {
2016-09-19 14:54:23 +02:00
nns.push(convertNode(configNodes[i], exportCredentials));
2014-08-08 01:01:35 +02:00
}
2013-09-05 16:02:48 +02:00
}
2014-08-08 01:01:35 +02:00
for (i=0;i<nodes.length;i++) {
2013-09-05 16:02:48 +02:00
var node = nodes[i];
2016-09-19 14:54:23 +02:00
nns.push(convertNode(node, exportCredentials));
2013-09-05 16:02:48 +02:00
}
return nns;
}
function checkForMatchingSubflow(subflow,subflowNodes) {
var i;
var match = null;
try {
RED.nodes.eachSubflow(function(sf) {
if (sf.name != subflow.name ||
sf.info != subflow.info ||
sf.in.length != subflow.in.length ||
sf.out.length != subflow.out.length) {
return;
}
var sfNodes = RED.nodes.filterNodes({z:sf.id});
if (sfNodes.length != subflowNodes.length) {
return;
}
var subflowNodeSet = [subflow].concat(subflowNodes);
var sfNodeSet = [sf].concat(sfNodes);
var exportableSubflowNodes = JSON.stringify(subflowNodeSet);
var exportableSFNodes = JSON.stringify(createExportableNodeSet(sfNodeSet));
var nodeMap = {};
for (i=0;i<sfNodes.length;i++) {
exportableSubflowNodes = exportableSubflowNodes.replace(new RegExp("\""+subflowNodes[i].id+"\"","g"),'"'+sfNodes[i].id+'"');
}
exportableSubflowNodes = exportableSubflowNodes.replace(new RegExp("\""+subflow.id+"\"","g"),'"'+sf.id+'"');
if (exportableSubflowNodes !== exportableSFNodes) {
return;
}
match = sf;
throw new Error();
});
} catch(err) {
console.log(err.stack);
}
return match;
}
function compareNodes(nodeA,nodeB,idMustMatch) {
if (idMustMatch && nodeA.id != nodeB.id) {
return false;
}
if (nodeA.type != nodeB.type) {
return false;
}
var def = nodeA._def;
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
var vA = nodeA[d];
var vB = nodeB[d];
if (typeof vA !== typeof vB) {
return false;
}
if (vA === null || typeof vA === "string" || typeof vA === "number") {
if (vA !== vB) {
return false;
}
} else {
if (JSON.stringify(vA) !== JSON.stringify(vB)) {
return false;
}
}
}
}
return true;
}
2016-09-23 23:02:12 +02:00
function importNodes(newNodesObj,createNewIds,createMissingWorkspace) {
var i;
var n;
var newNodes;
var nodeZmap = {};
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
}
try {
2013-09-05 16:02:48 +02:00
newNodes = JSON.parse(newNodesObj);
} catch(err) {
2015-07-01 00:42:03 +02:00
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED";
throw e;
2013-09-05 16:02:48 +02:00
}
} else {
newNodes = newNodesObj;
}
2013-09-05 16:02:48 +02:00
if (!$.isArray(newNodes)) {
newNodes = [newNodes];
}
var isInitialLoad = false;
2016-12-06 23:37:21 +01:00
if (!initialLoad) {
isInitialLoad = true;
2016-12-06 23:37:21 +01:00
initialLoad = JSON.parse(JSON.stringify(newNodes));
}
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
2015-06-29 17:12:18 +02:00
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type);
2013-09-05 16:02:48 +02:00
}
if (n.z) {
nodeZmap[n.z] = nodeZmap[n.z] || [];
nodeZmap[n.z].push(n);
}
}
if (!isInitialLoad && unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var type = "type"+(unknownTypes.length > 1?"s":"");
2015-07-01 00:42:03 +02:00
RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
}
var activeWorkspace = RED.workspaces.active();
//TODO: check the z of the subflow instance and check _that_ if it exists
var activeSubflow = getSubflow(activeWorkspace);
for (i=0;i<newNodes.length;i++) {
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) {
var subflowId = m[1];
var parent = getSubflow(newNodes[i].z || activeWorkspace);
if (parent) {
var err;
if (subflowId === parent.id) {
2015-07-01 00:42:03 +02:00
err = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
2013-10-25 22:34:00 +02:00
}
if (subflowContains(subflowId,parent.id)) {
2015-07-01 00:42:03 +02:00
err = new Error(RED._("notification.errors.cannotAddCircularReference"));
}
if (err) {
// TODO: standardise error codes
err.code = "NODE_RED";
throw err;
2014-02-25 00:35:11 +01:00
}
}
}
}
2015-06-29 17:12:18 +02:00
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var subflow_blacklist = {};
var node_map = {};
var new_nodes = [];
var new_links = [];
var nid;
var def;
var configNode;
2016-09-23 23:02:12 +02:00
var missingWorkspace = null;
var d;
// Find all tabs and subflow templates
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type === "workspace" || n.type === "tab") {
if (n.type === "workspace") {
n.type = "tab";
}
if (defaultWorkspace == null) {
defaultWorkspace = n;
}
if (createNewIds) {
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
}
addWorkspace(n);
RED.workspaces.add(n);
new_workspaces.push(n);
} else if (n.type === "subflow") {
var matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
if (matchingSubflow) {
subflow_blacklist[n.id] = matchingSubflow;
} else {
subflow_map[n.id] = n;
if (createNewIds) {
nid = getID();
n.id = nid;
}
// TODO: handle createNewIds - map old to new subflow ids
n.in.forEach(function(input,i) {
input.type = "subflow";
input.direction = "in";
input.z = n.id;
input.i = i;
input.id = getID();
});
n.out.forEach(function(output,i) {
output.type = "subflow";
output.direction = "out";
output.z = n.id;
output.i = i;
output.id = getID();
});
new_subflows.push(n);
addSubflow(n,createNewIds);
}
2013-10-25 22:34:00 +02:00
}
}
// Add a tab if there isn't one there already
if (defaultWorkspace == null) {
defaultWorkspace = { type:"tab", id:getID(), disabled: false, info:"", label:RED._('workspace.defaultName',{number:1})};
addWorkspace(defaultWorkspace);
RED.workspaces.add(defaultWorkspace);
new_workspaces.push(defaultWorkspace);
activeWorkspace = RED.workspaces.active();
}
// Find all config nodes and add them
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
var existingConfigNode = null;
if (createNewIds) {
if (n.z) {
if (subflow_blacklist[n.z]) {
continue;
} else if (subflow_map[n.z]) {
n.z = subflow_map[n.z].id;
} else {
n.z = workspace_map[n.z];
if (!workspaces[n.z]) {
2016-09-23 23:02:12 +02:00
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
n.z = missingWorkspace.id;
} else {
n.z = activeWorkspace;
}
}
}
}
existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) {
existingConfigNode = null;
// Check the config nodes on n.z
for (var cn in configNodes) {
if (configNodes.hasOwnProperty(cn)) {
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
existingConfigNode = configNodes[cn];
node_map[n.id] = configNodes[cn];
break;
}
}
}
}
}
}
if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
configNode = {id:n.id, z:n.z, type:n.type, users:[], _config:{}};
for (d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
configNode._config[d] = JSON.stringify(n[d]);
}
}
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
configNode.credentials = {};
for (d in def.credentials) {
if (def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) {
configNode.credentials[d] = n.credentials[d];
}
}
}
configNode.label = def.label;
configNode._def = def;
if (createNewIds) {
configNode.id = getID();
}
node_map[n.id] = configNode;
new_nodes.push(configNode);
RED.nodes.add(configNode);
}
}
}
// Find regular flow nodes and subflow instances
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
def = registry.getNodeType(n.type);
if (!def || def.category != "config") {
2017-02-06 14:23:42 +01:00
var node = {
x:n.x,
y:n.y,
z:n.z,
type:0,
wires:n.wires,
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
2017-02-06 14:23:42 +01:00
changed:false,
_config:{}
};
if (createNewIds) {
if (subflow_blacklist[n.z]) {
continue;
} else if (subflow_map[node.z]) {
node.z = subflow_map[node.z].id;
2013-10-25 22:34:00 +02:00
} else {
node.z = workspace_map[node.z];
if (!workspaces[node.z]) {
2016-09-23 23:02:12 +02:00
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
node.z = missingWorkspace.id;
} else {
node.z = activeWorkspace;
}
2013-10-25 22:34:00 +02:00
}
2013-09-05 16:02:48 +02:00
}
node.id = getID();
} else {
node.id = n.id;
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
2016-09-23 23:02:12 +02:00
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
node.z = missingWorkspace.id;
} else {
node.z = activeWorkspace;
}
}
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
} else {
if (!node._def) {
if (node.x && node.y) {
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length,
set: registry.getNodeSet("node-red/unknown")
}
} else {
node._def = {
category:"config",
set: registry.getNodeSet("node-red/unknown")
};
node.users = [];
2014-02-25 00:35:11 +01:00
}
var orig = {};
for (var p in n) {
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
orig[p] = n[p];
2014-02-25 00:35:11 +01:00
}
2014-08-08 01:01:35 +02:00
}
node._orig = orig;
node.name = n.type;
node.type = "unknown";
2013-09-05 16:02:48 +02:00
}
if (node._def.category != "config") {
node.inputs = n.inputs||node._def.inputs;
node.outputs = n.outputs||node._def.outputs;
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}
}
node._config.x = node.x;
node._config.y = node.y;
if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
node.credentials = {};
for (d in node._def.credentials) {
if (node._def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) {
node.credentials[d] = n.credentials[d];
}
}
}
}
2013-09-05 16:02:48 +02:00
}
addNode(node);
RED.editor.validateNode(node);
node_map[n.id] = node;
if (node._def.category != "config") {
new_nodes.push(node);
2013-09-05 16:02:48 +02:00
}
}
}
}
// TODO: make this a part of the node definition so it doesn't have to
// be hardcoded here
var nodeTypeArrayReferences = {
"catch":"scope",
"status":"scope",
"link in":"links",
"link out":"links"
}
// Remap all wires and config node references
for (i=0;i<new_nodes.length;i++) {
n = new_nodes[i];
if (n.wires) {
for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) {
if (node_map.hasOwnProperty(wires[w2])) {
if (n.z === node_map[wires[w2]].z) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
addLink(link);
new_links.push(link);
} else {
console.log("Warning: dropping link that crosses tabs:",n.id,"->",node_map[wires[w2]].id);
}
}
}
}
delete n.wires;
}
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) {
n[d3] = node_map[n[d3]].id;
configNode = RED.nodes.node(n[d3]);
if (configNode && configNode.users.indexOf(n) === -1) {
configNode.users.push(n);
}
} else if (nodeTypeArrayReferences.hasOwnProperty(n.type) && nodeTypeArrayReferences[n.type] === d3 && n[d3] !== undefined && n[d3] !== null) {
for (var j = 0;j<n[d3].length;j++) {
if (node_map[n[d3][j]]) {
n[d3][j] = node_map[n[d3][j]].id;
}
}
}
}
}
// If importing into a subflow, ensure an outbound-link doesn't
// get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
var otherNode = RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace)
});
}
// With all properties now remapped to point at valid nodes,
// we can validate the node
RED.editor.validateNode(n);
}
for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i];
n.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
addLink(link);
new_links.push(link);
2014-02-25 00:35:11 +01:00
});
delete input.wires;
});
n.out.forEach(function(output) {
output.wires.forEach(function(wire) {
var link;
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
} else {
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
}
addLink(link);
new_links.push(link);
2014-02-25 00:35:11 +01:00
});
delete output.wires;
});
2013-09-05 16:02:48 +02:00
}
2015-06-29 17:12:18 +02:00
RED.workspaces.refresh();
2016-09-23 23:02:12 +02:00
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];
2013-09-05 16:02:48 +02:00
}
2015-06-29 17:12:18 +02:00
// TODO: supports filter.z|type
function filterNodes(filter) {
var result = [];
2015-06-29 17:12:18 +02:00
for (var n=0;n<nodes.length;n++) {
var node = nodes[n];
if (filter.hasOwnProperty("z") && node.z !== filter.z) {
continue;
}
if (filter.hasOwnProperty("type") && node.type !== filter.type) {
continue;
}
result.push(node);
}
return result;
}
function filterLinks(filter) {
var result = [];
2015-06-29 17:12:18 +02:00
for (var n=0;n<links.length;n++) {
var link = links[n];
if (filter.source) {
if (filter.source.hasOwnProperty("id") && link.source.id !== filter.source.id) {
continue;
}
if (filter.source.hasOwnProperty("z") && link.source.z !== filter.source.z) {
continue;
}
}
if (filter.target) {
if (filter.target.hasOwnProperty("id") && link.target.id !== filter.target.id) {
continue;
}
if (filter.target.hasOwnProperty("z") && link.target.z !== filter.target.z) {
continue;
}
}
if (filter.hasOwnProperty("sourcePort") && link.sourcePort !== filter.sourcePort) {
continue;
}
result.push(link);
}
return result;
}
2015-06-29 17:12:18 +02:00
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
function updateConfigNodeUsers(n) {
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
var property = n._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[n[d]];
if (configNode) {
if (configNode.users.indexOf(n) === -1) {
configNode.users.push(n);
}
}
}
}
}
}
}
function flowVersion(version) {
if (version !== undefined) {
loadedFlowVersion = version;
} else {
return loadedFlowVersion;
}
}
function clear() {
nodes = [];
links = [];
configNodes = {};
workspacesOrder = [];
var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id)
});
var workspaceIds = Object.keys(workspaces);
workspaceIds.forEach(function(id) {
RED.workspaces.remove(workspaces[id]);
});
defaultWorkspace = null;
initialLoad = null;
2017-09-20 11:30:07 +02:00
RED.nodes.dirty(false);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
2017-09-20 11:30:07 +02:00
RED.sidebar.info.refresh();
// var node_defs = {};
// var nodes = [];
// var configNodes = {};
// var links = [];
// var defaultWorkspace;
// var workspaces = {};
// var workspacesOrder =[];
// var subflows = {};
// var loadedFlowVersion = null;
}
2013-09-05 16:02:48 +02:00
return {
init: function() {
RED.events.on("registry:node-type-added",function(type) {
var def = registry.getNodeType(type);
var replaced = false;
var replaceNodes = [];
RED.nodes.eachNode(function(n) {
if (n.type === "unknown" && n.name === type) {
replaceNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.type === "unknown" && n.name === type) {
replaceNodes.push(n);
}
});
if (replaceNodes.length > 0) {
var reimportList = [];
replaceNodes.forEach(function(n) {
if (configNodes.hasOwnProperty(n.id)) {
delete configNodes[n.id];
} else {
nodes.splice(nodes.indexOf(n),1);
}
reimportList.push(convertNode(n));
});
RED.view.redraw(true);
var result = importNodes(reimportList,false);
var newNodeMap = {};
result[0].forEach(function(n) {
newNodeMap[n.id] = n;
});
RED.nodes.eachLink(function(l) {
if (newNodeMap.hasOwnProperty(l.source.id)) {
l.source = newNodeMap[l.source.id];
}
if (newNodeMap.hasOwnProperty(l.target.id)) {
l.target = newNodeMap[l.target.id];
}
});
RED.view.redraw(true);
}
});
},
registry:registry,
setNodeList: registry.setNodeList,
2015-06-29 17:12:18 +02:00
getNodeSet: registry.getNodeSet,
addNodeSet: registry.addNodeSet,
removeNodeSet: registry.removeNodeSet,
enableNodeSet: registry.enableNodeSet,
disableNodeSet: registry.disableNodeSet,
2015-06-29 17:12:18 +02:00
setIconSets: registry.setIconSets,
getIconSets: registry.getIconSets,
registerType: registry.registerNodeType,
getType: registry.getNodeType,
2013-09-05 16:02:48 +02:00
convertNode: convertNode,
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
add: addNode,
remove: removeNode,
clear: clear,
2015-06-29 17:12:18 +02:00
2014-02-25 00:35:11 +01:00
addLink: addLink,
2013-09-05 16:02:48 +02:00
removeLink: removeLink,
2015-06-29 17:12:18 +02:00
2013-10-25 22:34:00 +02:00
addWorkspace: addWorkspace,
2013-10-26 23:29:24 +02:00
removeWorkspace: removeWorkspace,
2016-05-04 16:22:30 +02:00
getWorkspaceOrder: function() { return workspacesOrder },
setWorkspaceOrder: function(order) { workspacesOrder = order; },
2013-10-26 23:29:24 +02:00
workspace: getWorkspace,
2015-06-29 17:12:18 +02:00
2014-02-25 00:35:11 +01:00
addSubflow: addSubflow,
removeSubflow: removeSubflow,
subflow: getSubflow,
subflowContains: subflowContains,
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
eachNode: function(cb) {
2014-08-08 01:01:35 +02:00
for (var n=0;n<nodes.length;n++) {
2013-09-05 16:02:48 +02:00
cb(nodes[n]);
}
},
eachLink: function(cb) {
2014-08-08 01:01:35 +02:00
for (var l=0;l<links.length;l++) {
2013-09-05 16:02:48 +02:00
cb(links[l]);
}
},
eachConfig: function(cb) {
for (var id in configNodes) {
2014-08-08 01:01:35 +02:00
if (configNodes.hasOwnProperty(id)) {
cb(configNodes[id]);
}
2013-09-05 16:02:48 +02:00
}
},
2014-02-25 00:35:11 +01:00
eachSubflow: function(cb) {
for (var id in subflows) {
if (subflows.hasOwnProperty(id)) {
cb(subflows[id]);
}
}
},
eachWorkspace: function(cb) {
2016-05-04 16:22:30 +02:00
for (var i=0;i<workspacesOrder.length;i++) {
cb(workspaces[workspacesOrder[i]]);
}
},
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
node: getNode,
2015-06-29 17:12:18 +02:00
version: flowVersion,
2016-12-06 23:37:21 +01:00
originalFlow: function(flow) {
if (flow === undefined) {
return initialLoad;
} else {
initialLoad = flow;
}
},
filterNodes: filterNodes,
filterLinks: filterLinks,
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
import: importNodes,
2015-06-29 17:12:18 +02:00
2013-09-05 16:02:48 +02:00
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,
updateConfigNodeUsers: updateConfigNodeUsers,
2013-10-25 22:34:00 +02:00
id: getID,
dirty: function(d) {
if (d == null) {
return dirty;
} else {
setDirty(d);
}
}
2013-09-05 16:02:48 +02:00
};
2014-08-08 01:01:35 +02:00
})();