Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

nodes.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  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. function addConfig(c) {
  104. configNodes[c.id] = c;
  105. }
  106. function getNode(id) {
  107. if (id in configNodes) {
  108. return configNodes[id];
  109. } else {
  110. for (var n in nodes) {
  111. if (nodes[n].id == id) {
  112. return nodes[n];
  113. }
  114. }
  115. }
  116. return null;
  117. }
  118. function removeNode(id) {
  119. var removedLinks = [];
  120. if (id in configNodes) {
  121. delete configNodes[id];
  122. RED.sidebar.config.refresh();
  123. } else {
  124. var node = getNode(id);
  125. if (node) {
  126. nodes.splice(nodes.indexOf(node),1);
  127. removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
  128. removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); });
  129. }
  130. var updatedConfigNode = false;
  131. for (var d in node._def.defaults) {
  132. if (node._def.defaults.hasOwnProperty(d)) {
  133. var property = node._def.defaults[d];
  134. if (property.type) {
  135. var type = getType(property.type)
  136. if (type && type.category == "config") {
  137. var configNode = configNodes[node[d]];
  138. if (configNode) {
  139. updatedConfigNode = true;
  140. var users = configNode.users;
  141. users.splice(users.indexOf(node),1);
  142. }
  143. }
  144. }
  145. }
  146. }
  147. if (updatedConfigNode) {
  148. RED.sidebar.config.refresh();
  149. }
  150. }
  151. return removedLinks;
  152. }
  153. function removeLink(l) {
  154. var index = links.indexOf(l);
  155. if (index != -1) {
  156. links.splice(index,1);
  157. }
  158. }
  159. function refreshValidation() {
  160. for (var n=0;n<nodes.length;n++) {
  161. RED.editor.validateNode(nodes[n]);
  162. }
  163. }
  164. function addWorkspace(ws) {
  165. workspaces[ws.id] = ws;
  166. }
  167. function getWorkspace(id) {
  168. return workspaces[id];
  169. }
  170. function removeWorkspace(id) {
  171. delete workspaces[id];
  172. var removedNodes = [];
  173. var removedLinks = [];
  174. var n;
  175. for (n=0;n<nodes.length;n++) {
  176. var node = nodes[n];
  177. if (node.z == id) {
  178. removedNodes.push(node);
  179. }
  180. }
  181. for (n=0;n<removedNodes.length;n++) {
  182. var rmlinks = removeNode(removedNodes[n].id);
  183. removedLinks = removedLinks.concat(rmlinks);
  184. }
  185. return {nodes:removedNodes,links:removedLinks};
  186. }
  187. function getAllFlowNodes(node) {
  188. var visited = {};
  189. visited[node.id] = true;
  190. var nns = [node];
  191. var stack = [node];
  192. while(stack.length !== 0) {
  193. var n = stack.shift();
  194. var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
  195. for (var i=0;i<childLinks.length;i++) {
  196. var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
  197. if (!visited[child.id]) {
  198. visited[child.id] = true;
  199. nns.push(child);
  200. stack.push(child);
  201. }
  202. }
  203. }
  204. return nns;
  205. }
  206. /**
  207. * Converts a node to an exportable JSON Object
  208. **/
  209. function convertNode(n, exportCreds) {
  210. exportCreds = exportCreds || false;
  211. var node = {};
  212. node.id = n.id;
  213. node.type = n.type;
  214. for (var d in n._def.defaults) {
  215. if (n._def.defaults.hasOwnProperty(d)) {
  216. node[d] = n[d];
  217. }
  218. }
  219. if(exportCreds && n.credentials) {
  220. node.credentials = {};
  221. for (var cred in n._def.credentials) {
  222. if (n._def.credentials.hasOwnProperty(cred)) {
  223. if (n.credentials[cred] != null) {
  224. node.credentials[cred] = n.credentials[cred];
  225. }
  226. }
  227. }
  228. }
  229. if (n._def.category != "config") {
  230. node.x = n.x;
  231. node.y = n.y;
  232. node.z = n.z;
  233. node.wires = [];
  234. for(var i=0;i<n.outputs;i++) {
  235. node.wires.push([]);
  236. }
  237. var wires = links.filter(function(d){return d.source === n;});
  238. for (var j=0;j<wires.length;j++) {
  239. var w = wires[j];
  240. node.wires[w.sourcePort].push(w.target.id + ":" + w.targetPort);
  241. }
  242. }
  243. return node;
  244. }
  245. /**
  246. * Converts the current node selection to an exportable JSON Object
  247. **/
  248. function createExportableNodeSet(set) {
  249. var nns = [];
  250. var exportedConfigNodes = {};
  251. for (var n=0;n<set.length;n++) {
  252. var node = set[n].n;
  253. var convertedNode = RED.nodes.convertNode(node);
  254. for (var d in node._def.defaults) {
  255. if (node._def.defaults[d].type && node[d] in configNodes) {
  256. var confNode = configNodes[node[d]];
  257. var exportable = getType(node._def.defaults[d].type).exportable;
  258. if ((exportable == null || exportable)) {
  259. if (!(node[d] in exportedConfigNodes)) {
  260. exportedConfigNodes[node[d]] = true;
  261. nns.unshift(RED.nodes.convertNode(confNode));
  262. }
  263. } else {
  264. convertedNode[d] = "";
  265. }
  266. }
  267. }
  268. nns.push(convertedNode);
  269. }
  270. return nns;
  271. }
  272. //TODO: rename this (createCompleteNodeSet)
  273. function createCompleteNodeSet() {
  274. var nns = [];
  275. var i;
  276. for (i in workspaces) {
  277. if (workspaces.hasOwnProperty(i)) {
  278. nns.push(workspaces[i]);
  279. }
  280. }
  281. for (i in configNodes) {
  282. if (configNodes.hasOwnProperty(i)) {
  283. nns.push(convertNode(configNodes[i], true));
  284. }
  285. }
  286. for (i=0;i<nodes.length;i++) {
  287. var node = nodes[i];
  288. nns.push(convertNode(node, true));
  289. }
  290. return nns;
  291. }
  292. /**
  293. * Parses the input string which contains copied code from the Arduino IDE, scans the
  294. * nodes and connections and forms them into a JSON representation which will be
  295. * returned as string.
  296. *
  297. * So the result may directly imported in the localStorage or the import dialog.
  298. */
  299. function cppToJSON(newNodesStr) {
  300. var data = "";
  301. var nodes = [];
  302. var cables = [];
  303. const CODE_START = "// GUItool: begin automatically generated code";
  304. const CODE_END = "// GUItool: end automatically generated code";
  305. const NODE_COMMENT = "//";
  306. const NODE_AC = "AudioConnection";
  307. const NODE_AI_I2S = "AudioInputI2S";
  308. const NODE_AM_4 = "AudioMixer4";
  309. const NODE_AC_SGTL = "AudioControlSGTL5000";
  310. var parseLine = function(line) {
  311. var parts = line.match(/^(\S+)\s(.*)/).slice(1);
  312. var type = $.trim(parts[0]);
  313. line = $.trim(parts[1]) + " ";
  314. var description = "";
  315. var coords = [0, 0];
  316. var conn = [];
  317. parts = line.match(/^([^;]{0,});(.*)/);
  318. if (parts) {
  319. parts = parts.slice(1);
  320. description = $.trim(parts[0]);
  321. coords = $.trim(parts[1]);
  322. parts = coords.match(/^([^\/]{0,})\/\/xy=(.*)/);
  323. if (parts) {
  324. parts = parts.slice(1);
  325. coords = $.trim(parts[1]).split(",");
  326. }
  327. }
  328. switch (type) {
  329. case NODE_AC:
  330. parts = description.match(/^([^\(]*\()([^\)]*)(.*)/);
  331. if (parts) {
  332. conn = $.trim(parts[2]).split(",");
  333. cables.push(conn);
  334. }
  335. break;
  336. case NODE_COMMENT:
  337. // do nothing ...
  338. break;
  339. default:
  340. var node = new Object({
  341. "id": description,
  342. "type": type,
  343. "x": parseInt(coords ? coords[0] : 0),
  344. "y": parseInt(coords ? coords[1] : 0),
  345. "z": 0,
  346. "wires": []
  347. });
  348. nodes.push(node);
  349. break;
  350. }
  351. };
  352. var findNode = function(name) {
  353. var len = nodes.length;
  354. for (var key = 0; key < len; key++) {
  355. if (nodes[key].id == name) {
  356. return nodes[key];
  357. }
  358. }
  359. };
  360. var linkCables = function(cables) {
  361. $.each(cables, function(i, item) {
  362. var conn = item;
  363. // when there are only two entries in the array, there
  364. // is only one output to connect to one input, so we have
  365. // to extend the array with the appropriate index "0" for
  366. // both parst (in and out)
  367. if (conn.length == 2) {
  368. conn[2] = conn[1];
  369. conn[1] = conn[3] = 0;
  370. }
  371. // now we assign the outputs (marked by the "idx" of the array)
  372. // to the inputs describend by text
  373. var currNode = findNode($.trim(conn[0]));
  374. var idx = parseInt($.trim(conn[1]));
  375. if (currNode) {
  376. if ($.trim(conn[2]) != "" && $.trim(conn[3]) != "") {
  377. var wire = $.trim(conn[2]) + ":" + $.trim(conn[3]);
  378. var tmp = currNode.wires[idx] ? currNode.wires[idx] : [];
  379. tmp.push(wire);
  380. currNode.wires[idx] = tmp;
  381. }
  382. }
  383. });
  384. };
  385. var traverseLines = function(raw) {
  386. var lines = raw.split("\n");
  387. var useLine = 0;
  388. for (var i = 0; i < lines.length; i++) {
  389. var line = lines[i];
  390. useLine += (line.indexOf(CODE_START) >= 0) ? (useLine ? 0 : 1) : 0;
  391. if (useLine > 0) {
  392. parseLine(line);
  393. }
  394. useLine -= (line.indexOf(CODE_END) >= 0) ? (useLine ? 1 : 0) : 0;
  395. }
  396. };
  397. var readCode = function() {
  398. var fileImport = $("#importInput")[0];
  399. var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.ino|.txt)$/;
  400. if (regex.test(fileImport.value.toLowerCase())) {
  401. if (typeof (FileReader) != "undefined") {
  402. var reader = new FileReader();
  403. $(reader).on("load", function (e) {
  404. });
  405. reader.readAsText(fileImport.files[0]);
  406. } else {
  407. alert("This browser does not support HTML5.");
  408. }
  409. } else {
  410. alert("Please upload a valid INO or text file.");
  411. }
  412. };
  413. traverseLines(newNodesStr);
  414. linkCables(cables);
  415. return JSON.stringify(nodes);
  416. }
  417. function importNodes(newNodesObj,createNewIds) {
  418. try {
  419. var i;
  420. var n;
  421. var newNodes;
  422. if (typeof newNodesObj === "string") {
  423. if (newNodesObj === "") {
  424. return;
  425. }
  426. newNodes = JSON.parse(newNodesObj);
  427. } else {
  428. newNodes = newNodesObj;
  429. }
  430. if (!$.isArray(newNodes)) {
  431. newNodes = [newNodes];
  432. }
  433. var unknownTypes = [];
  434. for (i=0;i<newNodes.length;i++) {
  435. n = newNodes[i];
  436. // TODO: remove workspace in next release+1
  437. if (n.type != "workspace" && n.type != "tab" && !getType(n.type)) {
  438. // TODO: get this UI thing out of here! (see below as well)
  439. n.name = n.type;
  440. n.type = "unknown";
  441. if (unknownTypes.indexOf(n.name)==-1) {
  442. unknownTypes.push(n.name);
  443. }
  444. if (n.x == null && n.y == null) {
  445. // config node - remove it
  446. newNodes.splice(i,1);
  447. i--;
  448. }
  449. }
  450. }
  451. /*
  452. if (unknownTypes.length > 0) {
  453. var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
  454. var type = "type"+(unknownTypes.length > 1?"s":"");
  455. RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
  456. //"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");
  457. }
  458. for (i=0;i<newNodes.length;i++) {
  459. n = newNodes[i];
  460. // TODO: remove workspace in next release+1
  461. if (n.type === "workspace" || n.type === "tab") {
  462. if (n.type === "workspace") {
  463. n.type = "tab";
  464. }
  465. if (defaultWorkspace == null) {
  466. defaultWorkspace = n;
  467. }
  468. addWorkspace(n);
  469. RED.view.addWorkspace(n);
  470. }
  471. }
  472. if (defaultWorkspace == null) {
  473. defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
  474. addWorkspace(defaultWorkspace);
  475. RED.view.addWorkspace(defaultWorkspace);
  476. }
  477. */
  478. var node_map = {};
  479. var new_nodes = [];
  480. var new_links = [];
  481. for (i=0;i<newNodes.length;i++) {
  482. n = newNodes[i];
  483. // TODO: remove workspace in next release+1
  484. if (n.type !== "workspace" && n.type !== "tab") {
  485. var def = getType(n.type);
  486. if (def && def.category == "config") {
  487. if (!RED.nodes.node(n.id)) {
  488. var configNode = {id:n.id,type:n.type,users:[]};
  489. for (var d in def.defaults) {
  490. if (def.defaults.hasOwnProperty(d)) {
  491. configNode[d] = n[d];
  492. }
  493. }
  494. configNode.label = def.label;
  495. configNode._def = def;
  496. RED.nodes.add(configNode);
  497. }
  498. } else {
  499. var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
  500. if (createNewIds) {
  501. node.z = RED.view.getWorkspace();
  502. node.id = getID();
  503. } else {
  504. node.id = n.id;
  505. if (node.z == null || !workspaces[node.z]) {
  506. node.z = RED.view.getWorkspace();
  507. }
  508. }
  509. node.type = n.type;
  510. node._def = def;
  511. if (!node._def) {
  512. node._def = {
  513. color:"#fee",
  514. defaults: {},
  515. label: "unknown: "+n.type,
  516. labelStyle: "node_label_italic",
  517. outputs: n.outputs||n.wires.length
  518. }
  519. }
  520. node.outputs = n.outputs||node._def.outputs;
  521. for (var d2 in node._def.defaults) {
  522. if (node._def.defaults.hasOwnProperty(d2)) {
  523. node[d2] = n[d2];
  524. }
  525. }
  526. addNode(node);
  527. RED.editor.validateNode(node);
  528. node_map[n.id] = node;
  529. new_nodes.push(node);
  530. }
  531. }
  532. }
  533. for (i=0;i<new_nodes.length;i++) {
  534. n = new_nodes[i];
  535. for (var w1=0;w1<n.wires.length;w1++) {
  536. var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
  537. for (var w2=0;w2<wires.length;w2++) {
  538. var parts = wires[w2].split(":");
  539. if (parts.length == 2 && parts[0] in node_map) {
  540. var dst = node_map[parts[0]];
  541. var link = {source:n,sourcePort:w1,target:dst,targetPort:parts[1]};
  542. addLink(link);
  543. new_links.push(link);
  544. }
  545. }
  546. }
  547. delete n.wires;
  548. }
  549. return [new_nodes,new_links];
  550. } catch(error) {
  551. //TODO: get this UI thing out of here! (see above as well)
  552. RED.notify("<strong>Error</strong>: "+error,"error");
  553. return null;
  554. }
  555. }
  556. return {
  557. registerType: registerType,
  558. getType: getType,
  559. convertNode: convertNode,
  560. selectNode: selectNode,
  561. add: addNode,
  562. addLink: addLink,
  563. remove: removeNode,
  564. removeLink: removeLink,
  565. addWorkspace: addWorkspace,
  566. removeWorkspace: removeWorkspace,
  567. workspace: getWorkspace,
  568. eachNode: function(cb) {
  569. for (var n=0;n<nodes.length;n++) {
  570. cb(nodes[n]);
  571. }
  572. },
  573. eachLink: function(cb) {
  574. for (var l=0;l<links.length;l++) {
  575. cb(links[l]);
  576. }
  577. },
  578. eachConfig: function(cb) {
  579. for (var id in configNodes) {
  580. if (configNodes.hasOwnProperty(id)) {
  581. cb(configNodes[id]);
  582. }
  583. }
  584. },
  585. node: getNode,
  586. cppToJSON: cppToJSON,
  587. import: importNodes,
  588. refreshValidation: refreshValidation,
  589. getAllFlowNodes: getAllFlowNodes,
  590. createExportableNodeSet: createExportableNodeSet,
  591. createCompleteNodeSet: createCompleteNodeSet,
  592. id: getID,
  593. cppName: createUniqueCppName,
  594. nodes: nodes, // TODO: exposed for d3 vis
  595. links: links // TODO: exposed for d3 vis
  596. };
  597. })();