blob: dc0827a627b7b13dd3edef52b9ec98ec3dbc7f43 [file] [log] [blame]
Chinthakayala, Sheshashailavas (sc2914)d1569972017-08-28 05:25:46 -09001/**
2 * Copyright 2013 IBM Corp.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 **/
16RED.nodes = (function() {
17
18 var node_defs = {};
19 var nodes = [];
20 var configNodes = {};
21 var links = [];
22 var defaultWorkspace;
23 var workspaces = {};
24
25 var registry = (function() {
26 var nodeList = [];
27 var nodeSets = {};
28 var typeToId = {};
29 var nodeDefinitions = {};
30
31 var exports = {
32 getNodeList: function() {
33 return nodeList;
34 },
35 setNodeList: function(list) {
36 nodeList = [];
37 for(var i=0;i<list.length;i++) {
38 var ns = list[i];
39 exports.addNodeSet(ns);
40 }
41 },
42 addNodeSet: function(ns) {
43 ns.added = false;
44 nodeSets[ns.id] = ns;
45 for (var j=0;j<ns.types.length;j++) {
46 typeToId[ns.types[j]] = ns.id;
47 }
48 nodeList.push(ns);
49 },
50 removeNodeSet: function(id) {
51 var ns = nodeSets[id];
52 for (var j=0;j<ns.types.length;j++) {
53 if (ns.added) {
54 // TODO: too tightly coupled into palette UI
55 RED.palette.remove(ns.types[j]);
56 var def = nodeDefinitions[ns.types[j]];
57 if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
58 def.onpaletteremove.call(def);
59 }
60 }
61 delete typeToId[ns.types[j]];
62 }
63 delete nodeSets[id];
64 for (var i=0;i<nodeList.length;i++) {
65 if (nodeList[i].id == id) {
66 nodeList.splice(i,1);
67 break;
68 }
69 }
70 return ns;
71 },
72 getNodeSet: function(id) {
73 return nodeSets[id];
74 },
75 enableNodeSet: function(id) {
76 var ns = nodeSets[id];
77 ns.enabled = true;
78 for (var j=0;j<ns.types.length;j++) {
79 // TODO: too tightly coupled into palette UI
80 RED.palette.show(ns.types[j]);
81 var def = nodeDefinitions[ns.types[j]];
82 if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
83 def.onpaletteadd.call(def);
84 }
85 }
86 },
87 disableNodeSet: function(id) {
88 var ns = nodeSets[id];
89 ns.enabled = false;
90 for (var j=0;j<ns.types.length;j++) {
91 // TODO: too tightly coupled into palette UI
92 RED.palette.hide(ns.types[j]);
93 var def = nodeDefinitions[ns.types[j]];
94 if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
95 def.onpaletteremove.call(def);
96 }
97 }
98 },
99 registerNodeType: function(nt,def) {
100 nodeDefinitions[nt] = def;
101 nodeSets[typeToId[nt]].added = true;
102 // TODO: too tightly coupled into palette UI
103 RED.palette.add(nt,def);
104 if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
105 def.onpaletteadd.call(def);
106 }
107 },
108 getNodeType: function(nt) {
109 return nodeDefinitions[nt];
110 }
111 };
112 return exports;
113 })();
114
115 function getID() {
116 return (1+Math.random()*4294967295).toString(16);
117 }
118
119 function addNode(n) {
120 if (n._def.category == "config") {
121 configNodes[n.id] = n;
122 RED.sidebar.config.refresh();
123 } else {
124 n.dirty = true;
125 nodes.push(n);
126 var updatedConfigNode = false;
127 for (var d in n._def.defaults) {
128 if (n._def.defaults.hasOwnProperty(d)) {
129 var property = n._def.defaults[d];
130 if (property.type) {
131 var type = registry.getNodeType(property.type);
132 if (type && type.category == "config") {
133 var configNode = configNodes[n[d]];
134 if (configNode) {
135 updatedConfigNode = true;
136 configNode.users.push(n);
137 }
138 }
139 }
140 }
141 }
142 if (updatedConfigNode) {
143 RED.sidebar.config.refresh();
144 }
145 }
146 }
147 function addLink(l) {
148 links.push(l);
149 }
150 function addConfig(c) {
151 configNodes[c.id] = c;
152 }
153
154 function getNode(id) {
155 if (id in configNodes) {
156 return configNodes[id];
157 } else {
158 for (var n in nodes) {
159 if (nodes[n].id == id) {
160 return nodes[n];
161 }
162 }
163 }
164 return null;
165 }
166
167 function removeNode(id) {
168 var removedLinks = [];
169 if (id in configNodes) {
170 delete configNodes[id];
171 RED.sidebar.config.refresh();
172 } else {
173 var node = getNode(id);
174 if (node) {
175 nodes.splice(nodes.indexOf(node),1);
176 removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
177 removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); });
178 }
179 var updatedConfigNode = false;
180 for (var d in node._def.defaults) {
181 if (node._def.defaults.hasOwnProperty(d)) {
182 var property = node._def.defaults[d];
183 if (property.type) {
184 var type = registry.getNodeType(property.type);
185 if (type && type.category == "config") {
186 var configNode = configNodes[node[d]];
187 if (configNode) {
188 updatedConfigNode = true;
189 var users = configNode.users;
190 users.splice(users.indexOf(node),1);
191 }
192 }
193 }
194 }
195 }
196 if (updatedConfigNode) {
197 RED.sidebar.config.refresh();
198 }
199 }
200 return removedLinks;
201 }
202
203 function removeLink(l) {
204 var index = links.indexOf(l);
205 if (index != -1) {
206 links.splice(index,1);
207 }
208 }
209
210 function refreshValidation() {
211 for (var n=0;n<nodes.length;n++) {
212 RED.editor.validateNode(nodes[n]);
213 }
214 }
215
216 function addWorkspace(ws) {
217 workspaces[ws.id] = ws;
218 }
219 function getWorkspace(id) {
220 return workspaces[id];
221 }
222 function removeWorkspace(id) {
223 delete workspaces[id];
224 var removedNodes = [];
225 var removedLinks = [];
226 var n;
227 for (n=0;n<nodes.length;n++) {
228 var node = nodes[n];
229 if (node.z == id) {
230 removedNodes.push(node);
231 }
232 }
233 for (n=0;n<removedNodes.length;n++) {
234 var rmlinks = removeNode(removedNodes[n].id);
235 removedLinks = removedLinks.concat(rmlinks);
236 }
237 return {nodes:removedNodes,links:removedLinks};
238 }
239
240 function getAllFlowNodes(node) {
241 var visited = {};
242 visited[node.id] = true;
243 var nns = [node];
244 var stack = [node];
245 while(stack.length !== 0) {
246 var n = stack.shift();
247 var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
248 for (var i=0;i<childLinks.length;i++) {
249 var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
250 if (!visited[child.id]) {
251 visited[child.id] = true;
252 nns.push(child);
253 stack.push(child);
254 }
255 }
256 }
257 return nns;
258 }
259
260 /**
261 * Converts a node to an exportable JSON Object
262 **/
263 function convertNode(n, exportCreds) {
264 exportCreds = exportCreds || false;
265 var node = {};
266 node.id = n.id;
267 node.type = n.type;
268 for (var d in n._def.defaults) {
269 if (n._def.defaults.hasOwnProperty(d)) {
270 node[d] = n[d];
271 }
272 }
273 if(exportCreds && n.credentials) {
274 node.credentials = {};
275 for (var cred in n._def.credentials) {
276 if (n._def.credentials.hasOwnProperty(cred)) {
277 if (n.credentials[cred] != null) {
278 node.credentials[cred] = n.credentials[cred];
279 }
280 }
281 }
282 }
283 if (n._def.category != "config") {
284 node.x = n.x;
285 node.y = n.y;
286 node.z = n.z;
287 node.wires = [];
288 for(var i=0;i<n.outputs;i++) {
289 node.wires.push([]);
290 }
291 var wires = links.filter(function(d){return d.source === n;});
292 for (var j=0;j<wires.length;j++) {
293 var w = wires[j];
294 node.wires[w.sourcePort].push(w.target.id);
295 }
296 }
297 return node;
298 }
299
300 /**
301 * Converts the current node selection to an exportable JSON Object
302 **/
303 function createExportableNodeSet(set) {
304 var nns = [];
305 var exportedConfigNodes = {};
306 for (var n=0;n<set.length;n++) {
307 var node = set[n].n;
308 var convertedNode = RED.nodes.convertNode(node);
309 for (var d in node._def.defaults) {
310 if (node._def.defaults[d].type && node[d] in configNodes) {
311 var confNode = configNodes[node[d]];
312 var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
313 if ((exportable == null || exportable)) {
314 if (!(node[d] in exportedConfigNodes)) {
315 exportedConfigNodes[node[d]] = true;
316 nns.unshift(RED.nodes.convertNode(confNode));
317 }
318 } else {
319 convertedNode[d] = "";
320 }
321 }
322 }
323
324 nns.push(convertedNode);
325 }
326 return nns;
327 }
328
329 //TODO: rename this (createCompleteNodeSet)
330 function createCompleteNodeSet() {
331 var nns = [];
332 var i;
333 for (i in workspaces) {
334 if (workspaces.hasOwnProperty(i)) {
335 nns.push(workspaces[i]);
336 }
337 }
338 for (i in configNodes) {
339 if (configNodes.hasOwnProperty(i)) {
340 nns.push(convertNode(configNodes[i], true));
341 }
342 }
343 for (i=0;i<nodes.length;i++) {
344 var node = nodes[i];
345 nns.push(convertNode(node, true));
346 }
347 return nns;
348 }
349
350 function importNodes(newNodesObj,createNewIds) {
351 try {
352 var i;
353 var n;
354 var newNodes;
355 if (typeof newNodesObj === "string") {
356 if (newNodesObj === "") {
357 return;
358 }
359 newNodes = JSON.parse(newNodesObj);
360 } else {
361 newNodes = newNodesObj;
362 }
363
364 if (!$.isArray(newNodes)) {
365 newNodes = [newNodes];
366 }
367 var unknownTypes = [];
368 for (i=0;i<newNodes.length;i++) {
369 n = newNodes[i];
370 // TODO: remove workspace in next release+1
371 if (n.type != "workspace" && n.type != "tab" && !registry.getNodeType(n.type)) {
372 // TODO: get this UI thing out of here! (see below as well)
373 n.name = n.type;
374 n.type = "unknown";
375 if (unknownTypes.indexOf(n.name)==-1) {
376 unknownTypes.push(n.name);
377 }
378 if (n.x == null && n.y == null) {
379 // config node - remove it
380 newNodes.splice(i,1);
381 i--;
382 }
383 }
384 }
385 if (unknownTypes.length > 0) {
386 var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
387 var type = "type"+(unknownTypes.length > 1?"s":"");
388 RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
389 //"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
390 }
391
392 var new_workspaces = [];
393 var workspace_map = {};
394
395 for (i=0;i<newNodes.length;i++) {
396 n = newNodes[i];
397 // TODO: remove workspace in next release+1
398 if (n.type === "workspace" || n.type === "tab") {
399 if (n.type === "workspace") {
400 n.type = "tab";
401 }
402 if (defaultWorkspace == null) {
403 defaultWorkspace = n;
404 }
405 if (createNewIds) {
406 var nid = getID();
407 workspace_map[n.id] = nid;
408 n.id = nid;
409 }
410 addWorkspace(n);
411 RED.view.addWorkspace(n);
412 new_workspaces.push(n);
413 }
414 }
415 if (defaultWorkspace == null) {
416 defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
417 addWorkspace(defaultWorkspace);
418 RED.view.addWorkspace(defaultWorkspace);
419 new_workspaces.push(defaultWorkspace);
420 }
421
422 var node_map = {};
423 var new_nodes = [];
424 var new_links = [];
425
426 for (i=0;i<newNodes.length;i++) {
427 n = newNodes[i];
428 // TODO: remove workspace in next release+1
429 if (n.type !== "workspace" && n.type !== "tab") {
430 var def = registry.getNodeType(n.type);
431 if (def && def.category == "config") {
432 if (!RED.nodes.node(n.id)) {
433 var configNode = {id:n.id,type:n.type,users:[]};
434 for (var d in def.defaults) {
435 if (def.defaults.hasOwnProperty(d)) {
436 configNode[d] = n[d];
437 }
438 }
439 configNode.label = def.label;
440 configNode._def = def;
441 RED.nodes.add(configNode);
442 }
443 } else {
444 var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
445 if (createNewIds) {
446 node.z = workspace_map[node.z];
447 if (!workspaces[node.z]) {
448 node.z = RED.view.getWorkspace();
449 }
450 node.id = getID();
451 } else {
452 node.id = n.id;
453 if (node.z == null || !workspaces[node.z]) {
454 node.z = RED.view.getWorkspace();
455 }
456 }
457 node.type = n.type;
458 node._def = def;
459 if (!node._def) {
460 node._def = {
461 color:"#fee",
462 defaults: {},
463 label: "unknown: "+n.type,
464 labelStyle: "node_label_italic",
465 outputs: n.outputs||n.wires.length
466 }
467 }
468 node.outputs = n.outputs||node._def.outputs;
469
470 for (var d2 in node._def.defaults) {
471 if (node._def.defaults.hasOwnProperty(d2)) {
472 node[d2] = n[d2];
473 }
474 }
475
476 addNode(node);
477 RED.editor.validateNode(node);
478 node_map[n.id] = node;
479 new_nodes.push(node);
480 }
481 }
482 }
483 for (i=0;i<new_nodes.length;i++) {
484 n = new_nodes[i];
485 for (var w1=0;w1<n.wires.length;w1++) {
486 var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
487 for (var w2=0;w2<wires.length;w2++) {
488 if (wires[w2] in node_map) {
489 var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
490 addLink(link);
491 new_links.push(link);
492 }
493 }
494 }
495 delete n.wires;
496 }
497 return [new_nodes,new_links,new_workspaces];
498 } catch(error) {
499 //TODO: get this UI thing out of here! (see above as well)
500 RED.notify("<strong>Error</strong>: "+error,"error");
501 return null;
502 }
503
504 }
505
506 return {
507 registry:registry,
508 setNodeList: registry.setNodeList,
509
510 getNodeSet: registry.getNodeSet,
511 addNodeSet: registry.addNodeSet,
512 removeNodeSet: registry.removeNodeSet,
513 enableNodeSet: registry.enableNodeSet,
514 disableNodeSet: registry.disableNodeSet,
515
516 registerType: registry.registerNodeType,
517 getType: registry.getNodeType,
518 convertNode: convertNode,
519 add: addNode,
520 addLink: addLink,
521 remove: removeNode,
522 removeLink: removeLink,
523 addWorkspace: addWorkspace,
524 removeWorkspace: removeWorkspace,
525 workspace: getWorkspace,
526 eachNode: function(cb) {
527 for (var n=0;n<nodes.length;n++) {
528 cb(nodes[n]);
529 }
530 },
531 eachLink: function(cb) {
532 for (var l=0;l<links.length;l++) {
533 cb(links[l]);
534 }
535 },
536 eachConfig: function(cb) {
537 for (var id in configNodes) {
538 if (configNodes.hasOwnProperty(id)) {
539 cb(configNodes[id]);
540 }
541 }
542 },
543 node: getNode,
544 import: importNodes,
545 refreshValidation: refreshValidation,
546 getAllFlowNodes: getAllFlowNodes,
547 createExportableNodeSet: createExportableNodeSet,
548 createCompleteNodeSet: createCompleteNodeSet,
549 id: getID,
550 nodes: nodes, // TODO: exposed for d3 vis
551 links: links // TODO: exposed for d3 vis
552 };
553})();