From e910f3915daeef9ef33cb378d72ed0563682457e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 10:33:43 +0100 Subject: [PATCH] Initial refactor of edit dialogs to separate panes --- Gruntfile.js | 2 +- .../@node-red/editor-client/src/js/nodes.js | 19 +- .../editor-client/src/js/ui/editor.js | 1852 ++++------------- .../js/ui/{common => editors}/colorPicker.js | 2 +- .../src/js/ui/editors/iconPicker.js | 99 + .../src/js/ui/editors/panes/appearance.js | 515 +++++ .../src/js/ui/editors/panes/description.js | 71 + .../src/js/ui/editors/panes/properties.js | 190 ++ .../src/js/ui/editors/panes/subflowModule.js | 181 ++ .../js/ui/editors/panes/subflowProperties.js | 68 + .../editor-client/src/js/ui/group.js | 6 +- .../editor-client/src/js/ui/library.js | 2 +- .../editor-client/src/js/ui/subflow.js | 191 +- 13 files changed, 1519 insertions(+), 1679 deletions(-) rename packages/node_modules/@node-red/editor-client/src/js/ui/{common => editors}/colorPicker.js (99%) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js diff --git a/Gruntfile.js b/Gruntfile.js index df9939a8c..261f0c72b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -162,7 +162,6 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js", - "packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js", @@ -182,6 +181,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index e623b3b0f..2d442eea9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -592,18 +592,17 @@ RED.nodes = (function() { inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, oneditprepare: function() { - RED.subflow.buildEditForm("subflow",this); - RED.subflow.buildPropertiesForm(this); + if (this.type !== 'subflow') { + // Whilst this definition is used for both template and instance + // nodes, the work to build the edit form for the template nodes + // is handled elsewhere. + RED.subflow.buildEditForm("subflow",this); + } }, oneditresize: function(size) { - // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); - var height = size.height; - // for (var i=0; idiv.node-input-env-container-row"); - // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("ol.red-ui-editor-subflow-env-list").editableList('height',height); + if (this.type === 'subflow') { + $("#node-input-env-container").editableList('height',size.height - 80); + } }, set:{ module: "node-red" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index d1cc3ed80..a2841cdde 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -20,19 +20,16 @@ RED.editor = (function() { var editStack = []; + var buildingEditDialog = false; var editing_node = null; var editing_config_node = null; - var subflowEditor; var customEditTypes = {}; + var editPanes = {}; + var filteredEditPanes = []; var editTrayWidthCache = {}; - function getCredentialsURL(nodeType, nodeID) { - var dashedType = nodeType.replace(/\s+/g, '-'); - return 'credentials/' + dashedType + "/" + nodeID; - } - /** * Validate a node * @param node - the node being validated @@ -160,7 +157,6 @@ RED.editor = (function() { return valid; } - function validateNodeEditor(node,prefix) { for (var prop in node._def.defaults) { if (node._def.defaults.hasOwnProperty(prop)) { @@ -398,74 +394,95 @@ RED.editor = (function() { } } - /** - * Update the node credentials from the edit form - * @param node - the node containing the credentials - * @param credDefinition - definition of the credentials - * @param prefix - prefix of the input fields - * @return {boolean} whether anything has changed - */ - function updateNodeCredentials(node, credDefinition, prefix) { - var changed = false; - if (!node.credentials) { - node.credentials = {_:{}}; - } else if (!node.credentials._) { - node.credentials._ = {}; - } - - for (var cred in credDefinition) { - if (credDefinition.hasOwnProperty(cred)) { - var input = $("#" + prefix + '-' + cred); - if (input.length > 0) { - var value = input.val(); - if (credDefinition[cred].type == 'password') { - node.credentials['has_' + cred] = (value !== ""); - if (value == '__PWRD__') { - continue; - } - changed = true; - - } - node.credentials[cred] = value; - if (value != node.credentials._[cred]) { - changed = true; - } - } - } - } - return changed; - } - /** * Prepare all of the editor dialog fields + * @param trayBody - the tray body to populate + * @param nodeEditPanes - array of edit pane ids to add to the dialog * @param node - the node being edited * @param definition - the node definition * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) + * @param default - the id of the tab to show by default */ - function prepareEditDialog(node,definition,prefix,done) { - for (var d in definition.defaults) { - if (definition.defaults.hasOwnProperty(d)) { - if (definition.defaults[d].type) { - if (!definition.defaults[d]._type.array) { - var configTypeDef = RED.nodes.getType(definition.defaults[d].type); - if (configTypeDef && configTypeDef.category === 'config') { - if (configTypeDef.exclusive) { - prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix); - } else { - prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix); - } - } else { - console.log("Unknown type:", definition.defaults[d].type); - preparePropertyEditor(node,d,prefix,definition.defaults); - } - } - } else { - preparePropertyEditor(node,d,prefix,definition.defaults); - } - attachPropertyChangeHandler(node,definition.defaults,d,prefix); - } - } + function prepareEditDialog(trayBody, nodeEditPanes, node, definition, prefix, defaultTab, done) { + var finishedBuilding = false; var completePrepare = function() { + + var editorTabEl = $('').appendTo(trayBody); + var editorContent = $('
').appendTo(trayBody); + + var editorTabs = RED.tabs.create({ + element:editorTabEl, + onchange:function(tab) { + editorContent.children().hide(); + if (tab.onchange) { + tab.onchange.call(tab); + } + tab.content.show(); + if (finishedBuilding) { + RED.tray.resize(); + } + }, + collapsible: true, + menu: false + }); + + var activeEditPanes = []; + nodeEditPanes.forEach(function(id) { + try { + var editPaneDefinition = editPanes[id]; + if (editPaneDefinition) { + if (typeof editPaneDefinition === 'function') { + editPaneDefinition = editPaneDefinition.call(editPaneDefinition); + } + var editTab = { + id: id, + label: editPaneDefinition.label, + name: editPaneDefinition.name, + iconClass: editPaneDefinition.iconClass, + content: editPaneDefinition.create.call(editPaneDefinition,editorContent,node).hide(), + onchange: function() { + if (editPaneDefinition.onchange) { + editPaneDefinition.onchange.call(editPaneDefinition,node) + } + } + } + editorTabs.addTab(editTab); + activeEditPanes.push(editPaneDefinition); + } else { + console.warn("Unregisted edit pane:",id) + } + } catch(err) { + console.log(id,err); + } + }); + + for (var d in definition.defaults) { + if (definition.defaults.hasOwnProperty(d)) { + if (definition.defaults[d].type) { + if (!definition.defaults[d]._type.array) { + var configTypeDef = RED.nodes.getType(definition.defaults[d].type); + if (configTypeDef && configTypeDef.category === 'config') { + if (configTypeDef.exclusive) { + prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix); + } else { + prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix); + } + } else { + console.log("Unknown type:", definition.defaults[d].type); + preparePropertyEditor(node,d,prefix,definition.defaults); + } + } + } else { + preparePropertyEditor(node,d,prefix,definition.defaults); + } + attachPropertyChangeHandler(node,definition.defaults,d,prefix); + } + } + + if (!/^subflow:/.test(definition.type)) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + } + if (definition.oneditprepare) { try { definition.oneditprepare.call(node); @@ -474,6 +491,7 @@ RED.editor = (function() { console.log(err.stack); } } + // Now invoke any change handlers added to the fields - passing true // to prevent full node validation from being triggered each time for (var d in definition.defaults) { @@ -503,8 +521,12 @@ RED.editor = (function() { } } validateNodeEditor(node,prefix); + finishedBuilding = true; + if (defaultTab) { + editorTabs.activateTab(defaultTab); + } if (done) { - done(); + done(activeEditPanes); } } if (definition.credentials || /^subflow:/.test(definition.type) || (node.type === "group")) { @@ -512,13 +534,14 @@ RED.editor = (function() { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); } else { - getNodeCredentials(node.type, node.id, function(data) { + var nodeType = node.type; + if (/^subflow:/.test(nodeType)) { + nodeType = "subflow" + } + getNodeCredentials(nodeType, node.id, function(data) { if (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); - if (!/^subflow:/.test(definition.type)) { - populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); - } } completePrepare(); }); @@ -598,13 +621,6 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if (type === "subflow-template") { - // This is the 'edit properties' dialog for a subflow template - // TODO: this needs to happen later in the dialog open sequence - // so that credentials can be loaded prior to building the form - RED.subflow.buildEditForm(type,node); - } - // Add dummy fields to prevent 'Enter' submitting the form in some // cases, and also prevent browser auto-fill of password // - the elements cannot be hidden otherwise Chrome will ignore them. @@ -617,506 +633,147 @@ RED.editor = (function() { return dialogForm; } - function refreshLabelForm(container,node) { - - var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); - var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); - - var inputsDiv = $("#red-ui-editor-node-label-form-inputs"); - var outputsDiv = $("#red-ui-editor-node-label-form-outputs"); - - var inputCount; - var formInputs = $("#node-input-inputs").val(); - if (formInputs === undefined) { - if (node.type === 'subflow') { - inputCount = node.in.length; - } else { - inputCount = node.inputs || node._def.inputs || 0; - } - } else { - inputCount = Math.min(1,Math.max(0,parseInt(formInputs))); - if (isNaN(inputCount)) { - inputCount = 0; - } - } - - var children = inputsDiv.children(); - var childCount = children.length; - if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) { - childCount--; - } - - if (childCount < inputCount) { - if (childCount === 0) { - // remove the 'none' placeholder - $(children[0]).remove(); - } - for (i = childCount;i inputCount) { - for (i=inputCount;i B.__label__) { + return 1; + } + return 0; + } + + function updateConfigNodeSelect(name,type,value,prefix) { + // if prefix is null, there is no config select to update + if (prefix) { + var button = $("#"+prefix+"-edit-"+name); + if (button.length) { + if (value) { + button.text(RED._("editor.configEdit")); } else { - row.detach(); + button.text(RED._("editor.configAdd")); } - if (outputMap[p] !== -1) { - outputCount++; - rows.push({i:parseInt(outputMap[p]),r:row}); - } - }); - rows.sort(function(A,B) { - return A.i-B.i; - }) - rows.forEach(function(r,i) { - r.r.find("label").text((i+1)+"."); - r.r.appendTo(outputsDiv); - }) - if (rows.length === 0) { - buildLabelRow("output",i,"").appendTo(outputsDiv); + $("#"+prefix+"-"+name).val(value); } else { - } - } else { - outputCount = Math.max(0,parseInt(formOutputs)); - } - children = outputsDiv.children(); - childCount = children.length; - if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) { - childCount--; - } - if (childCount < outputCount) { - if (childCount === 0) { - // remove the 'none' placeholder - $(children[0]).remove(); - } - for (i = childCount;i outputCount) { - for (i=outputCount;i',{class:"red-ui-editor-node-label-form-row"}); - if (type === undefined) { - $('').text(RED._("editor.noDefaultLabel")).appendTo(result); - result.addClass("red-ui-editor-node-label-form-none"); - } else { - result.addClass(""); - var id = "red-ui-editor-node-label-form-"+type+"-"+index; - $('