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 9fd4ba01e..5b5f45572 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 @@ -326,47 +326,78 @@ RED.editor = (function() { /** * Create a config-node select box for this property - * @param node - the node being edited - * @param property - the name of the field - * @param type - the type of the config-node + * @param {Object} node - the node being edited + * @param {String} property - the name of the node property + * @param {String} type - the type of the config-node + * @param {"node-config-input"|"node-input"|"node-input-subflow-env"} prefix - the prefix to use in the input element ids + * @param {Function} [filter] - a function to filter the list of config nodes + * @param {Object} [env] - the environment variable object (only used for subflow env vars) */ - function prepareConfigNodeSelect(node,property,type,prefix,filter) { - var input = $("#"+prefix+"-"+property); - if (input.length === 0 ) { + function prepareConfigNodeSelect(node, property, type, prefix, filter, env) { + let nodeValue + if (prefix === 'node-input-subflow-env') { + nodeValue = env?.value + } else { + nodeValue = node[property] + } + + const buttonId = `${prefix}-lookup-${property}` + const selectId = prefix + '-' + property + const input = $(`#${selectId}`); + if (input.length === 0) { return; } - var newWidth = input.width(); - var attrStyle = input.attr('style'); - var m; + const attrStyle = input.attr('style'); + let newWidth; + let m; if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) { newWidth = m[2].trim(); } else { newWidth = "70%"; } - var outerWrap = $("
").css({ + const outerWrap = $("").css({ width: newWidth, - display:'inline-flex' + display: 'inline-flex' }); - var select = $('').appendTo(outerWrap); + const select = $('').appendTo(outerWrap); input.replaceWith(outerWrap); // set the style attr directly - using width() on FF causes a value of 114%... select.css({ 'flex-grow': 1 }); - updateConfigNodeSelect(property,type,node[property],prefix,filter); - $('') - .css({"margin-left":"10px"}) + updateConfigNodeSelect(property, type, nodeValue, prefix, filter); + const disableButton = function(disabled) { + btn.prop( "disabled", !!disabled) + btn.toggleClass("disabled", !!disabled) + } + // create the edit button + const btn = $('') + .css({ "margin-left": "10px" }) .appendTo(outerWrap); - $('#'+prefix+'-lookup-'+property).on("click", function(e) { - showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix,node); + + // add the click handler + btn.on("click", function (e) { + const selectedOpt = select.find(":selected") + if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog) + if (btn.prop("disabled")) { return } + showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node); e.preventDefault(); }); + + // dont permit the user to click the button if the selected option is an env var + select.on("change", function () { + const selectedOpt = select.find(":selected") + if (selectedOpt?.data('env')) { + disableButton(true) + } else { + disableButton(false) + } + }); var label = ""; - var configNode = RED.nodes.node(node[property]); - var node_def = RED.nodes.getType(type); + var configNode = RED.nodes.node(nodeValue); if (configNode) { - label = RED.utils.getNodeLabel(configNode,configNode.id); + label = RED.utils.getNodeLabel(configNode, configNode.id); } input.val(label); } @@ -768,12 +799,9 @@ RED.editor = (function() { } function defaultConfigNodeSort(A,B) { - if (A.__label__ < B.__label__) { - return -1; - } else if (A.__label__ > B.__label__) { - return 1; - } - return 0; + // sort case insensitive so that `[env] node-name` items are at the top and + // not mixed inbetween the the lower and upper case items + return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'}) } function updateConfigNodeSelect(name,type,value,prefix,filter) { @@ -788,7 +816,7 @@ RED.editor = (function() { } $("#"+prefix+"-"+name).val(value); } else { - + let inclSubflowEnvvars = false var select = $("#"+prefix+"-"+name); var node_def = RED.nodes.getType(type); select.children().remove(); @@ -796,6 +824,7 @@ RED.editor = (function() { var activeWorkspace = RED.nodes.workspace(RED.workspaces.active()); if (!activeWorkspace) { activeWorkspace = RED.nodes.subflow(RED.workspaces.active()); + inclSubflowEnvvars = true } var configNodes = []; @@ -811,6 +840,31 @@ RED.editor = (function() { } } }); + + // as includeSubflowEnvvars is true, this is a subflow. + // include any 'conf-types' env vars as a list of avaiable configs + // in the config dropdown as `[env] node-name` + if (inclSubflowEnvvars && activeWorkspace.env) { + const parentEnv = activeWorkspace.env.filter(env => env.ui?.type === 'conf-types' && env.type === type) + if (parentEnv && parentEnv.length > 0) { + const locale = RED.i18n.lang() + for (let i = 0; i < parentEnv.length; i++) { + const tenv = parentEnv[i] + const ui = tenv.ui || {} + const labels = ui.label || {} + const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale) + const config = { + env: tenv, + id: '${' + parentEnv[0].name + '}', + type: type, + label: labelText, + __label__: `[env] ${labelText}` + } + configNodes.push(config) + } + } + } + var configSortFn = defaultConfigNodeSort; if (typeof node_def.sort == "function") { configSortFn = node_def.sort; @@ -822,7 +876,10 @@ RED.editor = (function() { } configNodes.forEach(function(cn) { - $('').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select); + const option = $('').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select); + if (cn.env) { + option.data('env', cn.env) // set a data attribute to indicate this is an env var (to inhibit the edit button) + } delete cn.__label__; }); @@ -1478,9 +1535,16 @@ RED.editor = (function() { } RED.tray.close(function() { var filter = null; - if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') { - filter = function(n) { - return editContext._def.defaults[configProperty].filter.call(editContext,n); + // when editing a config via subflow edit panel, the `configProperty` will not + // necessarily be a property of the editContext._def.defaults object + // Also, when editing via dashboard sidebar, editContext can be null + // so we need to guard both scenarios + if (editContext?._def) { + const isSubflow = (editContext._def.type === 'subflow' || /subflow:.*/.test(editContext._def.type)) + if (editContext && !isSubflow && typeof editContext._def.defaults?.[configProperty]?.filter === 'function') { + filter = function(n) { + return editContext._def.defaults[configProperty].filter.call(editContext,n); + } } } updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter); @@ -1541,7 +1605,7 @@ RED.editor = (function() { RED.history.push(historyEvent); RED.tray.close(function() { var filter = null; - if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') { + if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') { filter = function(n) { return editContext._def.defaults[configProperty].filter.call(editContext,n); } @@ -2127,6 +2191,7 @@ RED.editor = (function() { filteredEditPanes[type] = filter } editPanes[type] = definition; - } + }, + prepareConfigNodeSelect: prepareConfigNodeSelect, } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index ba71e651f..514e7ff94 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -1,8 +1,9 @@ RED.editor.envVarList = (function() { var currentLocale = 'en-US'; - var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; - var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata']; + const DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; + const DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES = ['str','num','bool','json','bin','env','conf-types']; + const DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata']; /** * Create env var edit interface @@ -10,8 +11,8 @@ RED.editor.envVarList = (function() { * @param node - subflow node */ function buildPropertiesList(envContainer, node) { - - var isTemplateNode = (node.type === "subflow"); + if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) } + const isTemplateNode = (node.type === "subflow"); envContainer .css({ @@ -83,7 +84,14 @@ RED.editor.envVarList = (function() { // if `opt.ui` does not exist, then apply defaults. If these // defaults do not change then they will get stripped off // before saving. - if (opt.type === 'cred') { + if (opt.type === 'conf-types') { + opt.ui = opt.ui || { + icon: "fa fa-cog", + type: "conf-types", + opts: {opts:[]} + } + opt.ui.type = "conf-types"; + } else if (opt.type === 'cred') { opt.ui = opt.ui || { icon: "", type: "cred" @@ -119,7 +127,7 @@ RED.editor.envVarList = (function() { } }); - buildEnvEditRow(uiRow, opt.ui, nameField, valueField); + buildEnvEditRow(uiRow, opt, nameField, valueField); nameField.trigger('change'); } }, @@ -181,21 +189,23 @@ RED.editor.envVarList = (function() { * @param nameField - name field of env var * @param valueField - value field of env var */ - function buildEnvEditRow(container, ui, nameField, valueField) { + function buildEnvEditRow(container, opt, nameField, valueField) { + const ui = opt.ui + if(RED.editor.envVarList.debug) { console.log('envVarList: buildEnvEditRow', container, ui, nameField, valueField) } container.addClass("red-ui-editor-subflow-env-ui-row") var topRow = $('').appendTo(container); $('').appendTo(topRow); $('