diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 9a53a870c..6e88bf14c 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -321,6 +321,31 @@ "description": "Description", "show": "Show", "hide": "Hide", + "locale": "Select UI Language", + "icon": "Icon", + "inputType": "Input type", + "previewUI": "Preview UI", + "previewOK": "Preview OK", + "types": { + "str": "string", + "num": "number", + "bool": "bool", + "json": "json", + "bin": "buffer", + "env": "env var", + "no-value": "no value" + }, + "menu": { + "input": "input", + "select": "select", + "checkbox": "checkbox", + "spinner": "spinner", + "hidden": "label only" + }, + "spinner": { + "min": "min", + "max": "max" + }, "errors": { "scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it", "invalidProperties": "Invalid properties:" @@ -939,9 +964,11 @@ }, "editor-tab": { "properties": "Properties", + "envProperties": "Environment Variables", "description": "Description", "appearance": "Appearance", - "env": "Environment Variables" + "preview": "UI Preview", + "defaultValue": "Default value" }, "languages" : { "de": "German", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 9d6ceaf6a..57ffa6e2c 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -321,6 +321,31 @@ "description": "詳細", "show": "表示", "hide": "非表示", + "locale": "UI言語の選択", + "icon": "記号", + "inputType": "入力形式", + "previewUI": "UI確認", + "previewOK": "確認OK", + "types": { + "str": "文字列", + "num": "数値", + "bool": "真偽", + "json": "JSON", + "bin": "バッファ", + "env": "環境変数", + "no-value": "値無し" + }, + "menu": { + "input": "入力", + "select": "選択", + "checkbox": "チェックボックス", + "spinner": "数値", + "hidden": "ラベルのみ" + }, + "spinner": { + "min": "最小", + "max": "最大" + }, "errors": { "scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします", "invalidProperties": "プロパティが不正です:" @@ -940,7 +965,7 @@ "properties": "プロパティ", "description": "説明", "appearance": "外観", - "env": "環境変数" + "env": "サブフロープロパティ" }, "languages": { "de": "ドイツ語", diff --git a/packages/node_modules/@node-red/editor-client/src/js/i18n.js b/packages/node_modules/@node-red/editor-client/src/js/i18n.js index 87afc3ebc..f1d0edfaf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/i18n.js +++ b/packages/node_modules/@node-red/editor-client/src/js/i18n.js @@ -50,6 +50,19 @@ RED.i18n = (function() { } }, + lang: function() { + // Gets the active message catalog language. This is based on what + // locale the editor is using and what languages are available. + // + var preferredLangs = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage()); + var knownLangs = RED.settings.theme("languages")||["en-US"]; + for (var i=0;i -1) { + return preferredLangs[i] + } + } + return 'end-US' + }, loadNodeCatalog: function(namespace,done) { var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage()); var toLoad = languageList.length; 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 bca4fab04..c2df8335e 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 @@ -399,14 +399,14 @@ RED.nodes = (function() { inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, oneditresize: function(size) { - var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); + // 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"))); - $("#node-input-env-container").editableList('height',height-80); + // 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); }, set:{ module: "node-red" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index d4fa53dfa..06ccc97d9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -34,6 +34,8 @@ * - addItem(itemData) * - insertItemAt : function(data,index) - add an item at the specified index * - removeItem(itemData) + * - getItemAt(index) + * - indexOf(itemData) * - width(width) * - height(height) * - items() @@ -186,7 +188,11 @@ } }, _destroy: function() { - this.topContainer.remove(); + if (this.topContainer) { + var tc = this.topContainer; + delete this.topContainer; + tc.remove(); + } }, _refreshFilter: function() { var that = this; @@ -232,6 +238,23 @@ this.uiHeight = desiredHeight; this._resize(); }, + getItemAt: function(index) { + var items = this.items(); + if (index >= 0 && index < items.length) { + return $(items[index]).data('data'); + } else { + return; + } + }, + indexOf: function(data) { + var items = this.items(); + for (var i=0;i'); + panel.css({ display: "none" }); + panel.appendTo(document.body); + content.appendTo(panel); + var closeCallback; + + function hide() { + $(document).off("mousedown.red-ui-popover-panel-close"); + panel.hide(); + panel.css({ + height: "auto" + }); + panel.remove(); + } + function show(options) { + var closeCallback = options.onclose; + var target = options.target; + var align = options.align || "left"; + + var pos = target.offset(); + var targetWidth = target.width(); + var targetHeight = target.height(); + var panelHeight = panel.height(); + var panelWidth = panel.width(); + + var top = (targetHeight+pos.top); + if (top+panelHeight > $(window).height()) { + top -= (top+panelHeight)-$(window).height() + 5; + } + if (top < 0) { + panelHeight.height(panelHeight+top) + top = 0; + } + if (align === "left") { + panel.css({ + top: top+"px", + left: (pos.left)+"px", + }); + } else if(align === "right") { + panel.css({ + top: top+"px", + left: (pos.left-panelWidth)+"px", + }); + } + panel.slideDown(100); + + $(document).on("mousedown.red-ui-popover-panel-close", function(event) { + if(!$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) { + if (closeCallback) { + closeCallback(); + } + hide(); + } + // if ($(event.target).closest(target).length) { + // event.preventDefault(); + // } + }) + } + return { + container: panel, + show:show, + hide:hide + } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index daef61f64..7b3ed45f2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -81,7 +81,7 @@ } }, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"}, - date: {value:"date",label:"timestamp",hasValue:false}, + date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false}, jsonata: { value: "jsonata", label: "expression", @@ -298,7 +298,8 @@ that.uiSelect.addClass('red-ui-typedInput-focus'); }); - this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButtonIcon = $('').appendTo(this.optionExpandButton); this.type(this.options.default||this.typeList[0].value); }catch(err) { console.log(err.stack); @@ -336,6 +337,17 @@ menu.css({ height: "auto" }); + + if (menu.opts.multiple) { + var selected = []; + menu.find('input[type="checkbox"]').each(function() { + if ($(this).prop("checked")) { + selected.push($(this).data('value')) + } + }) + menu.callback(selected); + } + if (this.elementDiv.is(":visible")) { this.input.trigger("focus"); } else if (this.optionSelectTrigger.is(":visible")){ @@ -344,10 +356,12 @@ this.selectTrigger.trigger("focus"); } }, - _createMenu: function(opts,callback) { + _createMenu: function(menuOptions,opts,callback) { var that = this; - var menu = $("
").addClass("red-ui-typedInput-options"); - opts.forEach(function(opt) { + var menu = $("
").addClass("red-ui-typedInput-options red-ui-editor-dialog"); + menu.opts = opts; + menu.callback = callback; + menuOptions.forEach(function(opt) { if (typeof opt === 'string') { opt = {value:opt,label:opt}; } @@ -369,12 +383,20 @@ if (!opt.icon && !opt.label) { op.text(opt.value); } + var cb; + if (opts.multiple) { + cb = $('').css("pointer-events","none").data('value',opt.value).prependTo(op).on("mousedown", function(evt) { evt.preventDefault() }); + } op.on("click", function(event) { event.preventDefault(); event.stopPropagation(); - callback(opt.value); - that._hideMenu(menu); + if (!opts.multiple) { + callback(opt.value); + that._hideMenu(menu); + } else { + cb.prop("checked",!cb.prop("checked")); + } }); }); menu.css({ @@ -398,9 +420,6 @@ } evt.stopPropagation(); }) - - - return menu; }, @@ -409,11 +428,22 @@ this.disarmClick = false; return } + if (menu.opts.multiple) { + var selected = {}; + this.value().split(",").forEach(function(f) { + selected[f] = true; + }) + menu.find('input[type="checkbox"]').each(function() { + $(this).prop("checked",selected[$(this).data('value')]) + }) + } + + var that = this; var pos = relativeTo.offset(); var height = relativeTo.height(); var menuHeight = menu.height(); - var top = (height+pos.top-3); + var top = (height+pos.top); if (top+menuHeight > $(window).height()) { top -= (top+menuHeight)-$(window).height()+5; } @@ -423,7 +453,7 @@ } menu.css({ top: top+"px", - left: (2+pos.left)+"px", + left: (pos.left)+"px", }); menu.slideDown(100); this._delay(function() { @@ -471,7 +501,7 @@ this._getLabelWidth(this.selectTrigger, function(labelWidth) { that.elementDiv.css('left',labelWidth+"px"); that.valueLabelContainer.css('left',labelWidth+"px"); - if (that.optionExpandButton.is(":visible")) { + if (that.optionExpandButton.shown) { that.elementDiv.css('right',"22px"); that.valueLabelContainer.css('right',"22px"); } else { @@ -496,7 +526,7 @@ } else { that.optionSelectLabel.css({'left':'0'}) that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); - if (!that.optionExpandButton.is(":visible")) { + if (!that.optionExpandButton.shown) { that.elementDiv.css({'right':0}); that.input.css({ 'border-top-right-radius': '4px', @@ -511,25 +541,35 @@ _updateOptionSelectLabel: function(o) { var opt = this.typeMap[this.propertyType]; this.optionSelectLabel.empty(); - if (o.icon) { - if (o.icon.indexOf("<") === 0) { - $(o.icon).prependTo(this.optionSelectLabel); - } else if (o.icon.indexOf("/") !== -1) { - // url - $('',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel); + if (this.typeMap[this.propertyType].valueLabel) { + if (opt.multiple) { + this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o); } else { - // icon class - $('',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); + this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o.value); + } + } else if (!opt.multiple) { + if (o.icon) { + if (o.icon.indexOf("<") === 0) { + $(o.icon).prependTo(this.optionSelectLabel); + } else if (o.icon.indexOf("/") !== -1) { + // url + $('',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel); + } else { + // icon class + $('',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); + } + } else if (o.label) { + this.optionSelectLabel.text(o.label); + } else { + this.optionSelectLabel.text(o.value); + } + if (opt.hasValue) { + this.optionValue = o.value; + this._resize(); + this.input.trigger('change',this.propertyType,this.value()); } - } else if (o.label) { - this.optionSelectLabel.text(o.label); } else { - this.optionSelectLabel.text(o.value); - } - if (opt.hasValue) { - this.optionValue = o.value; - this._resize(); - this.input.trigger('change',this.propertyType,this.value()); + this.optionSelectLabel.text(o.length+" selected"); } }, _destroy: function() { @@ -558,7 +598,7 @@ if (this.menu) { this.menu.remove(); } - this.menu = this._createMenu(this.typeList, function(v) { that.type(v) }); + this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); if (currentType && !this.typeMap.hasOwnProperty(currentType)) { this.type(this.typeList[0].value); } else { @@ -572,38 +612,51 @@ this._resize(); }, value: function(value) { + var that = this; + var opt = this.typeMap[this.propertyType]; if (!arguments.length) { var v = this.input.val(); - if (this.typeMap[this.propertyType].export) { - v = this.typeMap[this.propertyType].export(v,this.optionValue) + if (opt.export) { + v = opt.export(v,this.optionValue) } return v; } else { - var selectedOption; - if (this.typeMap[this.propertyType].options) { - for (var i=0;i',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); } - } else { + } + if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { this.selectLabel.text(opt.label); } if (this.optionMenu) { @@ -646,6 +700,7 @@ if (opt.options) { if (this.optionExpandButton) { this.optionExpandButton.hide(); + this.optionExpandButton.shown = false; } if (this.optionSelectTrigger) { this.optionSelectTrigger.show(); @@ -668,36 +723,50 @@ if (!that.activeOptions.hasOwnProperty(that.optionValue)) { that.optionValue = null; } - this.optionMenu = this._createMenu(opt.options,function(v){ - that._updateOptionSelectLabel(that.activeOptions[v]); - if (!opt.hasValue) { - that.value(that.activeOptions[v].value) - } - }); + var op; if (!opt.hasValue) { - var currentVal = this.input.val(); var validValue = false; - for (var i=0;i'); + var content = opt.expand.content.call(that,container); + var panel = RED.popover.panel(container); + panel.container.css({ + width:that.valueLabelContainer.width() + }); + if (opt.expand.minWidth) { + panel.container.css({ + minWidth: opt.expand.minWidth+"px" + }); + } + panel.show({ + target:that.optionExpandButton, + onclose:content.onclose, + align: "right" + }); + } }) } else { + this.optionExpandButton.shown = false; this.optionExpandButton.hide(); } } + this._trigger("typechange",null,this.propertyType); this.input.trigger('change',this.propertyType,this.value()); } if (!image) { 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 1abace286..c3d1f5bd7 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 @@ -19,7 +19,6 @@ */ RED.editor = (function() { - var editStack = []; var editing_node = null; var editing_config_node = null; @@ -546,148 +545,7 @@ RED.editor = (function() { return label; } - function buildEnvForm(container, node) { - var env_container = $('#node-input-env-container'); - env_container - .css({ - 'min-height':'150px', - 'min-width':'450px' - }) - .editableList({ - addItem: function(container, i, opt) { - var row = $('
').appendTo(container); - if (opt.parent) { - $('
', { - class:"uneditable-input", - style: "margin-left: 5px; width: calc(40% - 8px)", - }).appendTo(row).text(opt.name); - } else { - $('', { - class: "node-input-env-name", - type: "text", - style: "margin-left: 5px; width: calc(40% - 8px)", - placeholder: RED._("common.label.name") - }).attr("autocomplete","disable").appendTo(row).val(opt.name); - } - var valueField = $('',{ - class: "node-input-env-value", - type: "text", - style: "margin-left: 5px; width: calc(60% - 8px)" - }).attr("autocomplete","disable").appendTo(row) - - valueField.typedInput({default:'str', - types:['str','num','bool','json','bin','env'] - }); - - valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); - valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); - - var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(container); - $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); - container.parent().addClass("red-ui-editableList-item-removable"); - if (opt.parent) { - if ((opt.value !== undefined) && (opt.value !== opt.parent.value || opt.type !== opt.parent.type)) { - actionButton.show(); - } else { - actionButton.hide(); - } - var restoreTip = RED.popover.tooltip(actionButton,RED._("subflow.env.restore")); - valueField.on("change", function(evt) { - var newType = valueField.typedInput('type'); - var newValue = valueField.typedInput('value'); - if (newType === opt.parent.type && newValue === opt.parent.value) { - actionButton.hide(); - } else { - actionButton.show(); - } - }) - actionButton.on("click", function(evt) { - evt.preventDefault(); - restoreTip.close(); - valueField.typedInput('type', opt.parent.type); - valueField.typedInput('value', opt.parent.value); - }) - } else { - var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); - actionButton.on("click", function(evt) { - evt.preventDefault(); - removeTip.close(); - container.parent().addClass("red-ui-editableList-item-deleting") - container.fadeOut(300, function() { - env_container.editableList('removeItem',opt); - }); - }); - } - }, - sortable: false, - removable: false - }); - var parentEnv = {}; - var envList = []; - if (/^subflow:/.test(node.type)) { - var subflowDef = RED.nodes.subflow(node.type.substring(8)); - if (subflowDef.env) { - subflowDef.env.forEach(function(env) { - var item = { - name:env.name, - parent: { - type: env.type, - value: env.value - } - } - envList.push(item); - parentEnv[env.name] = item; - }) - } - } - - if (node.env) { - for (var i = 0; i < node.env.length; i++) { - var env = node.env[i]; - if (parentEnv.hasOwnProperty(env.name)) { - parentEnv[env.name].type = env.type; - parentEnv[env.name].value = env.value; - } else { - envList.push({ - name: env.name, - type: env.type, - value: env.value - }); - } - } - } - envList.forEach(function(env) { - env_container.editableList('addItem', env); - }) - } - - function exportEnvList(list) { - if (list) { - var env = []; - list.each(function(i) { - var entry = $(this); - var item = entry.data('data'); - var name = (item.parent?item.name:entry.find(".node-input-env-name").val()).trim(); - if (name !== "") { - var valueInput = entry.find(".node-input-env-value"); - var value = valueInput.typedInput("value"); - var type = valueInput.typedInput("type"); - if (!item.parent || (item.parent.value !== value || item.parent.type !== type)) { - var item = { - name: name, - type: type, - value: value - }; - env.push(item); - } - } - }); - return env; - } - return null; - } - - function isSameEnv(env0, env1) { + function isSameObj(env0, env1) { return (JSON.stringify(env0) === JSON.stringify(env1)); } @@ -713,8 +571,8 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if ((type === "subflow") || (type === "subflow-template")) { - buildEnvForm(dialogForm, node); + if (type === "subflow-template" || type === "subflow") { + RED.subflow.buildEditForm(dialogForm,type,node); } // Add dummy fields to prevent 'Enter' submitting the form in some @@ -857,7 +715,7 @@ RED.editor = (function() { } return result; } - function showIconPicker(container, node, iconPath, done) { + function showIconPicker(container, node, iconPath, faOnly, done) { var containerPos = container.offset(); var pickerBackground = $('
').css({ position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20 @@ -912,7 +770,14 @@ RED.editor = (function() { done(null); }); var iconSets = RED.nodes.getIconSets(); + var backgroundColor = node && RED.utils.getNodeColor(node.type, node._def); + if (!node && faOnly) { + iconList.addClass("red-ui-icon-list-dark"); + } Object.keys(iconSets).forEach(function(moduleName) { + if (faOnly && (moduleName !== "font-awesome")) { + return; + } var icons = iconSets[moduleName]; if (icons.length > 0) { // selectIconModule.append($("").val(moduleName).text(moduleName)); @@ -921,10 +786,13 @@ RED.editor = (function() { icons.forEach(function(icon) { var iconDiv = $('
',{class:"red-ui-icon-list-icon"}).appendTo(iconList); var nodeDiv = $('
',{class:"red-ui-search-result-node"}).appendTo(iconDiv); - var colour = RED.utils.getNodeColor(node.type, node._def); var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon; iconDiv.data('icon',icon_url); - nodeDiv.css('backgroundColor',colour); + if (node) { + nodeDiv.css({ + 'backgroundColor': backgroundColor + }); + } var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, true); @@ -1049,7 +917,7 @@ RED.editor = (function() { } else { iconPath = RED.utils.getDefaultNodeIcon(node._def, node); } - showIconPicker(iconRow,node,iconPath,function(newIcon) { + showIconPicker(iconRow,node,iconPath,false,function(newIcon) { $("#red-ui-editor-node-icon").text(newIcon||""); var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon}); RED.utils.createIconElement(icon_url, iconContainer, true); @@ -1093,6 +961,7 @@ RED.editor = (function() { } } + function updateLabels(editing_node, changes, outputMap) { var inputLabels = $("#red-ui-editor-node-label-form-inputs").children().find("input"); var outputLabels = $("#red-ui-editor-node-label-form-outputs").children().find("input"); @@ -1483,13 +1352,13 @@ RED.editor = (function() { if (type === "subflow") { var old_env = editing_node.env; - var new_env = exportEnvList($("#node-input-env-container").editableList("items")); - if (!isSameEnv(old_env, new_env)) { + var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node); + if (!isSameObj(old_env, new_env)) { editing_node.env = new_env; changes.env = editing_node.env; changed = true; } - } + } if (changed) { var wasChanged = editing_node.changed; @@ -1607,6 +1476,19 @@ RED.editor = (function() { buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns,node); editorTabs.addTab(nodePropertiesTab); + if (/^subflow:/.test(node.type)) { + var subflowPropertiesTab = { + id: "editor-subflow-envProperties", + label: RED._("editor-tab.envProperties"), + name: RED._("editor-tab.envProperties"), + content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + iconClass: "fa fa-list" + }; + + RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node); + editorTabs.addTab(subflowPropertiesTab); + } + if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { var descriptionTab = { id: "editor-tab-description", @@ -1875,7 +1757,6 @@ RED.editor = (function() { var configId = editing_config_node.id; var configAdding = adding; var configTypeDef = RED.nodes.getType(configType); - if (configTypeDef.oneditcancel) { // TODO: what to pass as this to call if (configTypeDef.oneditcancel) { @@ -2234,6 +2115,15 @@ RED.editor = (function() { editing_node.category = newCategory; changed = true; } + + var old_env = editing_node.env; + var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); + if (!isSameObj(old_env, new_env)) { + editing_node.env = new_env; + changes.env = editing_node.env; + changed = true; + } + RED.palette.refresh(); if (changed) { @@ -2274,15 +2164,18 @@ RED.editor = (function() { ], resize: function(size) { $(".red-ui-tray-content").height(size.height - 50); - // var form = $(".red-ui-tray-content form").height(size.height - 50 - 40); - var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); - var height = size.height; - for (var i=0; idiv:not(#subflow-env-tabs-content)"); + var height = size.height; + for (var i=0; idiv.node-input-env-container-row"); + // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("#node-input-env-container").editableList('height',height-95); } - var editorRow = $("#dialog-form>div.node-input-env-container-row"); - height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("#node-input-env-container").editableList('height',height-60); }, open: function(tray) { var trayFooter = tray.find(".red-ui-tray-footer"); @@ -2518,6 +2411,8 @@ RED.editor = (function() { validateNode: validateNode, updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo + showIconPicker:showIconPicker, + /** * Show a type editor. * @param {string} type - the type to display diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index a8be067d6..66d016d92 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -16,16 +16,29 @@ RED.subflow = (function() { + var currentLocale = "en-US"; + var _subflowEditTemplate = ''; var _subflowTemplateEditTemplate = ''; function findAvailableSubflowIOPosition(subflow,isInput) { @@ -747,6 +760,946 @@ RED.subflow = (function() { RED.view.redraw(true); } + + /** + * Create interface for controlling env var UI definition + */ + function buildEnvControl(envList) { + + 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 exportedEnv = exportEnvList(list, true); + buildEnvUI(inputContainer, exportedEnv); + } + $("#subflow-env-tabs-content").children().hide(); + $("#" + tab.id).show(); + } + }); + tabs.addTab({ + id: "subflow-env-tab-edit", + label: RED._("editor-tab.envProperties") + }); + tabs.addTab({ + id: "subflow-env-tab-preview", + label: RED._("editor-tab.preview") + }); + + var localesList = RED.settings.theme("languages") + .map(function(lc) { var name = RED._("languages."+lc); return {text: (name ? name : lc), val: lc}; }) + .sort(function(a, b) { return a.text.localeCompare(b.text) }); + RED.popover.tooltip($(".node-input-env-locales-row i"),RED._("editor.locale")) + var locales = $("#subflow-input-env-locale") + localesList.forEach(function(item) { + var opt = { + value: item.val + }; + if (item.val === "en-US") { // make en-US default selected + opt.selected = ""; + } + $("
').appendTo(container); + var nameField = null; + var valueField = null; + + // if (opt.parent) { + // buildEnvUIRow(envRow,opt,opt.parent.ui||{}) + // } else { + nameField = $('', { + class: "node-input-env-name", + type: "text", + placeholder: RED._("common.label.name") + }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); + valueField = $('',{ + style: "width:100%", + class: "node-input-env-value", + type: "text", + }).attr("autocomplete","disable").appendTo(envRow) + valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); + valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); + valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); + // } + + + opt.nameField = nameField; + opt.valueField = valueField; + + if (!opt.parent) { + var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); + $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); + var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); + actionButton.on("click", function(evt) { + evt.preventDefault(); + removeTip.close(); + container.parent().addClass("red-ui-editableList-item-deleting") + container.fadeOut(300, function() { + envContainer.editableList('removeItem',opt); + }); + }); + } + + if (isTemplateNode) { + // Add the UI customisation row + // if `opt.ui` does not exist, then apply defaults. If these + // defaults do not change then they will get stripped off + // before saving. + opt.ui = opt.ui || { + icon: "", + label: {}, + type: "input", + opts: {types:['str','num','bool','json','bin','env']} + } + opt.ui.label = opt.ui.label || {}; + opt.ui.type = opt.ui.type || "input"; + + var uiRow = $('
').appendTo(container).hide(); + // save current info for reverting on cancel + // var copy = $.extend(true, {}, ui); + + $('').prependTo(envRow).on("click", function (evt) { + evt.preventDefault(); + if ($(this).hasClass('expanded')) { + uiRow.slideUp(); + $(this).removeClass('expanded'); + } else { + uiRow.slideDown(); + $(this).addClass('expanded'); + } + }); + + buildEnvEditRow(uiRow, opt.ui, nameField, valueField); + nameField.trigger('change'); + } + }, + sortable: ".red-ui-editableList-item-handle", + removable: false + }); + var parentEnv = {}; + var envList = []; + if (/^subflow:/.test(node.type)) { + var subflowDef = RED.nodes.subflow(node.type.substring(8)); + if (subflowDef.env) { + subflowDef.env.forEach(function(env) { + var item = { + name:env.name, + parent: { + type: env.type, + value: env.value, + ui: env.ui + } + } + envList.push(item); + parentEnv[env.name] = item; + }) + } + } + + if (node.env) { + for (var i = 0; i < node.env.length; i++) { + var env = node.env[i]; + if (parentEnv.hasOwnProperty(env.name)) { + parentEnv[env.name].type = env.type; + parentEnv[env.name].value = env.value; + } else { + envList.push({ + name: env.name, + type: env.type, + value: env.value, + ui: env.ui + }); + } + } + } + envList.forEach(function(env) { + if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') { + return; + } + if (!isTemplateNode && env.parent) { + return; + } + envContainer.editableList('addItem', JSON.parse(JSON.stringify(env))); + }); + } + + /** + * Create UI edit interface for environment variable + * @param container - container + * @param env - env var definition + * @param nameField - name field of env var + * @param valueField - value field of env var + */ + function buildEnvEditRow(container, ui, nameField, valueField) { + container.addClass("red-ui-editor-subflow-env-ui-row") + var topRow = $('
').appendTo(container); + $('
').appendTo(topRow); + $('
').text(RED._("editor.icon")).appendTo(topRow); + $('
').text(RED._("editor.label")).appendTo(topRow); + $('
').text(RED._("editor.inputType")).appendTo(topRow); + + var row = $('
').appendTo(container); + $('
').appendTo(row); + + var typeOptions = { + 'input': {types:['str','num','bool','json','bin','env']}, + 'select': {opts:[]}, + 'spinner': {} + }; + if (ui.opts) { + typeOptions[ui.type] = ui.opts; + } else { + // Pick up the default values if not otherwise provided + ui.opts = typeOptions[ui.type]; + } + var iconCell = $('
').appendTo(row); + + var iconButton = $('').appendTo(iconCell); + iconButton.on("click", function(evt) { + evt.preventDefault(); + var icon = ui.icon || ""; + var iconPath = (icon ? RED.utils.separateIconPath(icon) : {}); + RED.editor.showIconPicker(row, null, iconPath, true, function (newIcon) { + iconButton.empty(); + var path = newIcon || ""; + var newPath = RED.utils.separateIconPath(path); + if (newPath) { + $('').addClass(newPath.file).appendTo(iconButton); + } + ui.icon = path; + }); + }) + + if (ui.icon) { + var newPath = RED.utils.separateIconPath(ui.icon); + $('').appendTo(iconButton); + } + + var labelCell = $('
').appendTo(row); + + var label = ui.label && ui.label[currentLocale] || ""; + var labelInput = $('').val(label).appendTo(labelCell); + ui.labelField = labelInput; + labelInput.on('change', function(evt) { + ui.label = ui.label || {}; + var val = $(this).val().trim(); + if (val === "") { + delete ui.label[currentLocale]; + } else { + ui.label[currentLocale] = val; + } + }) + var labelIcon = $('').appendTo(labelCell); + RED.popover.tooltip(labelIcon,function() { + var langs = Object.keys(ui.label); + var content = $("
"); + if (langs.indexOf(currentLocale) === -1) { + langs.push(currentLocale); + langs.sort(); + } + langs.forEach(function(l) { + var row = $('
').appendTo(content); + $('').css({display:"inline-block",width:"50px"}).text(l+(l===currentLocale?"*":"")).appendTo(row); + $('').text(ui.label[l]||"").appendTo(row); + }); + return content; + }) + + nameField.on('change',function(evt) { + labelInput.attr("placeholder",$(this).val()) + }); + + var inputCell = $('
').appendTo(row); + var inputCellInput = $('').css("width","100%").appendTo(inputCell); + if (ui.type === "input") { + inputCellInput.val(ui.opts.types.join(",")); + } + var checkbox; + var selectBox; + + inputCellInput.typedInput({ + types: [ + {value:"input", + label:"input", icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ + {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, + {value:"num",label:"number",icon:"red/images/typedInput/09.svg"}, + {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg"}, + {value:"json",label:"JSON",icon:"red/images/typedInput/json.svg"}, + {value: "bin",label: "buffer",icon: "red/images/typedInput/bin.svg"}, + {value: "env",label: "env variable",icon: "red/images/typedInput/env.svg"} + ], + default: ['str','num','bool','json','bin','env'], + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); + + var input = $('
').appendTo(innerContainer); + $('').appendTo(input); + if (value.length) { + value.forEach(function(v) { + $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); + }) + } else { + $("").css({ + "color":"#aaa", + "padding-left": "4px" + }).text("select types...").appendTo(input); + } + } + }, + {value:"select", + label:"select", icon:"fa fa-tasks",showLabel:false, + valueLabel: function(container,value) { + container.css("padding","0"); + + selectBox = $('').appendTo(container); + if (ui.opts && Array.isArray(ui.opts.opts)) { + ui.opts.opts.forEach(function(o) { + var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); + // $('