mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge pull request #4587 from Steve-Mcl/config-in-subflow
Support config selection in a subflow env var
This commit is contained in:
		@@ -924,7 +924,8 @@
 | 
			
		||||
            "date": "timestamp",
 | 
			
		||||
            "jsonata": "expression",
 | 
			
		||||
            "env": "env variable",
 | 
			
		||||
            "cred": "credential"
 | 
			
		||||
            "cred": "credential",
 | 
			
		||||
            "conf-types": "config node"
 | 
			
		||||
        },
 | 
			
		||||
        "date": {
 | 
			
		||||
            "format": {
 | 
			
		||||
 
 | 
			
		||||
@@ -91,6 +91,31 @@ RED.nodes = (function() {
 | 
			
		||||
            getNodeTypes: function() {
 | 
			
		||||
                return Object.keys(nodeDefinitions);
 | 
			
		||||
            },
 | 
			
		||||
            /**
 | 
			
		||||
             * Get an array of node definitions
 | 
			
		||||
             * @param {Object} options - options object
 | 
			
		||||
             * @param {boolean} [options.configOnly] - if true, only return config nodes
 | 
			
		||||
             * @param {function} [options.filter] - a filter function to apply to the list of nodes
 | 
			
		||||
             * @returns array of node definitions
 | 
			
		||||
             */
 | 
			
		||||
            getNodeDefinitions: function(options) {
 | 
			
		||||
                const result = []
 | 
			
		||||
                const configOnly = (options && options.configOnly)
 | 
			
		||||
                const filter = (options && options.filter)
 | 
			
		||||
                const keys = Object.keys(nodeDefinitions)
 | 
			
		||||
                for (const key of keys) {
 | 
			
		||||
                    const def = nodeDefinitions[key]
 | 
			
		||||
                    if(!def) { continue }
 | 
			
		||||
                    if (configOnly && def.category !== "config") {
 | 
			
		||||
                            continue
 | 
			
		||||
                    }
 | 
			
		||||
                    if (filter && !filter(nodeDefinitions[key])) {
 | 
			
		||||
                        continue
 | 
			
		||||
                    }
 | 
			
		||||
                    result.push(nodeDefinitions[key])
 | 
			
		||||
                }
 | 
			
		||||
                return result
 | 
			
		||||
            },
 | 
			
		||||
            setNodeList: function(list) {
 | 
			
		||||
                nodeList = [];
 | 
			
		||||
                for(var i=0;i<list.length;i++) {
 | 
			
		||||
 
 | 
			
		||||
@@ -174,12 +174,24 @@
 | 
			
		||||
                this.uiContainer.width(m[1]);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.options.sortable) {
 | 
			
		||||
                var isCanceled = false; // Flag to track if an item has been canceled from being dropped into a different list
 | 
			
		||||
                var noDrop = false; // Flag to track if an item is being dragged into a different list
 | 
			
		||||
                var handle = (typeof this.options.sortable === 'string')?
 | 
			
		||||
                                this.options.sortable :
 | 
			
		||||
                                ".red-ui-editableList-item-handle";
 | 
			
		||||
                var sortOptions = {
 | 
			
		||||
                    axis: "y",
 | 
			
		||||
                    update: function( event, ui ) {
 | 
			
		||||
                        // dont trigger update if the item is being canceled
 | 
			
		||||
                        const targetList = $(event.target);
 | 
			
		||||
                        const draggedItem = ui.item;
 | 
			
		||||
                        const draggedItemParent = draggedItem.parent();
 | 
			
		||||
                        if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
 | 
			
		||||
                            noDrop = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (isCanceled || noDrop) {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (that.options.sortItems) {
 | 
			
		||||
                            that.options.sortItems(that.items());
 | 
			
		||||
                        }
 | 
			
		||||
@@ -189,8 +201,32 @@
 | 
			
		||||
                    tolerance: "pointer",
 | 
			
		||||
                    forcePlaceholderSize:true,
 | 
			
		||||
                    placeholder: "red-ui-editabelList-item-placeholder",
 | 
			
		||||
                    start: function(e, ui){
 | 
			
		||||
                        ui.placeholder.height(ui.item.height()-4);
 | 
			
		||||
                    start: function (event, ui) {
 | 
			
		||||
                        isCanceled = false;
 | 
			
		||||
                        ui.placeholder.height(ui.item.height() - 4);
 | 
			
		||||
                        ui.item.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
 | 
			
		||||
                    },
 | 
			
		||||
                    stop: function (event, ui) {
 | 
			
		||||
                        ui.item.css('cursor', 'auto');
 | 
			
		||||
                    },
 | 
			
		||||
                    receive: function (event, ui) {
 | 
			
		||||
                        if (ui.item.hasClass("red-ui-editableList-item-constrained")) {
 | 
			
		||||
                            isCanceled = true;
 | 
			
		||||
                            $(ui.sender).sortable('cancel');
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    over: function (event, ui) {
 | 
			
		||||
                        // if the dragged item is constrained, prevent it from being dropped into a different list
 | 
			
		||||
                        const targetList = $(event.target);
 | 
			
		||||
                        const draggedItem = ui.item;
 | 
			
		||||
                        const draggedItemParent = draggedItem.parent();
 | 
			
		||||
                        if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
 | 
			
		||||
                            noDrop = true;
 | 
			
		||||
                            draggedItem.css('cursor', 'no-drop'); // TODO: this doesn't seem to work, use a class instead?
 | 
			
		||||
                        } else {
 | 
			
		||||
                            noDrop = false;
 | 
			
		||||
                            draggedItem.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                if (this.options.connectWith) {
 | 
			
		||||
 
 | 
			
		||||
@@ -596,18 +596,75 @@
 | 
			
		||||
                    eyeButton.show();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'conf-types': {
 | 
			
		||||
            value: "conf-types",
 | 
			
		||||
            label: "config",
 | 
			
		||||
            icon: "fa fa-cog",
 | 
			
		||||
            // hasValue: false,
 | 
			
		||||
            valueLabel: function (container, value) {
 | 
			
		||||
                // get the selected option (for access to the "name" and "module" properties)
 | 
			
		||||
                const _options = this._optionsCache || this.typeList.find(opt => opt.value === value)?.options || []
 | 
			
		||||
                const selectedOption = _options.find(opt => opt.value === value) || {
 | 
			
		||||
                    title: '',
 | 
			
		||||
                    name: '',
 | 
			
		||||
                    module: ''
 | 
			
		||||
                }
 | 
			
		||||
                container.attr("title", selectedOption.title) // set tooltip to the full path/id of the module/node
 | 
			
		||||
                container.text(selectedOption.name) // apply the "name" of the selected option
 | 
			
		||||
                // set "line-height" such as to make the "name" appear further up, giving room for the "module" to be displayed below the value
 | 
			
		||||
                container.css("line-height", "1.4em")
 | 
			
		||||
                // add the module name in smaller, lighter font below the value
 | 
			
		||||
                $('<div></div>').text(selectedOption.module).css({
 | 
			
		||||
                    // "font-family": "var(--red-ui-monospace-font)",
 | 
			
		||||
                    color: "var(--red-ui-tertiary-text-color)",
 | 
			
		||||
                    "font-size": "0.8em",
 | 
			
		||||
                    "line-height": "1em",
 | 
			
		||||
                    opacity: 0.8
 | 
			
		||||
                }).appendTo(container);
 | 
			
		||||
            },
 | 
			
		||||
            // hasValue: false,
 | 
			
		||||
            options: function () {
 | 
			
		||||
                if (this._optionsCache) {
 | 
			
		||||
                    return this._optionsCache
 | 
			
		||||
                }
 | 
			
		||||
                const configNodes = RED.nodes.registry.getNodeDefinitions({configOnly: true, filter: (def) => def.type !== "global-config"}).map((def) => {
 | 
			
		||||
                    // create a container with with 2 rows (row 1 for the name, row 2 for the module name in smaller, lighter font)
 | 
			
		||||
                    const container = $('<div style="display: flex; flex-direction: column; justify-content: space-between; row-gap: 1px;">')
 | 
			
		||||
                    const row1Name = $('<div>').text(def.type)
 | 
			
		||||
                    const row2Module = $('<div style="font-size: 0.8em; color: var(--red-ui-tertiary-text-color);">').text(def.set.module)
 | 
			
		||||
                    container.append(row1Name, row2Module)
 | 
			
		||||
        
 | 
			
		||||
                    return {
 | 
			
		||||
                        value: def.type,
 | 
			
		||||
                        name: def.type,
 | 
			
		||||
                        enabled: def.set.enabled ?? true,
 | 
			
		||||
                        local: def.set.local,
 | 
			
		||||
                        title: def.set.id, // tooltip e.g. "node-red-contrib-foo/bar"
 | 
			
		||||
                        module: def.set.module,
 | 
			
		||||
                        icon: container[0].outerHTML.trim(), // the typeInput will interpret this as html text and render it in the anchor
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                this._optionsCache = configNodes
 | 
			
		||||
                return configNodes
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    // For a type with options, check value is a valid selection
 | 
			
		||||
    // If !opt.multiple, returns the valid option object
 | 
			
		||||
    // if opt.multiple, returns an array of valid option objects
 | 
			
		||||
    // If not valid, returns null;
 | 
			
		||||
 | 
			
		||||
    function isOptionValueValid(opt, currentVal) {
 | 
			
		||||
        let _options = opt.options
 | 
			
		||||
        if (typeof _options === "function") {
 | 
			
		||||
            _options = _options.call(this)
 | 
			
		||||
        }
 | 
			
		||||
        if (!opt.multiple) {
 | 
			
		||||
            for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                op = opt.options[i];
 | 
			
		||||
            for (var i=0;i<_options.length;i++) {
 | 
			
		||||
                op = _options[i];
 | 
			
		||||
                if (typeof op === "string" && op === currentVal) {
 | 
			
		||||
                    return {value:currentVal}
 | 
			
		||||
                } else if (op.value === currentVal) {
 | 
			
		||||
@@ -624,8 +681,8 @@
 | 
			
		||||
                    currentValues[v] = true;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                op = opt.options[i];
 | 
			
		||||
            for (var i=0;i<_options.length;i++) {
 | 
			
		||||
                op = _options[i];
 | 
			
		||||
                var val = typeof op === "string" ? op : op.value;
 | 
			
		||||
                if (currentValues.hasOwnProperty(val)) {
 | 
			
		||||
                    delete currentValues[val];
 | 
			
		||||
@@ -1056,7 +1113,9 @@
 | 
			
		||||
            if (this.optionMenu) {
 | 
			
		||||
                this.optionMenu.remove();
 | 
			
		||||
            }
 | 
			
		||||
            this.menu.remove();
 | 
			
		||||
            if (this.menu) {
 | 
			
		||||
                this.menu.remove();
 | 
			
		||||
            }
 | 
			
		||||
            this.uiSelect.remove();
 | 
			
		||||
        },
 | 
			
		||||
        types: function(types) {
 | 
			
		||||
@@ -1089,7 +1148,7 @@
 | 
			
		||||
            this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
 | 
			
		||||
            if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
 | 
			
		||||
                if (!firstCall) {
 | 
			
		||||
                    this.type(this.typeList[0].value);
 | 
			
		||||
                    this.type(this.typeList[0]?.value || ""); // permit empty typeList
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.propertyType = null;
 | 
			
		||||
@@ -1126,6 +1185,11 @@
 | 
			
		||||
                var selectedOption = [];
 | 
			
		||||
                var valueToCheck = value;
 | 
			
		||||
                if (opt.options) {
 | 
			
		||||
                    let _options = opt.options
 | 
			
		||||
                    if (typeof opt.options === "function") {
 | 
			
		||||
                        _options = opt.options.call(this)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (opt.hasValue && opt.parse) {
 | 
			
		||||
                        var parts = opt.parse(value);
 | 
			
		||||
                        if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
 | 
			
		||||
@@ -1139,8 +1203,8 @@
 | 
			
		||||
                        checkValues = valueToCheck.split(",");
 | 
			
		||||
                    }
 | 
			
		||||
                    checkValues.forEach(function(valueToCheck) {
 | 
			
		||||
                        for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                            var op = opt.options[i];
 | 
			
		||||
                        for (var i=0;i<_options.length;i++) {
 | 
			
		||||
                            var op = _options[i];
 | 
			
		||||
                            if (typeof op === "string") {
 | 
			
		||||
                                if (op === valueToCheck || op === ""+valueToCheck) {
 | 
			
		||||
                                    selectedOption.push(that.activeOptions[op]);
 | 
			
		||||
@@ -1175,7 +1239,7 @@
 | 
			
		||||
        },
 | 
			
		||||
        type: function(type) {
 | 
			
		||||
            if (!arguments.length) {
 | 
			
		||||
                return this.propertyType;
 | 
			
		||||
                return this.propertyType || this.options?.default || '';
 | 
			
		||||
            } else {
 | 
			
		||||
                var that = this;
 | 
			
		||||
                if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
 | 
			
		||||
@@ -1276,6 +1340,10 @@
 | 
			
		||||
                        this.optionMenu = null;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (opt.options) {
 | 
			
		||||
                        let _options = opt.options
 | 
			
		||||
                        if (typeof _options === "function") {
 | 
			
		||||
                            _options = opt.options.call(this);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (this.optionExpandButton) {
 | 
			
		||||
                            this.optionExpandButton.hide();
 | 
			
		||||
                            this.optionExpandButton.shown = false;
 | 
			
		||||
@@ -1292,7 +1360,7 @@
 | 
			
		||||
                                this.valueLabelContainer.hide();
 | 
			
		||||
                            }
 | 
			
		||||
                            this.activeOptions = {};
 | 
			
		||||
                            opt.options.forEach(function(o) {
 | 
			
		||||
                            _options.forEach(function(o) {
 | 
			
		||||
                                if (typeof o === 'string') {
 | 
			
		||||
                                    that.activeOptions[o] = {label:o,value:o};
 | 
			
		||||
                                } else {
 | 
			
		||||
@@ -1312,7 +1380,7 @@
 | 
			
		||||
                                    if (validValues) {
 | 
			
		||||
                                        that._updateOptionSelectLabel(validValues)
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        op = opt.options[0];
 | 
			
		||||
                                        op = _options[0] || {value:""}; // permit zero options
 | 
			
		||||
                                        if (typeof op === "string") {
 | 
			
		||||
                                            this.value(op);
 | 
			
		||||
                                            that._updateOptionSelectLabel({value:op});
 | 
			
		||||
@@ -1331,7 +1399,7 @@
 | 
			
		||||
                                    that._updateOptionSelectLabel(validValues);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                var selectedOption = this.optionValue||opt.options[0];
 | 
			
		||||
                                var selectedOption = this.optionValue||_options[0];
 | 
			
		||||
                                if (opt.parse) {
 | 
			
		||||
                                    var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
 | 
			
		||||
                                    var parts = opt.parse(this.input.val(),selectedOptionObj);
 | 
			
		||||
@@ -1375,7 +1443,7 @@
 | 
			
		||||
                                    })
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            this.optionMenu = this._createMenu(opt.options,opt,function(v){
 | 
			
		||||
                            this.optionMenu = this._createMenu(_options,opt,function(v){
 | 
			
		||||
                                if (!opt.multiple) {
 | 
			
		||||
                                    that._updateOptionSelectLabel(that.activeOptions[v]);
 | 
			
		||||
                                    if (!opt.hasValue) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = $("<div></div>").css({
 | 
			
		||||
        const outerWrap = $("<div></div>").css({
 | 
			
		||||
            width: newWidth,
 | 
			
		||||
            display:'inline-flex'
 | 
			
		||||
            display: 'inline-flex'
 | 
			
		||||
        });
 | 
			
		||||
        var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(outerWrap);
 | 
			
		||||
        const select = $('<select id="' + selectId + '"></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);
 | 
			
		||||
        $('<a id="'+prefix+'-lookup-'+property+'" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
 | 
			
		||||
            .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 = $('<a id="' + buttonId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
 | 
			
		||||
            .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) {
 | 
			
		||||
                    $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
 | 
			
		||||
                    const option = $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></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__;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
@@ -1483,9 +1540,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);
 | 
			
		||||
@@ -1546,7 +1610,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);
 | 
			
		||||
                            }
 | 
			
		||||
@@ -2132,6 +2196,7 @@ RED.editor = (function() {
 | 
			
		||||
                filteredEditPanes[type] = filter
 | 
			
		||||
            }
 | 
			
		||||
            editPanes[type] = definition;
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        prepareConfigNodeSelect: prepareConfigNodeSelect,
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div></div>').appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.icon")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.label")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.inputType")).appendTo(topRow);
 | 
			
		||||
         $('<div class="red-env-ui-input-type-col">').text(RED._("editor.inputType")).appendTo(topRow);
 | 
			
		||||
 | 
			
		||||
         var row = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
 | 
			
		||||
         var typeOptions = {
 | 
			
		||||
             'input': {types:DEFAULT_ENV_TYPE_LIST},
 | 
			
		||||
             'select': {opts:[]},
 | 
			
		||||
             'spinner': {},
 | 
			
		||||
             'cred': {}
 | 
			
		||||
            'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES},
 | 
			
		||||
            'select': {opts:[]},
 | 
			
		||||
            'spinner': {},
 | 
			
		||||
            'cred': {}
 | 
			
		||||
         };
 | 
			
		||||
         if (ui.opts) {
 | 
			
		||||
             typeOptions[ui.type] = ui.opts;
 | 
			
		||||
@@ -260,15 +270,16 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
            labelInput.attr("placeholder",$(this).val())
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var inputCell = $('<div></div>').appendTo(row);
 | 
			
		||||
        var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
 | 
			
		||||
        var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row);
 | 
			
		||||
        var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
 | 
			
		||||
        if (ui.type === "input") {
 | 
			
		||||
            inputCellInput.val(ui.opts.types.join(","));
 | 
			
		||||
            uiInputTypeInput.val(ui.opts.types.join(","));
 | 
			
		||||
        }
 | 
			
		||||
        var checkbox;
 | 
			
		||||
        var selectBox;
 | 
			
		||||
 | 
			
		||||
        inputCellInput.typedInput({
 | 
			
		||||
        // the options presented in the UI section for an "input" type selection
 | 
			
		||||
        uiInputTypeInput.typedInput({
 | 
			
		||||
            types: [
 | 
			
		||||
                {
 | 
			
		||||
                    value:"input",
 | 
			
		||||
@@ -429,7 +440,7 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    ui.opts.opts = vals;
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                    uiInputTypeInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@@ -496,12 +507,13 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        delete ui.opts.max;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                    uiInputTypeInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                'conf-types',
 | 
			
		||||
                {
 | 
			
		||||
                    value:"none",
 | 
			
		||||
                    label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
 | 
			
		||||
@@ -519,14 +531,20 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
                // In the case of 'input' type, the typedInput uses the multiple-option
 | 
			
		||||
                // mode. Its value needs to be set to a comma-separately list of the
 | 
			
		||||
                // selected options.
 | 
			
		||||
                inputCellInput.typedInput('value',ui.opts.types.join(","))
 | 
			
		||||
                uiInputTypeInput.typedInput('value',ui.opts.types.join(","))
 | 
			
		||||
            } else if (ui.type === 'conf-types') {
 | 
			
		||||
                // In the case of 'conf-types' type, the typedInput will be populated
 | 
			
		||||
                // with a list of all config nodes types installed.
 | 
			
		||||
                // Restore the value to the last selected type
 | 
			
		||||
                uiInputTypeInput.typedInput('value', opt.type)
 | 
			
		||||
            } else {
 | 
			
		||||
                // No other type cares about `value`, but doing this will
 | 
			
		||||
                // force a refresh of the label now that `ui.opts` has
 | 
			
		||||
                // been updated.
 | 
			
		||||
                inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                uiInputTypeInput.typedInput('value',Date.now())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:typedinputtypechange. ui.type = ' + ui.type) }
 | 
			
		||||
            switch (ui.type) {
 | 
			
		||||
                case 'input':
 | 
			
		||||
                    valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
@@ -544,7 +562,7 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
                    valueField.typedInput('types',['cred']);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
 | 
			
		||||
                    valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST);
 | 
			
		||||
            }
 | 
			
		||||
            if (ui.type === 'checkbox') {
 | 
			
		||||
                valueField.typedInput('type','bool');
 | 
			
		||||
@@ -556,8 +574,46 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }).on("change", function(evt,type) {
 | 
			
		||||
            if (ui.type === 'input') {
 | 
			
		||||
                var types = inputCellInput.typedInput('value');
 | 
			
		||||
            const selectedType = $(this).typedInput('type') // the UI typedInput type
 | 
			
		||||
            if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:change. selectedType = ' + selectedType) }
 | 
			
		||||
            if (selectedType === 'conf-types') {
 | 
			
		||||
                const selectedConfigType = $(this).typedInput('value') || opt.type
 | 
			
		||||
                let activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
 | 
			
		||||
                if (!activeWorkspace) {
 | 
			
		||||
                    activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // get a list of all config nodes matching the selectedValue
 | 
			
		||||
                const configNodes = [];
 | 
			
		||||
                RED.nodes.eachConfig(function(config) {
 | 
			
		||||
                    if (config.type == selectedConfigType && (!config.z || config.z === activeWorkspace.id)) {
 | 
			
		||||
                        const modulePath = config._def?.set?.id || ''
 | 
			
		||||
                        let label = RED.utils.getNodeLabel(config, config.id) || config.id;
 | 
			
		||||
                        label += config.d ? ' ['+RED._('workspace.disabled')+']' : '';
 | 
			
		||||
                        const _config = {
 | 
			
		||||
                            _type: selectedConfigType,
 | 
			
		||||
                            value: config.id,
 | 
			
		||||
                            label: label,
 | 
			
		||||
                            title: modulePath ? modulePath + ' - ' + label : label,
 | 
			
		||||
                            enabled: config.d !== true,
 | 
			
		||||
                            disabled: config.d === true,
 | 
			
		||||
                        }
 | 
			
		||||
                        configNodes.push(_config);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                const tiTypes = {
 | 
			
		||||
                    value: selectedConfigType,
 | 
			
		||||
                    label: "config",
 | 
			
		||||
                    icon: "fa fa-cog",
 | 
			
		||||
                    options: configNodes,
 | 
			
		||||
                }
 | 
			
		||||
                valueField.typedInput('types', [tiTypes]);
 | 
			
		||||
                valueField.typedInput('type', selectedConfigType);
 | 
			
		||||
                valueField.typedInput('value', opt.value);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            } else if (ui.type === 'input') {
 | 
			
		||||
                var types = uiInputTypeInput.typedInput('value');
 | 
			
		||||
                ui.opts.types = (types === "") ? ["str"] : types.split(",");
 | 
			
		||||
                valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
            }
 | 
			
		||||
@@ -569,7 +625,7 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
        })
 | 
			
		||||
        // Set the input to the right type. This will trigger the 'typedinputtypechange'
 | 
			
		||||
        // event handler (just above ^^) to update the value if needed
 | 
			
		||||
        inputCellInput.typedInput('type',ui.type)
 | 
			
		||||
        uiInputTypeInput.typedInput('type',ui.type)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setLocale(l, list) {
 | 
			
		||||
 
 | 
			
		||||
@@ -909,17 +909,19 @@ RED.subflow = (function() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create interface for controlling env var UI definition
 | 
			
		||||
     * Build the edit dialog for a subflow template (creating/modifying a subflow template)
 | 
			
		||||
     * @param {Object} uiContainer - the jQuery container for the environment variable list
 | 
			
		||||
     * @param {Object} node - the subflow template node
 | 
			
		||||
     */
 | 
			
		||||
    function buildEnvControl(envList,node) {
 | 
			
		||||
    function buildEnvControl(uiContainer,node) {
 | 
			
		||||
        var tabs = RED.tabs.create({
 | 
			
		||||
            id: "subflow-env-tabs",
 | 
			
		||||
            onchange: function(tab) {
 | 
			
		||||
                if (tab.id === "subflow-env-tab-preview") {
 | 
			
		||||
                    var inputContainer = $("#subflow-input-ui");
 | 
			
		||||
                    var list = envList.editableList("items");
 | 
			
		||||
                    var list = uiContainer.editableList("items");
 | 
			
		||||
                    var exportedEnv = exportEnvList(list, true);
 | 
			
		||||
                    buildEnvUI(inputContainer, exportedEnv,node);
 | 
			
		||||
                    buildEnvUI(inputContainer, exportedEnv, node);
 | 
			
		||||
                }
 | 
			
		||||
                $("#subflow-env-tabs-content").children().hide();
 | 
			
		||||
                $("#" + tab.id).show();
 | 
			
		||||
@@ -957,12 +959,33 @@ RED.subflow = (function() {
 | 
			
		||||
        RED.editor.envVarList.setLocale(locale);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function buildEnvUIRow(row, tenv, ui, node) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Build a UI row for a subflow instance environment variable
 | 
			
		||||
     * Also used to build the UI row for subflow template preview
 | 
			
		||||
     * @param {JQuery} row - A form row element
 | 
			
		||||
     * @param {Object} tenv - A template environment variable
 | 
			
		||||
     * @param {String} tenv.name - The name of the environment variable
 | 
			
		||||
     * @param {String} tenv.type - The type of the environment variable
 | 
			
		||||
     * @param {String} tenv.value - The value set for this environment variable
 | 
			
		||||
     * @param {Object} tenv.parent - The parent environment variable
 | 
			
		||||
     * @param {String} tenv.parent.value - The value set for the parent environment variable
 | 
			
		||||
     * @param {String} tenv.parent.type - The type of the parent environment variable
 | 
			
		||||
     * @param {Object} tenv.ui - The UI configuration for the environment variable
 | 
			
		||||
     * @param {String} tenv.ui.icon - The icon for the environment variable
 | 
			
		||||
     * @param {Object} tenv.ui.label - The label for the environment variable
 | 
			
		||||
     * @param {String} tenv.ui.type - The type of the UI control for the environment variable
 | 
			
		||||
     * @param {Object} node - The subflow instance node
 | 
			
		||||
     */
 | 
			
		||||
    function buildEnvUIRow(row, tenv, node) {
 | 
			
		||||
        if(RED.subflow.debug) { console.log("buildEnvUIRow", tenv) }
 | 
			
		||||
        const ui = tenv.ui || {}
 | 
			
		||||
        ui.label = ui.label||{};
 | 
			
		||||
        if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
 | 
			
		||||
            ui.type = "cred";
 | 
			
		||||
            ui.opts = {};
 | 
			
		||||
        } else if (tenv.type === "conf-types") {
 | 
			
		||||
            ui.type = "conf-types"
 | 
			
		||||
            ui.opts = { types: ['conf-types'] }
 | 
			
		||||
        } else if (!ui.type) {
 | 
			
		||||
            ui.type = "input";
 | 
			
		||||
            ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
 | 
			
		||||
@@ -1006,9 +1029,10 @@ RED.subflow = (function() {
 | 
			
		||||
        if (tenv.hasOwnProperty('type')) {
 | 
			
		||||
            val.type = tenv.type;
 | 
			
		||||
        }
 | 
			
		||||
        const elId = getSubflowEnvPropertyName(tenv.name)
 | 
			
		||||
        switch(ui.type) {
 | 
			
		||||
            case "input":
 | 
			
		||||
                input = $('<input type="text">').css('width','70%').appendTo(row);
 | 
			
		||||
                input = $('<input type="text">').css('width','70%').attr('id', elId).appendTo(row);
 | 
			
		||||
                if (ui.opts.types && ui.opts.types.length > 0) {
 | 
			
		||||
                    var inputType = val.type;
 | 
			
		||||
                    if (ui.opts.types.indexOf(inputType) === -1) {
 | 
			
		||||
@@ -1035,7 +1059,7 @@ RED.subflow = (function() {
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case "select":
 | 
			
		||||
                input = $('<select>').css('width','70%').appendTo(row);
 | 
			
		||||
                input = $('<select>').css('width','70%').attr('id', elId).appendTo(row);
 | 
			
		||||
                if (ui.opts.opts) {
 | 
			
		||||
                    ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                        $('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
 | 
			
		||||
@@ -1046,7 +1070,7 @@ RED.subflow = (function() {
 | 
			
		||||
            case "checkbox":
 | 
			
		||||
                label.css("cursor","default");
 | 
			
		||||
                var cblabel = $('<label>').css('width','70%').appendTo(row);
 | 
			
		||||
                input = $('<input type="checkbox">').css({
 | 
			
		||||
                input = $('<input type="checkbox">').attr('id', elId).css({
 | 
			
		||||
                    marginTop: 0,
 | 
			
		||||
                    width: 'auto',
 | 
			
		||||
                    height: '34px'
 | 
			
		||||
@@ -1064,7 +1088,7 @@ RED.subflow = (function() {
 | 
			
		||||
                input.prop("checked",boolVal);
 | 
			
		||||
                break;
 | 
			
		||||
            case "spinner":
 | 
			
		||||
                input = $('<input>').css('width','70%').appendTo(row);
 | 
			
		||||
                input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
 | 
			
		||||
                var spinnerOpts = {};
 | 
			
		||||
                if (ui.opts.hasOwnProperty('min')) {
 | 
			
		||||
                    spinnerOpts.min = ui.opts.min;
 | 
			
		||||
@@ -1093,18 +1117,25 @@ RED.subflow = (function() {
 | 
			
		||||
                    default: 'cred'
 | 
			
		||||
                })
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        if (input) {
 | 
			
		||||
            input.attr('id',getSubflowEnvPropertyName(tenv.name))
 | 
			
		||||
            case "conf-types":
 | 
			
		||||
                // let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000);
 | 
			
		||||
                // clsId = clsId.replace(/\W/g, '-');
 | 
			
		||||
                // input = $('<input>').css('width','70%').addClass(clsId).attr('id', elId).appendTo(row);
 | 
			
		||||
                input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
 | 
			
		||||
                const _type = tenv.parent?.type || tenv.type;
 | 
			
		||||
                RED.editor.prepareConfigNodeSelect(node, tenv.name, _type, 'node-input-subflow-env', null, tenv);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create environment variable input UI
 | 
			
		||||
     * Build the edit form for a subflow instance
 | 
			
		||||
     * Also used to build the preview form in the subflow template edit dialog
 | 
			
		||||
     * @param uiContainer - container for UI
 | 
			
		||||
     * @param envList - env var definitions of template
 | 
			
		||||
     */
 | 
			
		||||
    function buildEnvUI(uiContainer, envList, node) {
 | 
			
		||||
        if(RED.subflow.debug) { console.log("buildEnvUI",envList) }
 | 
			
		||||
        uiContainer.empty();
 | 
			
		||||
        for (var i = 0; i < envList.length; i++) {
 | 
			
		||||
            var tenv = envList[i];
 | 
			
		||||
@@ -1112,7 +1143,7 @@ RED.subflow = (function() {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
 | 
			
		||||
            buildEnvUIRow(row,tenv, tenv.ui || {}, node);
 | 
			
		||||
            buildEnvUIRow(row, tenv, node);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // buildEnvUI
 | 
			
		||||
@@ -1185,6 +1216,9 @@ RED.subflow = (function() {
 | 
			
		||||
                                        delete ui.opts
 | 
			
		||||
                                    }
 | 
			
		||||
                                    break;
 | 
			
		||||
                                case "conf-types":
 | 
			
		||||
                                    delete ui.opts;
 | 
			
		||||
                                    break;
 | 
			
		||||
                                default:
 | 
			
		||||
                                    delete ui.opts;
 | 
			
		||||
                            }
 | 
			
		||||
@@ -1207,8 +1241,9 @@ RED.subflow = (function() {
 | 
			
		||||
        if (/^subflow:/.test(node.type)) {
 | 
			
		||||
            var subflowDef = RED.nodes.subflow(node.type.substring(8));
 | 
			
		||||
            if (subflowDef.env) {
 | 
			
		||||
                subflowDef.env.forEach(function(env) {
 | 
			
		||||
                subflowDef.env.forEach(function(env, i) {
 | 
			
		||||
                    var item = {
 | 
			
		||||
                        index: i,
 | 
			
		||||
                        name:env.name,
 | 
			
		||||
                        parent: {
 | 
			
		||||
                            type: env.type,
 | 
			
		||||
@@ -1273,6 +1308,7 @@ RED.subflow = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function exportSubflowInstanceEnv(node) {
 | 
			
		||||
        if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) }
 | 
			
		||||
        var env = [];
 | 
			
		||||
        // First, get the values for the SubflowTemplate defined properties
 | 
			
		||||
        //  - these are the ones with custom UI elements
 | 
			
		||||
@@ -1319,6 +1355,9 @@ RED.subflow = (function() {
 | 
			
		||||
                        item.type = 'bool';
 | 
			
		||||
                        item.value = ""+input.prop("checked");
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "conf-types":
 | 
			
		||||
                        item.value = input.val()
 | 
			
		||||
                        item.type = data.parent.value;
 | 
			
		||||
                }
 | 
			
		||||
                if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
 | 
			
		||||
                    env.push(item);
 | 
			
		||||
@@ -1332,8 +1371,15 @@ RED.subflow = (function() {
 | 
			
		||||
        return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Called by subflow.oneditprepare for both instances and templates
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Build the subflow edit form
 | 
			
		||||
     * Called by subflow.oneditprepare for both instances and templates
 | 
			
		||||
     * @param {"subflow"|"subflow-template"} type - the type of subflow being edited
 | 
			
		||||
     * @param {Object} node - the node being edited
 | 
			
		||||
     */
 | 
			
		||||
    function buildEditForm(type,node) {
 | 
			
		||||
        if(RED.subflow.debug) { console.log("buildEditForm",type,node) }
 | 
			
		||||
        if (type === "subflow-template") {
 | 
			
		||||
            // This is the tabbed UI that offers the env list - with UI options
 | 
			
		||||
            // plus the preview tab
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user