You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

648 lines
16KB

  1. /** Modified from original Node-Red source, for audio system visualization
  2. * vim: set ts=4:
  3. * Copyright 2013 IBM Corp.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. **/
  17. RED.nodes = (function() {
  18. var node_defs = {};
  19. var nodes = [];
  20. var configNodes = {};
  21. var links = [];
  22. //var defaultWorkspace;
  23. var workspaces = {};
  24. function registerType(nt,def) {
  25. node_defs[nt] = def;
  26. // TODO: too tightly coupled into palette UI
  27. RED.palette.add(nt,def);
  28. }
  29. function getID() {
  30. var str = (1+Math.random()*4294967295).toString(16);
  31. console.log("getID = " + str);
  32. return str;
  33. }
  34. function checkID(name) {
  35. var i;
  36. for (i=0;i<nodes.length;i++) {
  37. console.log("checkID, nodes[i].id = " + nodes[i].id);
  38. if (nodes[i].id == name) return true;
  39. }
  40. /*
  41. for (i in workspaces) {
  42. if (workspaces.hasOwnProperty(i)) { }
  43. }
  44. for (i in configNodes) {
  45. if (configNodes.hasOwnProperty(i)) { }
  46. }
  47. */
  48. return false;
  49. }
  50. function createUniqueCppName(n) {
  51. console.log("getUniqueCppName, n.type=" + n.type + ", n._def.shortName=" + n._def.shortName);
  52. var basename = (n._def.shortName) ? n._def.shortName : n.type.replace(/^Analog/, "");
  53. console.log("getUniqueCppName, using basename=" + basename);
  54. var count = 1;
  55. var sep = /[0-9]$/.test(basename) ? "_" : "";
  56. var name;
  57. while (1) {
  58. name = basename + sep + count;
  59. if (!checkID(name)) break;
  60. count++;
  61. }
  62. console.log("getUniqueCppName, unique name=" + name);
  63. return name;
  64. }
  65. function getType(type) {
  66. return node_defs[type];
  67. }
  68. function selectNode(name) {
  69. // window.history.pushState(null, null, window.location.protocol + "//"
  70. // + window.location.host + window.location.pathname + '?info=' + name);
  71. }
  72. function addNode(n) {
  73. if (n._def.category == "config") {
  74. configNodes[n.id] = n;
  75. RED.sidebar.config.refresh();
  76. } else {
  77. n.dirty = true;
  78. nodes.push(n);
  79. var updatedConfigNode = false;
  80. for (var d in n._def.defaults) {
  81. if (n._def.defaults.hasOwnProperty(d)) {
  82. var property = n._def.defaults[d];
  83. if (property.type) {
  84. var type = getType(property.type);
  85. if (type && type.category == "config") {
  86. var configNode = configNodes[n[d]];
  87. if (configNode) {
  88. updatedConfigNode = true;
  89. configNode.users.push(n);
  90. }
  91. }
  92. }
  93. }
  94. }
  95. if (updatedConfigNode) {
  96. RED.sidebar.config.refresh();
  97. }
  98. }
  99. }
  100. function addLink(l) {
  101. links.push(l);
  102. }
  103. /*
  104. function addConfig(c) {
  105. configNodes[c.id] = c;
  106. }
  107. */
  108. function getNode(id) {
  109. if (id in configNodes) {
  110. return configNodes[id];
  111. } else {
  112. for (var n in nodes) {
  113. if (nodes[n].id == id) {
  114. return nodes[n];
  115. }
  116. }
  117. }
  118. return null;
  119. }
  120. function removeNode(id) {
  121. var removedLinks = [];
  122. if (id in configNodes) {
  123. delete configNodes[id];
  124. RED.sidebar.config.refresh();
  125. } else {
  126. var node = getNode(id);
  127. if (node) {
  128. nodes.splice(nodes.indexOf(node),1);
  129. removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
  130. removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); });
  131. }
  132. var updatedConfigNode = false;
  133. for (var d in node._def.defaults) {
  134. if (node._def.defaults.hasOwnProperty(d)) {
  135. var property = node._def.defaults[d];
  136. if (property.type) {
  137. var type = getType(property.type);
  138. if (type && type.category == "config") {
  139. var configNode = configNodes[node[d]];
  140. if (configNode) {
  141. updatedConfigNode = true;
  142. var users = configNode.users;
  143. users.splice(users.indexOf(node),1);
  144. }
  145. }
  146. }
  147. }
  148. }
  149. if (updatedConfigNode) {
  150. RED.sidebar.config.refresh();
  151. }
  152. }
  153. return removedLinks;
  154. }
  155. function removeLink(l) {
  156. var index = links.indexOf(l);
  157. if (index != -1) {
  158. links.splice(index,1);
  159. }
  160. }
  161. function refreshValidation() {
  162. for (var n=0;n<nodes.length;n++) {
  163. RED.editor.validateNode(nodes[n]);
  164. }
  165. }
  166. function addWorkspace(ws) {
  167. workspaces[ws.id] = ws;
  168. }
  169. function getWorkspace(id) {
  170. return workspaces[id];
  171. }
  172. function removeWorkspace(id) {
  173. delete workspaces[id];
  174. var removedNodes = [];
  175. var removedLinks = [];
  176. var n;
  177. for (n=0;n<nodes.length;n++) {
  178. var node = nodes[n];
  179. if (node.z == id) {
  180. removedNodes.push(node);
  181. }
  182. }
  183. for (n=0;n<removedNodes.length;n++) {
  184. var rmlinks = removeNode(removedNodes[n].id);
  185. removedLinks = removedLinks.concat(rmlinks);
  186. }
  187. return {nodes:removedNodes,links:removedLinks};
  188. }
  189. function getAllFlowNodes(node) {
  190. var visited = {};
  191. visited[node.id] = true;
  192. var nns = [node];
  193. var stack = [node];
  194. while(stack.length !== 0) {
  195. var n = stack.shift();
  196. var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
  197. for (var i=0;i<childLinks.length;i++) {
  198. var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
  199. if (!visited[child.id]) {
  200. visited[child.id] = true;
  201. nns.push(child);
  202. stack.push(child);
  203. }
  204. }
  205. }
  206. return nns;
  207. }
  208. /**
  209. * Converts a node to an exportable JSON Object
  210. **/
  211. function convertNode(n, exportCreds) {
  212. exportCreds = exportCreds || false;
  213. var node = {};
  214. node.id = n.id;
  215. node.type = n.type;
  216. for (var d in n._def.defaults) {
  217. if (n._def.defaults.hasOwnProperty(d)) {
  218. node[d] = n[d];
  219. }
  220. }
  221. if(exportCreds && n.credentials) {
  222. node.credentials = {};
  223. for (var cred in n._def.credentials) {
  224. if (n._def.credentials.hasOwnProperty(cred)) {
  225. if (n.credentials[cred] != null) {
  226. node.credentials[cred] = n.credentials[cred];
  227. }
  228. }
  229. }
  230. }
  231. if (n._def.category != "config") {
  232. node.x = n.x;
  233. node.y = n.y;
  234. node.z = n.z;
  235. node.wires = [];
  236. for(var i=0;i<n.outputs;i++) {
  237. node.wires.push([]);
  238. }
  239. var wires = links.filter(function(d){return d.source === n;});
  240. for (var j=0;j<wires.length;j++) {
  241. var w = wires[j];
  242. node.wires[w.sourcePort].push(w.target.id + ":" + w.targetPort);
  243. }
  244. }
  245. return node;
  246. }
  247. /**
  248. * Converts the current node selection to an exportable JSON Object
  249. **/
  250. function createExportableNodeSet(set) {
  251. var nns = [];
  252. var exportedConfigNodes = {};
  253. for (var n=0;n<set.length;n++) {
  254. var node = set[n].n;
  255. var convertedNode = RED.nodes.convertNode(node);
  256. for (var d in node._def.defaults) {
  257. if (node._def.defaults[d].type && node[d] in configNodes) {
  258. var confNode = configNodes[node[d]];
  259. var exportable = getType(node._def.defaults[d].type).exportable;
  260. if ((exportable == null || exportable)) {
  261. if (!(node[d] in exportedConfigNodes)) {
  262. exportedConfigNodes[node[d]] = true;
  263. nns.unshift(RED.nodes.convertNode(confNode));
  264. }
  265. } else {
  266. convertedNode[d] = "";
  267. }
  268. }
  269. }
  270. nns.push(convertedNode);
  271. }
  272. return nns;
  273. }
  274. //TODO: rename this (createCompleteNodeSet)
  275. function createCompleteNodeSet() {
  276. var nns = [];
  277. var i;
  278. for (i in workspaces) {
  279. if (workspaces.hasOwnProperty(i)) {
  280. nns.push(workspaces[i]);
  281. }
  282. }
  283. for (i in configNodes) {
  284. if (configNodes.hasOwnProperty(i)) {
  285. nns.push(convertNode(configNodes[i], true));
  286. }
  287. }
  288. for (i=0;i<nodes.length;i++) {
  289. var node = nodes[i];
  290. nns.push(convertNode(node, true));
  291. }
  292. return nns;
  293. }
  294. /**
  295. * Parses the input string which contains copied code from the Arduino IDE, scans the
  296. * nodes and connections and forms them into a JSON representation which will be
  297. * returned as string.
  298. *
  299. * So the result may directly imported in the localStorage or the import dialog.
  300. */
  301. function cppToJSON(newNodesStr) {
  302. var nodes = [];
  303. var skipped = [];
  304. var cables = [];
  305. const CODE_START = "// GUItool: begin automatically generated code";
  306. const CODE_END = "// GUItool: end automatically generated code";
  307. const NODE_COMMENT = "//";
  308. const NODE_AC = "AudioConnection";
  309. var parseLine = function(line) {
  310. var parts = line.match(/^(\S+)\s(.*)/).slice(1);
  311. var type = $.trim(parts[0]);
  312. line = $.trim(parts[1]) + " ";
  313. var description = "";
  314. var coords = [0, 0];
  315. var conn = [];
  316. parts = line.match(/^([^;]{0,});(.*)/);
  317. if (parts) {
  318. parts = parts.slice(1);
  319. description = $.trim(parts[0]);
  320. coords = $.trim(parts[1]);
  321. parts = coords.match(/^([^\/]{0,})\/\/xy=(.*)/);
  322. if (parts) {
  323. parts = parts.slice(1);
  324. coords = $.trim(parts[1]).split(",");
  325. }
  326. }
  327. switch (type) {
  328. case NODE_AC:
  329. parts = description.match(/^([^\(]*\()([^\)]*)(.*)/);
  330. if (parts) {
  331. conn = $.trim(parts[2]).split(",");
  332. cables.push(conn);
  333. }
  334. break;
  335. case NODE_COMMENT:
  336. // do nothing ...
  337. break;
  338. default:
  339. var node = new Object({
  340. "id": description,
  341. "type": type,
  342. "x": parseInt(coords ? coords[0] : 0),
  343. "y": parseInt(coords ? coords[1] : 0),
  344. "z": 0,
  345. "wires": []
  346. });
  347. // first solution: skip existing id
  348. if (!RED.nodes.node(node.id)) {
  349. nodes.push(node);
  350. } else {
  351. skipped.push(node.id);
  352. }
  353. break;
  354. }
  355. };
  356. var findNode = function(name) {
  357. var len = nodes.length;
  358. for (var key = 0; key < len; key++) {
  359. if (nodes[key].id == name) {
  360. return nodes[key];
  361. }
  362. }
  363. };
  364. var linkCables = function(cables) {
  365. $.each(cables, function(i, item) {
  366. var conn = item;
  367. // when there are only two entries in the array, there
  368. // is only one output to connect to one input, so we have
  369. // to extend the array with the appropriate index "0" for
  370. // both parst (in and out)
  371. if (conn.length == 2) {
  372. conn[2] = conn[1];
  373. conn[1] = conn[3] = 0;
  374. }
  375. // now we assign the outputs (marked by the "idx" of the array)
  376. // to the inputs describend by text
  377. var currNode = findNode($.trim(conn[0]));
  378. var idx = parseInt($.trim(conn[1]));
  379. if (currNode) {
  380. if ($.trim(conn[2]) != "" && $.trim(conn[3]) != "") {
  381. var wire = $.trim(conn[2]) + ":" + $.trim(conn[3]);
  382. var tmp = currNode.wires[idx] ? currNode.wires[idx] : [];
  383. tmp.push(wire);
  384. currNode.wires[idx] = tmp;
  385. }
  386. }
  387. });
  388. };
  389. var traverseLines = function(raw) {
  390. var lines = raw.split("\n");
  391. var useLine = 0;
  392. for (var i = 0; i < lines.length; i++) {
  393. var line = lines[i];
  394. useLine += (line.indexOf(CODE_START) >= 0) ? (useLine ? 0 : 1) : 0;
  395. if (useLine > 0) {
  396. parseLine(line);
  397. }
  398. useLine -= (line.indexOf(CODE_END) >= 0) ? (useLine ? 1 : 0) : 0;
  399. }
  400. };
  401. /*
  402. var readCode = function() {
  403. var fileImport = $("#importInput")[0];
  404. var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.ino|.txt)$/;
  405. if (regex.test(fileImport.value.toLowerCase())) {
  406. if (typeof (FileReader) != "undefined") {
  407. var reader = new FileReader();
  408. $(reader).on("load", function (e) {
  409. });
  410. reader.readAsText(fileImport.files[0]);
  411. } else {
  412. alert("This browser does not support HTML5.");
  413. }
  414. } else {
  415. alert("Please upload a valid INO or text file.");
  416. }
  417. };
  418. */
  419. traverseLines(newNodesStr);
  420. linkCables(cables);
  421. return {
  422. count: nodes.length,
  423. skipped: skipped.length,
  424. data: count > 0 ? JSON.stringify(nodes) : ""
  425. };
  426. }
  427. function importNodes(newNodesObj,createNewIds) {
  428. try {
  429. var i;
  430. var n;
  431. var newNodes;
  432. if (typeof newNodesObj === "string") {
  433. if (newNodesObj === "") {
  434. return;
  435. }
  436. newNodes = JSON.parse(newNodesObj);
  437. } else {
  438. newNodes = newNodesObj;
  439. }
  440. if (!$.isArray(newNodes)) {
  441. newNodes = [newNodes];
  442. }
  443. var unknownTypes = [];
  444. for (i=0;i<newNodes.length;i++) {
  445. n = newNodes[i];
  446. // TODO: remove workspace in next release+1
  447. if (n.type != "workspace" && n.type != "tab" && !getType(n.type)) {
  448. // TODO: get this UI thing out of here! (see below as well)
  449. n.name = n.type;
  450. n.type = "unknown";
  451. if (unknownTypes.indexOf(n.name)==-1) {
  452. unknownTypes.push(n.name);
  453. }
  454. if (n.x == null && n.y == null) {
  455. // config node - remove it
  456. newNodes.splice(i,1);
  457. i--;
  458. }
  459. }
  460. }
  461. /*
  462. if (unknownTypes.length > 0) {
  463. var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
  464. var type = "type"+(unknownTypes.length > 1?"s":"");
  465. RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
  466. //"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");
  467. }
  468. for (i=0;i<newNodes.length;i++) {
  469. n = newNodes[i];
  470. // TODO: remove workspace in next release+1
  471. if (n.type === "workspace" || n.type === "tab") {
  472. if (n.type === "workspace") {
  473. n.type = "tab";
  474. }
  475. if (defaultWorkspace == null) {
  476. defaultWorkspace = n;
  477. }
  478. addWorkspace(n);
  479. RED.view.addWorkspace(n);
  480. }
  481. }
  482. if (defaultWorkspace == null) {
  483. defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
  484. addWorkspace(defaultWorkspace);
  485. RED.view.addWorkspace(defaultWorkspace);
  486. }
  487. */
  488. var node_map = {};
  489. var new_nodes = [];
  490. var new_links = [];
  491. for (i=0;i<newNodes.length;i++) {
  492. n = newNodes[i];
  493. // TODO: remove workspace in next release+1
  494. if (n.type !== "workspace" && n.type !== "tab") {
  495. var def = getType(n.type);
  496. if (def && def.category == "config") {
  497. if (!RED.nodes.node(n.id)) {
  498. var configNode = {id:n.id,type:n.type,users:[]};
  499. for (var d in def.defaults) {
  500. if (def.defaults.hasOwnProperty(d)) {
  501. configNode[d] = n[d];
  502. }
  503. }
  504. configNode.label = def.label;
  505. configNode._def = def;
  506. RED.nodes.add(configNode);
  507. }
  508. } else {
  509. var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
  510. if (createNewIds) {
  511. node.z = RED.view.getWorkspace();
  512. node.id = getID();
  513. } else {
  514. node.id = n.id;
  515. if (node.z == null || !workspaces[node.z]) {
  516. node.z = RED.view.getWorkspace();
  517. }
  518. }
  519. node.type = n.type;
  520. node._def = def;
  521. if (!node._def) {
  522. node._def = {
  523. color:"#fee",
  524. defaults: {},
  525. label: "unknown: "+n.type,
  526. labelStyle: "node_label_italic",
  527. outputs: n.outputs||n.wires.length
  528. }
  529. }
  530. node.outputs = n.outputs||node._def.outputs;
  531. for (var d2 in node._def.defaults) {
  532. if (node._def.defaults.hasOwnProperty(d2)) {
  533. node[d2] = n[d2];
  534. }
  535. }
  536. addNode(node);
  537. RED.editor.validateNode(node);
  538. node_map[n.id] = node;
  539. new_nodes.push(node);
  540. }
  541. }
  542. }
  543. for (i=0;i<new_nodes.length;i++) {
  544. n = new_nodes[i];
  545. for (var w1=0;w1<n.wires.length;w1++) {
  546. var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
  547. for (var w2=0;w2<wires.length;w2++) {
  548. var parts = wires[w2].split(":");
  549. if (parts.length == 2 && parts[0] in node_map) {
  550. var dst = node_map[parts[0]];
  551. var link = {source:n,sourcePort:w1,target:dst,targetPort:parts[1]};
  552. addLink(link);
  553. new_links.push(link);
  554. }
  555. }
  556. }
  557. delete n.wires;
  558. }
  559. return [new_nodes,new_links];
  560. } catch(error) {
  561. //TODO: get this UI thing out of here! (see above as well)
  562. RED.notify("<strong>Error</strong>: "+error,"error");
  563. return null;
  564. }
  565. }
  566. return {
  567. registerType: registerType,
  568. getType: getType,
  569. convertNode: convertNode,
  570. selectNode: selectNode,
  571. add: addNode,
  572. addLink: addLink,
  573. remove: removeNode,
  574. removeLink: removeLink,
  575. addWorkspace: addWorkspace,
  576. removeWorkspace: removeWorkspace,
  577. workspace: getWorkspace,
  578. eachNode: function(cb) {
  579. for (var n=0;n<nodes.length;n++) {
  580. cb(nodes[n]);
  581. }
  582. },
  583. eachLink: function(cb) {
  584. for (var l=0;l<links.length;l++) {
  585. cb(links[l]);
  586. }
  587. },
  588. eachConfig: function(cb) {
  589. for (var id in configNodes) {
  590. if (configNodes.hasOwnProperty(id)) {
  591. cb(configNodes[id]);
  592. }
  593. }
  594. },
  595. node: getNode,
  596. cppToJSON: cppToJSON,
  597. import: importNodes,
  598. refreshValidation: refreshValidation,
  599. getAllFlowNodes: getAllFlowNodes,
  600. createExportableNodeSet: createExportableNodeSet,
  601. createCompleteNodeSet: createCompleteNodeSet,
  602. id: getID,
  603. cppName: createUniqueCppName,
  604. nodes: nodes, // TODO: exposed for d3 vis
  605. links: links // TODO: exposed for d3 vis
  606. };
  607. })();