From de15a1c36fc705d988ee4b5741ecf91ae69316db Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 19 Nov 2020 14:28:50 +0000 Subject: [PATCH] Add subflow meta data edit pane --- .../editor-client/locales/en-US/editor.json | 11 ++ .../editor-client/locales/ja/editor.json | 6 + .../@node-red/editor-client/src/js/nodes.js | 1 + .../editor-client/src/js/ui/editor.js | 18 +++ .../editor-client/src/js/ui/subflow.js | 143 +++++++++++++++++- .../editor-client/src/js/ui/tab-info.js | 6 + 6 files changed, 184 insertions(+), 1 deletion(-) 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 0b92fc18c..c3ab1f8e0 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 @@ -339,6 +339,16 @@ "deleteSubflow": "delete subflow", "info": "Description", "category": "Category", + "module": "Module", + "license": "License", + "licenseOther": "Other", + "version": "Version", + "versionPlaceholder": "x.y.z", + "keys": "Keywords", + "keysPlaceholder": "Comma-separated keywords", + "author": "Author", + "authorPlaceholder": "Your Name ", + "desc": "Description", "env": { "restore": "Restore to subflow default", "remove": "Remove environment variable" @@ -1079,6 +1089,7 @@ "editor-tab": { "properties": "Properties", "envProperties": "Environment Variables", + "module": "Module Properties", "description": "Description", "appearance": "Appearance", "preview": "UI Preview", 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 f5774d57d..5d6c6ebf6 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -339,6 +339,12 @@ "deleteSubflow": "サブフローを削除", "info": "詳細", "category": "カテゴリ", + "module": "モジュール", + "license": "ライセンス", + "version": "バージョン", + "keys": "キーワード", + "author": "作者", + "desc": "説明", "env": { "restore": "デフォルト値に戻す", "remove": "環境変数を削除" 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 0d664ce3c..7b5bbe471 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 @@ -670,6 +670,7 @@ RED.nodes = (function() { node.in = []; node.out = []; node.env = n.env; + node.meta = n.meta; if (exportCreds) { var credentialSet = {}; 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 b712d3e99..e8c0daa39 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 @@ -2253,6 +2253,14 @@ RED.editor = (function() { changed = true; } + var newMeta = RED.subflow.exportSubflowModuleProperties(editing_node); + + if (!isSameObj(editing_node.meta,newMeta)) { + changes.meta = editing_node.meta; + editing_node.meta = newMeta; + changed = true; + } + if (changed) { var wasChanged = editing_node.changed; editing_node.changed = true; @@ -2359,6 +2367,16 @@ RED.editor = (function() { }; editorTabs.addTab(nodePropertiesTab); + var moduleTab = { + id: "editor-tab-module", + label: RED._("editor-tab.module"), + name: RED._("editor-tab.module"), + content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + iconClass: "fa fa-cube", + }; + editorTabs.addTab(moduleTab); + RED.subflow.buildModuleForm(moduleTab.content, editing_node); + var descriptionTab = { id: "editor-tab-description", label: RED._("editor-tab.description"), 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 49434f6a9..c8c649fac 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 @@ -47,6 +47,33 @@ RED.subflow = (function() { '
'+ ''; + var _subflowModulePaneTemplate = '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'; + function findAvailableSubflowIOPosition(subflow,isInput) { var pos = {x:50,y:30}; if (!isInput) { @@ -1893,6 +1920,118 @@ RED.subflow = (function() { buildPropertiesList(list, node); } + function setupInputValidation(input,validator) { + var errorTip; + var validateTimeout; + + var validateFunction = function() { + if (validateTimeout) { + return; + } + validateTimeout = setTimeout(function() { + var error = validator(input.val()); + // if (!error && errorTip) { + // errorTip.close(); + // errorTip = null; + // } else if (error && !errorTip) { + // errorTip = RED.popover.create({ + // tooltip: true, + // target:input, + // size: "small", + // direction: "bottom", + // content: error, + // }).open(); + // } + input.toggleClass("input-error",!!error); + validateTimeout = null; + }) + } + input.on("change keyup paste", validateFunction); + } + + function buildModuleForm(container, node) { + $(_subflowModulePaneTemplate).appendTo(container); + var moduleProps = node.meta || {}; + [ + 'module', + 'version', + 'author', + 'desc', + 'keywords', + 'license' + ].forEach(function(property) { + $("#subflow-input-module-"+property).val(moduleProps[property]||"") + }) + setupInputValidation($("#subflow-input-module-module"), function(newValue) { + newValue = newValue.trim(); + var isValid = newValue.length < 215; + isValid = isValid && !/^[._]/.test(newValue); + isValid = isValid && !/[A-Z]/.test(newValue); + if (newValue !== encodeURIComponent(newValue)) { + var m = /^@([^\/]+)\/([^\/]+)$/.exec(newValue); + if (m) { + isValid = isValid && (m[1] === encodeURIComponent(m[1]) && m[2] === encodeURIComponent(m[2])) + } else { + isValid = false; + } + } + return isValid?"":"Invalid module name" + }) + setupInputValidation($("#subflow-input-module-version"), function(newValue) { + newValue = newValue.trim(); + var isValid = newValue === "" || + /^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/.test(newValue); + return isValid?"":"Invalid version number" + }) + + var licenses = ["none", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "GPL-2.0", "GPL-3.0", "MIT", "MPL-2.0", "CDDL-1.0", "EPL-2.0"]; + var typedLicenses = { + types: licenses.map(function(l) { + return {value:l,label:l,hasValue:false} + }) + } + typedLicenses.types.push({ + value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg" + }) + if (!moduleProps.license) { + typedLicenses.default = "none"; + } else if (licenses.indexOf(moduleProps.license) > -1) { + typedLicenses.default = moduleProps.license; + } else { + typedLicenses.default = "_custom_"; + } + $("#subflow-input-module-license").typedInput(typedLicenses) + } + + function exportSubflowModuleProperties(node) { + var value; + var moduleProps = {}; + [ + 'module', + 'version', + 'author', + 'desc', + 'keywords' + ].forEach(function(property) { + value = $("#subflow-input-module-"+property).val().trim(); + if (value) { + moduleProps[property] = value; + } + }) + var selectedLicenseType = $("#subflow-input-module-license").typedInput("type"); + + if (selectedLicenseType === '_custom_') { + value = $("#subflow-input-module-license").val(); + if (value) { + moduleProps.license = value; + } + } else if (selectedLicenseType !== "none") { + moduleProps.license = selectedLicenseType; + } + return moduleProps; + } + + return { init: init, createSubflow: createSubflow, @@ -1906,9 +2045,11 @@ RED.subflow = (function() { buildEditForm: buildEditForm, buildPropertiesForm: buildPropertiesForm, + buildModuleForm: buildModuleForm, exportSubflowTemplateEnv: exportEnvList, - exportSubflowInstanceEnv: exportSubflowInstanceEnv + exportSubflowInstanceEnv: exportSubflowInstanceEnv, + exportSubflowModuleProperties: exportSubflowModuleProperties } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index d84f47f73..678356181 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -382,6 +382,12 @@ RED.sidebar.info = (function() { var category = subflowNode.category||"subflows"; $(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category})) $(''+RED._("sidebar.info.instances")+""+subflowUserCount+'').appendTo(tableBody); + if (subflowNode.meta) { + propRow = $(''+RED._("subflow.module")+'').appendTo(tableBody); + $(propRow.children()[1]).text(subflowNode.meta.module||"") + propRow = $(''+RED._("subflow.version")+'').appendTo(tableBody); + $(propRow.children()[1]).text(subflowNode.meta.version||"") + } } // var helpText = "";