Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

728 lines
19KB

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