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.

пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /** Modified from original Node-Red source, for audio system visualization
  2. * vim: set ts=4:
  3. * Copyright 2013, 2014 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.editor = (function() {
  18. var editing_node = null;
  19. // TODO: should IMPORT/EXPORT get their own dialogs?
  20. function getCredentialsURL(nodeType, nodeID) {
  21. var dashedType = nodeType.replace(/\s+/g, '-');
  22. return 'credentials/' + dashedType + "/" + nodeID;
  23. }
  24. /**
  25. * Validate a node
  26. * @param node - the node being validated
  27. * @returns {boolean} whether the node is valid. Sets node.dirty if needed
  28. */
  29. function validateNode(node) {
  30. var oldValue = node.valid;
  31. node.valid = validateNodeProperties(node, node._def.defaults, node);
  32. if (node._def._creds) {
  33. node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
  34. }
  35. if (oldValue != node.valid) {
  36. node.dirty = true;
  37. }
  38. }
  39. /**
  40. * Validate a node's properties for the given set of property definitions
  41. * @param node - the node being validated
  42. * @param definition - the node property definitions (either def.defaults or def.creds)
  43. * @param properties - the node property values to validate
  44. * @returns {boolean} whether the node's properties are valid
  45. */
  46. function validateNodeProperties(node, definition, properties) {
  47. var isValid = true;
  48. for (var prop in definition) {
  49. if (!validateNodeProperty(node, definition, prop, properties[prop])) {
  50. isValid = false;
  51. }
  52. }
  53. return isValid;
  54. }
  55. /**
  56. * Validate a individual node property
  57. * @param node - the node being validated
  58. * @param definition - the node property definitions (either def.defaults or def.creds)
  59. * @param property - the property name being validated
  60. * @param value - the property value being validated
  61. * @returns {boolean} whether the node proprty is valid
  62. */
  63. function validateNodeProperty(node,definition,property,value) {
  64. var valid = true;
  65. if ("required" in definition[property] && definition[property].required) {
  66. valid = value !== "";
  67. }
  68. if (valid && "validate" in definition[property]) {
  69. valid = definition[property].validate.call(node,value);
  70. }
  71. if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
  72. if (!value || value == "_ADD_") {
  73. valid = false;
  74. } else {
  75. var v = RED.nodes.node(value).valid;
  76. valid = (v==null || v);
  77. }
  78. }
  79. return valid;
  80. }
  81. /**
  82. * Called when the node's properties have changed.
  83. * Marks the node as dirty and needing a size check.
  84. * Removes any links to non-existant outputs.
  85. * @param node - the node that has been updated
  86. * @returns {array} the links that were removed due to this update
  87. */
  88. function updateNodeProperties(node) {
  89. node.resize = true;
  90. node.dirty = true;
  91. var removedLinks = [];
  92. if (node.outputs < node.ports.length) {
  93. while (node.outputs < node.ports.length) {
  94. node.ports.pop();
  95. }
  96. RED.nodes.eachLink(function(l) {
  97. if (l.source === node && l.sourcePort >= node.outputs) {
  98. removedLinks.push(l);
  99. }
  100. });
  101. for (var l=0;l<removedLinks.length;l++) {
  102. RED.nodes.removeLink(removedLinks[l]);
  103. }
  104. } else if (node.outputs > node.ports.length) {
  105. while (node.outputs > node.ports.length) {
  106. node.ports.push(node.ports.length);
  107. }
  108. }
  109. return removedLinks;
  110. }
  111. $( "#dialog" ).dialog({
  112. modal: true,
  113. autoOpen: false,
  114. closeOnEscape: false,
  115. width: 500,
  116. buttons: [
  117. {
  118. text: "Ok",
  119. click: function() {
  120. if (editing_node) {
  121. var changes = {};
  122. var changed = false;
  123. var wasDirty = RED.view.dirty();
  124. var d;
  125. if (editing_node._def.oneditsave) {
  126. var oldValues = {};
  127. for (d in editing_node._def.defaults) {
  128. if (editing_node._def.defaults.hasOwnProperty(d)) {
  129. if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
  130. oldValues[d] = editing_node[d];
  131. } else {
  132. oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
  133. }
  134. }
  135. }
  136. var rc = editing_node._def.oneditsave.call(editing_node);
  137. if (rc === true) {
  138. changed = true;
  139. }
  140. for (d in editing_node._def.defaults) {
  141. if (editing_node._def.defaults.hasOwnProperty(d)) {
  142. if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
  143. if (oldValues[d] !== editing_node[d]) {
  144. changes[d] = oldValues[d];
  145. changed = true;
  146. }
  147. } else {
  148. if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
  149. changes[d] = oldValues[d];
  150. changed = true;
  151. }
  152. }
  153. }
  154. }
  155. }
  156. if (editing_node._def.defaults) {
  157. for (d in editing_node._def.defaults) {
  158. if (editing_node._def.defaults.hasOwnProperty(d)) {
  159. var input = $("#node-input-"+d);
  160. var newValue;
  161. if (input.attr('type') === "checkbox") {
  162. newValue = input.prop('checked');
  163. } else {
  164. newValue = input.val();
  165. }
  166. if (newValue != null) {
  167. if (editing_node[d] != newValue) {
  168. if (editing_node._def.defaults[d].type) {
  169. if (newValue == "_ADD_") {
  170. newValue = "";
  171. }
  172. // Change to a related config node
  173. var configNode = RED.nodes.node(editing_node[d]);
  174. if (configNode) {
  175. var users = configNode.users;
  176. users.splice(users.indexOf(editing_node),1);
  177. }
  178. configNode = RED.nodes.node(newValue);
  179. if (configNode) {
  180. configNode.users.push(editing_node);
  181. }
  182. }
  183. changes[d] = editing_node[d];
  184. editing_node[d] = newValue;
  185. changed = true;
  186. }
  187. }
  188. }
  189. }
  190. }
  191. if (editing_node._def.credentials) {
  192. var prefix = 'node-input';
  193. var credDefinition = editing_node._def.credentials;
  194. var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
  195. changed = changed || credsChanged;
  196. }
  197. var removedLinks = updateNodeProperties(editing_node);
  198. if (changed) {
  199. var wasChanged = editing_node.changed;
  200. editing_node.changed = true;
  201. RED.view.dirty(true);
  202. RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
  203. }
  204. editing_node.dirty = true;
  205. validateNode(editing_node);
  206. RED.view.redraw();
  207. } else if (RED.view.state() == RED.state.EXPORT) {
  208. if (/library/.test($( "#dialog" ).dialog("option","title"))) {
  209. //TODO: move this to RED.library
  210. var flowName = $("#node-input-filename").val();
  211. if (!/^\s*$/.test(flowName)) {
  212. $.post('library/flows/'+flowName,$("#node-input-filename").attr('nodes'),function() {
  213. RED.library.loadFlowLibrary();
  214. RED.notify("Saved nodes","success");
  215. });
  216. }
  217. }
  218. } else if (RED.view.state() == RED.state.IMPORT) {
  219. RED.view.importNodes($("#node-input-import").val());
  220. }
  221. $( this ).dialog( "close" );
  222. }
  223. },
  224. {
  225. text: "Cancel",
  226. click: function() {
  227. $( this ).dialog( "close" );
  228. }
  229. }
  230. ],
  231. resize: function(e,ui) {
  232. if (editing_node) {
  233. $(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
  234. }
  235. },
  236. open: function(e) {
  237. RED.keyboard.disable();
  238. if (editing_node) {
  239. var size = $(this).dialog('option','sizeCache-'+editing_node.type);
  240. if (size) {
  241. $(this).dialog('option','width',size.width);
  242. $(this).dialog('option','height',size.height);
  243. }
  244. }
  245. },
  246. close: function(e) {
  247. RED.keyboard.enable();
  248. if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
  249. RED.view.state(RED.state.DEFAULT);
  250. }
  251. $( this ).dialog('option','height','auto');
  252. $( this ).dialog('option','width','500');
  253. if (editing_node) {
  254. RED.sidebar.info.refresh(editing_node);
  255. }
  256. RED.sidebar.config.refresh();
  257. editing_node = null;
  258. }
  259. });
  260. /**
  261. * Create a config-node select box for this property
  262. * @param node - the node being edited
  263. * @param property - the name of the field
  264. * @param type - the type of the config-node
  265. */
  266. function prepareConfigNodeSelect(node,property,type) {
  267. var input = $("#node-input-"+property);
  268. var node_def = RED.nodes.getType(type);
  269. input.replaceWith('<select style="width: 60%;" id="node-input-'+property+'"></select>');
  270. updateConfigNodeSelect(property,type,node[property]);
  271. var select = $("#node-input-"+property);
  272. select.after(' <a id="node-input-lookup-'+property+'" class="btn"><i class="icon icon-pencil"></i></a>');
  273. $('#node-input-lookup-'+property).click(function(e) {
  274. showEditConfigNodeDialog(property,type,select.find(":selected").val());
  275. e.preventDefault();
  276. });
  277. var label = "";
  278. var configNode = RED.nodes.node(node[property]);
  279. if (configNode && node_def.label) {
  280. if (typeof node_def.label == "function") {
  281. label = node_def.label.call(configNode);
  282. } else {
  283. label = node_def.label;
  284. }
  285. }
  286. input.val(label);
  287. }
  288. /**
  289. * Populate the editor dialog input field for this property
  290. * @param node - the node being edited
  291. * @param property - the name of the field
  292. * @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
  293. */
  294. function preparePropertyEditor(node,property,prefix) {
  295. var input = $("#"+prefix+"-"+property);
  296. if (input.attr('type') === "checkbox") {
  297. input.prop('checked',node[property]);
  298. } else {
  299. var val = node[property];
  300. if (val == null) {
  301. val = "";
  302. }
  303. input.val(val);
  304. }
  305. }
  306. /**
  307. * Add an on-change handler to revalidate a node field
  308. * @param node - the node being edited
  309. * @param definition - the definition of the node
  310. * @param property - the name of the field
  311. * @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
  312. */
  313. function attachPropertyChangeHandler(node,definition,property,prefix) {
  314. $("#"+prefix+"-"+property).change(function() {
  315. if (!validateNodeProperty(node, definition, property,this.value)) {
  316. $(this).addClass("input-error");
  317. } else {
  318. $(this).removeClass("input-error");
  319. }
  320. });
  321. }
  322. /**
  323. * Assign the value to each credential field
  324. * @param node
  325. * @param credDef
  326. * @param credData
  327. * @param prefix
  328. */
  329. function populateCredentialsInputs(node, credDef, credData, prefix) {
  330. var cred;
  331. for (cred in credDef) {
  332. if (credDef.hasOwnProperty(cred)) {
  333. if (credDef[cred].type == 'password') {
  334. if (credData[cred]) {
  335. $('#' + prefix + '-' + cred).val(credData[cred]);
  336. } else if (credData['has_' + cred]) {
  337. $('#' + prefix + '-' + cred).val('__PWRD__');
  338. }
  339. else {
  340. $('#' + prefix + '-' + cred).val('');
  341. }
  342. } else {
  343. preparePropertyEditor(credData, cred, prefix);
  344. }
  345. attachPropertyChangeHandler(node, credDef, cred, prefix);
  346. }
  347. }
  348. for (cred in credDef) {
  349. if (credDef.hasOwnProperty(cred)) {
  350. $("#" + prefix + "-" + cred).change();
  351. }
  352. }
  353. }
  354. /**
  355. * Update the node credentials from the edit form
  356. * @param node - the node containing the credentials
  357. * @param credDefinition - definition of the credentials
  358. * @param prefix - prefix of the input fields
  359. * @return {boolean} whether anything has changed
  360. */
  361. function updateNodeCredentials(node, credDefinition, prefix) {
  362. var changed = false;
  363. if(!node.credentials) {
  364. node.credentials = {_:{}};
  365. }
  366. for (var cred in credDefinition) {
  367. if (credDefinition.hasOwnProperty(cred)) {
  368. var input = $("#" + prefix + '-' + cred);
  369. var value = input.val();
  370. if (credDefinition[cred].type == 'password') {
  371. node.credentials['has_' + cred] = (value !== "");
  372. if (value == '__PWRD__') {
  373. continue;
  374. }
  375. changed = true;
  376. }
  377. if (value != node.credentials._[cred]) {
  378. node.credentials[cred] = value;
  379. changed = true;
  380. }
  381. }
  382. }
  383. return changed;
  384. }
  385. /**
  386. * Prepare all of the editor dialog fields
  387. * @param node - the node being edited
  388. * @param definition - the node definition
  389. * @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
  390. */
  391. function prepareEditDialog(node,definition,prefix) {
  392. for (var d in definition.defaults) {
  393. if (definition.defaults.hasOwnProperty(d)) {
  394. if (definition.defaults[d].type) {
  395. prepareConfigNodeSelect(node,d,definition.defaults[d].type);
  396. } else {
  397. preparePropertyEditor(node,d,prefix);
  398. }
  399. attachPropertyChangeHandler(node,definition.defaults,d,prefix);
  400. }
  401. }
  402. var completePrepare = function() {
  403. if (definition.oneditprepare) {
  404. definition.oneditprepare.call(node);
  405. }
  406. for (var d in definition.defaults) {
  407. if (definition.defaults.hasOwnProperty(d)) {
  408. $("#"+prefix+"-"+d).change();
  409. }
  410. }
  411. };
  412. if (definition.credentials) {
  413. if (node.credentials) {
  414. populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
  415. completePrepare();
  416. } else {
  417. $.getJSON(getCredentialsURL(node.type, node.id), function (data) {
  418. node.credentials = data;
  419. node.credentials._ = $.extend(true,{},data);
  420. populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
  421. completePrepare();
  422. });
  423. }
  424. } else {
  425. completePrepare();
  426. }
  427. }
  428. function showEditDialog(node) {
  429. editing_node = node;
  430. RED.view.state(RED.state.EDITING);
  431. //$("#dialog-form").html(RED.view.getForm(node.type));
  432. RED.view.getForm("dialog-form", node.type, function (d, f) {
  433. prepareEditDialog(node,node._def,"node-input");
  434. $( "#dialog" ).dialog("option","title","Edit "+node.type+" node").dialog( "open" );
  435. });
  436. }
  437. function showEditConfigNodeDialog(name,type,id) {
  438. var adding = (id == "_ADD_");
  439. var node_def = RED.nodes.getType(type);
  440. var configNode = RED.nodes.node(id);
  441. if (configNode == null) {
  442. configNode = {
  443. id: (1+Math.random()*4294967295).toString(16),
  444. _def: node_def,
  445. type: type
  446. };
  447. for (var d in node_def.defaults) {
  448. if (node_def.defaults[d].value) {
  449. configNode[d] = node_def.defaults[d].value;
  450. }
  451. }
  452. }
  453. //$("#dialog-config-form").html(RED.view.getForm(type));
  454. RED.view.getForm("dialog-config-form", type, function (d, f) {
  455. prepareEditDialog(configNode,node_def,"node-config-input");
  456. var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
  457. if (adding) {
  458. if (buttons.length == 3) {
  459. buttons = buttons.splice(1);
  460. }
  461. buttons[0].text = "Add";
  462. $("#node-config-dialog-user-count").html("").hide();
  463. } else {
  464. if (buttons.length == 2) {
  465. buttons.unshift({
  466. class: 'leftButton',
  467. text: "Delete",
  468. click: function() {
  469. var configProperty = $(this).dialog('option','node-property');
  470. var configId = $(this).dialog('option','node-id');
  471. var configType = $(this).dialog('option','node-type');
  472. var configNode = RED.nodes.node(configId);
  473. var configTypeDef = RED.nodes.getType(configType);
  474. if (configTypeDef.ondelete) {
  475. configTypeDef.ondelete.call(RED.nodes.node(configId));
  476. }
  477. RED.nodes.remove(configId);
  478. for (var i=0;i<configNode.users.length;i++) {
  479. var user = configNode.users[i];
  480. for (var d in user._def.defaults) {
  481. if (user._def.defaults.hasOwnProperty(d) && user[d] == configId) {
  482. user[d] = "";
  483. }
  484. }
  485. validateNode(user);
  486. }
  487. updateConfigNodeSelect(configProperty,configType,"");
  488. RED.view.dirty(true);
  489. $( this ).dialog( "close" );
  490. RED.view.redraw();
  491. }
  492. });
  493. }
  494. buttons[1].text = "Update";
  495. $("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show();
  496. }
  497. $( "#node-config-dialog" ).dialog("option","buttons",buttons);
  498. $( "#node-config-dialog" )
  499. .dialog("option","node-adding",adding)
  500. .dialog("option","node-property",name)
  501. .dialog("option","node-id",configNode.id)
  502. .dialog("option","node-type",type)
  503. .dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node")
  504. .dialog( "open" );
  505. });
  506. }
  507. function updateConfigNodeSelect(name,type,value) {
  508. var select = $("#node-input-"+name);
  509. var node_def = RED.nodes.getType(type);
  510. select.children().remove();
  511. RED.nodes.eachConfig(function(config) {
  512. if (config.type == type) {
  513. var label = "";
  514. if (typeof node_def.label == "function") {
  515. label = node_def.label.call(config);
  516. } else {
  517. label = node_def.label;
  518. }
  519. select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
  520. }
  521. });
  522. select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
  523. window.setTimeout(function() { select.change();},50);
  524. }
  525. $( "#node-config-dialog" ).dialog({
  526. modal: true,
  527. autoOpen: false,
  528. width: 500,
  529. closeOnEscape: false,
  530. buttons: [
  531. {
  532. text: "Ok",
  533. click: function() {
  534. var configProperty = $(this).dialog('option','node-property');
  535. var configId = $(this).dialog('option','node-id');
  536. var configType = $(this).dialog('option','node-type');
  537. var configAdding = $(this).dialog('option','node-adding');
  538. var configTypeDef = RED.nodes.getType(configType);
  539. var configNode;
  540. var d;
  541. if (configAdding) {
  542. configNode = {type:configType,id:configId,users:[]};
  543. for (d in configTypeDef.defaults) {
  544. if (configTypeDef.defaults.hasOwnProperty(d)) {
  545. configNode[d] = $("#node-config-input-"+d).val();
  546. }
  547. }
  548. configNode.label = configTypeDef.label;
  549. configNode._def = configTypeDef;
  550. RED.nodes.add(configNode);
  551. updateConfigNodeSelect(configProperty,configType,configNode.id);
  552. } else {
  553. configNode = RED.nodes.node(configId);
  554. for (d in configTypeDef.defaults) {
  555. if (configTypeDef.defaults.hasOwnProperty(d)) {
  556. configNode[d] = $("#node-config-input-"+d).val();
  557. }
  558. }
  559. updateConfigNodeSelect(configProperty,configType,configId);
  560. }
  561. if (configTypeDef.credentials) {
  562. updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
  563. }
  564. if (configTypeDef.oneditsave) {
  565. configTypeDef.oneditsave.call(RED.nodes.node(configId));
  566. }
  567. validateNode(configNode);
  568. RED.view.dirty(true);
  569. $(this).dialog("close");
  570. }
  571. },
  572. {
  573. text: "Cancel",
  574. click: function() {
  575. var configType = $(this).dialog('option','node-type');
  576. var configId = $(this).dialog('option','node-id');
  577. var configAdding = $(this).dialog('option','node-adding');
  578. var configTypeDef = RED.nodes.getType(configType);
  579. if (configTypeDef.oneditcancel) {
  580. // TODO: what to pass as this to call
  581. if (configTypeDef.oneditcancel) {
  582. var cn = RED.nodes.node(configId);
  583. if (cn) {
  584. configTypeDef.oneditcancel.call(cn,false);
  585. } else {
  586. configTypeDef.oneditcancel.call({id:configId},true);
  587. }
  588. }
  589. }
  590. $( this ).dialog( "close" );
  591. }
  592. }
  593. ],
  594. resize: function(e,ui) {
  595. },
  596. open: function(e) {
  597. if (RED.view.state() != RED.state.EDITING) {
  598. RED.keyboard.disable();
  599. }
  600. },
  601. close: function(e) {
  602. $("#dialog-config-form").html("");
  603. if (RED.view.state() != RED.state.EDITING) {
  604. RED.keyboard.enable();
  605. }
  606. RED.sidebar.config.refresh();
  607. }
  608. });
  609. return {
  610. edit: showEditDialog,
  611. editConfig: showEditConfigNodeDialog,
  612. validateNode: validateNode,
  613. updateNodeProperties: updateNodeProperties // TODO: only exposed for edit-undo
  614. }
  615. })();