From 6aecc3915c51a939b25f11ba1c2ef4addcf4d7c9 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 19 Aug 2021 21:15:13 +0900 Subject: [PATCH 01/27] add support of environtment variable for tab & group --- .../@node-red/editor-client/src/js/nodes.js | 8 +- .../editor-client/src/js/ui/editor.js | 45 +++++ .../editor-client/src/js/ui/group.js | 7 +- .../editor-client/src/js/ui/subflow.js | 62 ++++--- .../editor-client/src/js/ui/workspaces.js | 169 +++++++++++++----- .../@node-red/runtime/lib/flows/Flow.js | 66 +++++++ .../@node-red/runtime/lib/flows/Subflow.js | 58 ++++++ .../@node-red/runtime/lib/flows/index.js | 9 + .../@node-red/runtime/lib/flows/util.js | 13 +- .../@node-red/runtime/lib/nodes/Node.js | 1 + .../@node-red/runtime/lib/nodes/index.js | 2 +- .../node_modules/@node-red/util/lib/util.js | 87 +++++++-- test/nodes/core/function/15-change_spec.js | 71 ++++++++ test/nodes/subflow/subflow_spec.js | 120 +++++++++++++ .../@node-red/runtime/lib/flows/Flow_spec.js | 50 ++++++ 15 files changed, 673 insertions(+), 95 deletions(-) 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 ee3542eee..3ba5e5961 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 @@ -53,7 +53,8 @@ RED.nodes = (function() { defaults: { label: {value:""}, disabled: {value: false}, - info: {value: ""} + info: {value: ""}, + env: {value: []} } }; @@ -1387,7 +1388,8 @@ RED.nodes = (function() { type: "tab", disabled: false, label: RED._("clipboard.recoveredNodes"), - info: RED._("clipboard.recoveredNodesInfo") + info: RED._("clipboard.recoveredNodesInfo"), + env: [] } addWorkspace(recoveryWorkspace); RED.workspaces.add(recoveryWorkspace); @@ -1518,7 +1520,7 @@ RED.nodes = (function() { // Add a tab if there isn't one there already if (defaultWorkspace == null) { - defaultWorkspace = { type:"tab", id:getID(), disabled: false, info:"", label:RED._('workspace.defaultName',{number:1})}; + defaultWorkspace = { type:"tab", id:getID(), disabled: false, info:"", label:RED._('workspace.defaultName',{number:1}), env:[]}; addWorkspace(defaultWorkspace); RED.workspaces.add(defaultWorkspace); new_workspaces.push(defaultWorkspace); 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 9cac57872..0f24408ba 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 @@ -2637,6 +2637,28 @@ var buildingEditDialog = false; } } } + { + var old_env = editing_node.env; + var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node); + if (new_env && new_env.length > 0) { + new_env.forEach(function(prop) { + if (prop.type === "cred") { + editing_node.credentials = editing_node.credentials || {_:{}}; + editing_node.credentials[prop.name] = prop.value; + editing_node.credentials['has_'+prop.name] = (prop.value !== ""); + if (prop.value !== '__PWRD__') { + changed = true; + } + delete prop.value; + } + }); + } + 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; editing_node.changed = true; @@ -2668,6 +2690,16 @@ var buildingEditDialog = false; // console.log("oneditresize",editing_node.id,editing_node.type,err.toString()); // } // } + var envContainer = $("#red-ui-editor-group-env-list"); + if (envContainer.length) { + var rows = $("#dialog-form>div:not(#group-env-tabs-content)"); + var height = size.height; + for (var i=0; i', { + id: "editor-group-envProperties-content", + class: "red-ui-tray-content" + }).appendTo(editorContent).hide(), + iconClass: "fa fa-list", + }; + editorTabs.addTab(groupPropertiesTab); + var descriptionTab = { id: "editor-tab-description", label: RED._("editor-tab.description"), @@ -2725,6 +2769,7 @@ var buildingEditDialog = false; buildingEditDialog = false; done(); }); + }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 1c86eb631..7a56de563 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -87,14 +87,18 @@ RED.group = (function() { "label-position": "nw" }; + var groupDef = { defaults:{ name:{value:""}, style:{value:{label:true}}, - nodes:{value:[]} + nodes:{value:[]}, + env: {value:[]}, }, category: "config", oneditprepare: function() { + RED.subflow.buildPropertiesForm(this); + var style = this.style || {}; RED.colorPicker.create({ id:"node-input-style-stroke", @@ -144,7 +148,6 @@ RED.group = (function() { }) $("#node-input-style-label").prop("checked", this.style.label) $("#node-input-style-label").trigger("change"); - }, oneditresize: function(size) { }, 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 b0a93630a..e2a982462 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 @@ -980,6 +980,9 @@ RED.subflow = (function() { function buildPropertiesList(envContainer, node) { var isTemplateNode = (node.type === "subflow"); + var isGroup = (node.type === "group"); + var isTab = (node.type === "tab"); + var kind = isGroup ? "group" : (isTab ? "tab" : "subflow"); if (isTemplateNode) { buildEnvControl(envContainer, node); @@ -1841,27 +1844,30 @@ RED.subflow = (function() { function exportSubflowInstanceEnv(node) { var env = []; + var isGroup = (node.type === "group"); + var isTab = (node.type === "tab"); - // First, get the values for the SubflowTemplate defined properties - // - these are the ones with custom UI elements - var parentEnv = getSubflowInstanceParentEnv(node); - parentEnv.forEach(function(data) { - var item; - var ui = data.ui || {}; - if (!ui.type) { - if (data.parent && data.parent.type === "cred") { - ui.type = "cred"; + if (!isGroup && !isTab) { + // First, get the values for the SubflowTemplate defined properties + // - these are the ones with custom UI elements + var parentEnv = getSubflowInstanceParentEnv(node); + parentEnv.forEach(function(data) { + var item; + var ui = data.ui || {}; + if (!ui.type) { + if (data.parent && data.parent.type === "cred") { + ui.type = "cred"; + } else { + ui.type = "input"; + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} + } } else { - ui.type = "input"; - ui.opts = {types:DEFAULT_ENV_TYPE_LIST} + ui.opts = ui.opts || {}; } - } else { - ui.opts = ui.opts || {}; - } - var input = $("#"+getSubflowEnvPropertyName(data.name)); - if (input.length || ui.type === "cred") { - item = { name: data.name }; - switch(ui.type) { + var input = $("#"+getSubflowEnvPropertyName(data.name)); + if (input.length || ui.type === "cred") { + item = { name: data.name }; + switch(ui.type) { case "input": if (ui.opts.types && ui.opts.types.length > 0) { item.value = input.typedInput('value'); @@ -1887,14 +1893,16 @@ RED.subflow = (function() { item.type = 'bool'; item.value = ""+input.prop("checked"); break; + } + if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { + env.push(item); + } } - if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { - env.push(item); - } - } - }) + }) + } // Second, get the values from the Properties table tab - var items = $('#red-ui-editor-subflow-env-list').editableList('items'); + var kind = isGroup ? "group" : (isTab ? "tab" : "subflow"); + var items = $("#red-ui-editor-"+kind+"-env-list").editableList('items'); items.each(function (i,el) { var data = el.data('data'); var item; @@ -1947,11 +1955,13 @@ RED.subflow = (function() { buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node); } } + function buildPropertiesForm(node) { - var container = $('#editor-subflow-envProperties-content'); + var kind = (node.type === "group") ? "group" : ((node.type === "tab") ? "tab": "subflow"); + var container = $("#editor-"+kind+"-envProperties-content"); var form = $('
').appendTo(container); var listContainer = $('
').appendTo(form); - var list = $('
    ').appendTo(listContainer); + var list = $('
      ').appendTo(listContainer); buildPropertiesList(list, node); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index a7a4e024f..507447686 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -23,6 +23,9 @@ RED.workspaces = (function() { var viewStack = []; var viewStackPos = 0; + function isSameObj(env0, env1) { + return (JSON.stringify(env0) === JSON.stringify(env1)); + } function addToViewStack(id) { if (viewStackPos !== viewStack.length) { @@ -43,7 +46,7 @@ RED.workspaces = (function() { workspaceIndex += 1; } while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0); - ws = {type:"tab",id:tabId,disabled: false,info:"",label:RED._('workspace.defaultName',{number:workspaceIndex})}; + ws = {type:"tab",id:tabId,disabled: false,info:"",label:RED._('workspace.defaultName',{number:workspaceIndex}),env:[]}; RED.nodes.addWorkspace(ws,targetIndex); workspace_tabs.addTab(ws,targetIndex); workspace_tabs.activateTab(tabId); @@ -55,6 +58,7 @@ RED.workspaces = (function() { RED.view.focus(); return ws; } + function deleteWorkspace(ws) { if (workspaceTabCount === 1) { return; @@ -71,6 +75,51 @@ RED.workspaces = (function() { RED.sidebar.config.refresh(); } + var tabflowEditor; + + function buildProperties(container, workspace) { + var dialogForm = $('
      ').appendTo(container); + $('
      '+ + ''+ + ''+ + '
      ').appendTo(dialogForm); + + var row = $('
      '+ + ''+ + '
      '+ + '
      ').appendTo(dialogForm); + tabflowEditor = RED.editor.createEditor({ + id: 'node-input-info', + mode: 'ace/mode/markdown', + value: "" + }); + + $('#node-info-input-info-expand').on("click", function(e) { + e.preventDefault(); + var value = tabflowEditor.getValue(); + RED.editor.editMarkdown({ + value: value, + width: "Infinity", + cursor: tabflowEditor.getCursorPosition(), + complete: function(v,cursor) { + tabflowEditor.setValue(v, -1); + tabflowEditor.gotoLine(cursor.row+1,cursor.column,false); + setTimeout(function() { + tabflowEditor.focus(); + },300); + } + }) + }); + + $('').prependTo(dialogForm); + dialogForm.on("submit", function(e) { e.preventDefault();}); + + $("#node-input-name").val(workspace.label); + RED.text.bidi.prepareInput($("#node-input-name")); + tabflowEditor.getSession().setValue(workspace.info || "", -1); + dialogForm.i18n(); + } + function showEditWorkspaceDialog(id) { var workspace = RED.nodes.workspace(id); if (!workspace) { @@ -81,7 +130,6 @@ RED.workspaces = (function() { return; } RED.view.state(RED.state.EDITING); - var tabflowEditor; var trayOptions = { title: RED._("workspace.editFlow",{name:RED.utils.sanitize(workspace.label)}), buttons: [ @@ -130,6 +178,28 @@ RED.workspaces = (function() { $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + var old_env = workspace.env; + var new_env = RED.subflow.exportSubflowInstanceEnv(workspace); + console.log("; NE", new_env); + if (new_env && (new_env.length > 0)) { + new_env.forEach(function(prop) { + if (prop.type === "cred") { + workspace.credentials = workspace.credentials || {_:{}}; + workspace.credentials[prop.name] = prop.value; + workspace.credentials['has_'+prop.name] = (prop.value !== ""); + if (prop.value !== '__PWRD__') { + changed = true; + } + delete prop.value; + } + }); + } + if (!isSameObj(old_env, new_env)) { + workspace.env = new_env; + changes.env = old_env; + changed = true; + } + if (changed) { var historyEvent = { t: "edit", @@ -156,27 +226,67 @@ RED.workspaces = (function() { } ], resize: function(dimensions) { + var height = dimensions.height; + var rows = $("#dialog-form>div:not(.node-text-editor-row)"); var editorRow = $("#dialog-form>div.node-text-editor-row"); - var height = $("#dialog-form").height(); for (var i=0; i').appendTo(trayFooter) - var dialogForm = $('
      ').appendTo(trayBody); - $('
      '+ - ''+ - ''+ - '
      ').appendTo(dialogForm); + var editorTabEl = $('
        ').appendTo(trayBody); + var editorContent = $('
        ').appendTo(trayBody); + var finishedBuilding = false; + + var editorTabs = RED.tabs.create({ + element:editorTabEl, + onchange:function(tab) { + editorContent.children().hide(); + if (tab.onchange) { + tab.onchange.call(tab); + } + tab.content.show(); + if (finishedBuilding) { + RED.tray.resize(); + } + }, + collapsible: true, + menu: false + }); + + var nodePropertiesTab = { + id: "editor-tab-properties", + label: RED._("editor-tab.properties"), + name: RED._("editor-tab.properties"), + content: $('
        ', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + iconClass: "fa fa-cog" + }; + buildProperties(nodePropertiesTab.content, workspace); + editorTabs.addTab(nodePropertiesTab); + + var tabPropertiesTab = { + id: "editor-tab-envProperties", + label: RED._("editor-tab.envProperties"), + name: RED._("editor-tab.envProperties"), + content: $('
        ', { + id: "editor-tab-envProperties-content", + class: "red-ui-tray-content" + }).appendTo(editorContent).hide(), + iconClass: "fa fa-list", + }; + RED.subflow.buildPropertiesForm(workspace); + editorTabs.addTab(tabPropertiesTab); if (!workspace.hasOwnProperty("disabled")) { workspace.disabled = false; @@ -187,43 +297,8 @@ RED.workspaces = (function() { disabledIcon: "fa-ban", invertState: true }) - - - var row = $('
        '+ - ''+ - '
        '+ - '
        ').appendTo(dialogForm); - tabflowEditor = RED.editor.createEditor({ - id: 'node-input-info', - mode: 'ace/mode/markdown', - value: "" - }); - - $('#node-info-input-info-expand').on("click", function(e) { - e.preventDefault(); - var value = tabflowEditor.getValue(); - RED.editor.editMarkdown({ - value: value, - width: "Infinity", - cursor: tabflowEditor.getCursorPosition(), - complete: function(v,cursor) { - tabflowEditor.setValue(v, -1); - tabflowEditor.gotoLine(cursor.row+1,cursor.column,false); - setTimeout(function() { - tabflowEditor.focus(); - },300); - } - }) - }); - - - - $('').prependTo(dialogForm); - dialogForm.on("submit", function(e) { e.preventDefault();}); - $("#node-input-name").val(workspace.label); - RED.text.bidi.prepareInput($("#node-input-name")); - tabflowEditor.getSession().setValue(workspace.info || "", -1); - dialogForm.i18n(); + finishedBuilding = true; + RED.tray.resize(); }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index 9294121e7..3ef75b4c6 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -396,6 +396,17 @@ class Flow { return undefined; } + + /** + * Get a group node instance + * @param {String} id + * @return {Node} group node + */ + getGroupNode(id) { + const groups = this.global.groups; + return groups[id]; + } + /** * Get all of the nodes instantiated within this flow * @return {[type]} [description] @@ -404,6 +415,57 @@ class Flow { return this.activeNodes; } + /** + * Get a flow setting value deined in this flow. + * @param {String} key + * @return {Object} result containinig val property or null + */ + getFlowSetting(name) { + const flow = this.flow; + if (flow.env) { + if (!name.startsWith("$parent.")) { + if (!flow._env) { + const envs = flow.env; + const entries = envs.map((env) => [env.name, env]); + flow._env = Object.fromEntries(entries); + } + const env = flow._env[name]; + if (env) { + let value = env.value; + const type = env.type; + if ((type !== "env") || (value !== name)) { + if (type === "env") { + value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); + } + try { + if (type === "bool") { + const val = ((value === "true") || + (value === true)); + return { + val: val + }; + } + if (type === "cred") { + return { + val: value + }; + } + var val = redUtil.evaluateNodeProperty(value, type, null, null, null); + return { + val: val + }; + } + catch (e) { + this.error(e); + return null; + } + } + } + } + } + return null; + } + /** * Get a flow setting value. This currently automatically defers to the parent * flow which, as defined in ./index.js returns `process.env[key]`. @@ -412,6 +474,10 @@ class Flow { * @return {[type]} [description] */ getSetting(key) { + const result = this.getFlowSetting(key); + if (result) { + return result.val; + } return this.parent.getSetting(key); } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index a0b682faa..b4e8cd99a 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -43,6 +43,54 @@ function evaluateInputValue(value, type, node) { return redUtil.evaluateNodeProperty(value, type, node, null, null); } +/*! + * Get value of environment variable defined in group node. + * @param {String} group - group node + * @param {String} name - name of variable + * @return {Object} object containing the value in val property or null if not defined + */ +function getGroupSetting(node, group, flow, name) { + if (group) { + if (!name.startsWith("$parent.")) { + if (group.env) { + if (!group._env) { + const envs = group.env; + const entries = envs.map((env) => [env.name, env]); + group._env = Object.fromEntries(entries); + } + const env = group._env[name]; + if (env) { + let value = env.value; + const type = env.type; + if ((type !== "env") || (value !== name)) { + if (type === "env") { + value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); + } + try { + const val = evaluateInputValue(value, type, node); + return { + val: val + }; + } + catch (e) { + this.error(e); + return null; + } + } + } + } + } + else { + name = name.substring(8); + } + if (group.g && flow) { + const parent = flow.getGroupNode(group.g); + return getGroupSetting(node, parent, flow, name); + } + } + return null; +} + /** * This class represents a subflow - which is handled as a special type of Flow */ @@ -370,6 +418,16 @@ class Subflow extends Flow { // name starts $parent. ... so delegate to parent automatically name = name.substring(8); } + const node = this.subflowInstance; + if (node.g) { + const group = this.getGroupNode(node.g); + const result = getGroupSetting(node, group, this, name); + if (result) { + return result.val; + } + } + + var parent = this.parent; if (parent) { var val = parent.getSetting(name); diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index 92c3e5b1e..c6bdcea1c 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -539,6 +539,9 @@ async function addFlow(flow, user) { if (flow.hasOwnProperty('disabled')) { tabNode.disabled = flow.disabled; } + if (flow.hasOwnProperty('env')) { + tabNode.env = flow.env; + } var nodes = [tabNode]; @@ -599,6 +602,9 @@ function getFlow(id) { if (flow.hasOwnProperty('info')) { result.info = flow.info; } + if (flow.hasOwnProperty('env')) { + result.env = flow.env; + } if (id !== 'global') { result.nodes = []; } @@ -694,6 +700,9 @@ async function updateFlow(id,newFlow, user) { if (newFlow.hasOwnProperty('disabled')) { tabNode.disabled = newFlow.disabled; } + if (newFlow.hasOwnProperty('env')) { + tabNode.env = newFlow.env; + } nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]); nodes.forEach(function(n) { diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js index 00b54bc32..f81b6cdce 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js @@ -44,24 +44,25 @@ function diffNodes(oldNode,newNode) { var EnvVarPropertyRE_old = /^\$\((\S+)\)$/; var EnvVarPropertyRE = /^\${(\S+)}$/; -function mapEnvVarProperties(obj,prop,flow) { + +function mapEnvVarProperties(obj,prop,flow,config) { var v = obj[prop]; if (Buffer.isBuffer(v)) { return; } else if (Array.isArray(v)) { for (var i=0;i [env.name, env]); + group._env = Object.fromEntries(entries); + } + const env = group._env[name]; + if (env) { + let value = env.value; + const type = env.type; + if ((type !== "env") || (value !== name)) { + if (type === "env") { + value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); + } + try { + if (type === "bool") { + const val = ((value === "true") || + (value === true)); + return { + val: val + }; + } + if (type === "cred") { + return { + val: value + }; + } + var val = evaluateNodeProperty(value, type, node, null, null); + return { + val: val + }; + } + catch (e) { + this.error(e); + return null; + } + } + } + } + } + else { + name = name.substring(8); + } + if (group.g && flow) { + const parent = flow.getGroupNode(group.g); + return getGroupSetting(node, parent, flow, name); + } + } + return null; +} + /*! * Get value of environment variable. * @param {Node} node - accessing node * @param {String} name - name of variable * @return {String} value of env var */ -function getSetting(node, name) { - if (node && node._flow) { - var flow = node._flow; - if (flow) { - return flow.getSetting(name); +function getSetting(node, name, flow_) { + var flow = (flow_ ? flow_ : (node ? node._flow : null)); + if (flow) { + if (node && node.g) { + const group = flow.getGroupNode(node.g); + const result = getGroupSetting(node, group, flow, name); + if (result) { + return result.val; + } } + return flow.getSetting(name); } return process.env[name]; } @@ -544,18 +609,19 @@ function getSetting(node, name) { * @memberof @node-red/util_util */ function evaluateEnvProperty(value, node) { + var flow = (node && node.hasOwnProperty("_flow")) ? node._flow : null; var result; if (/^\${[^}]+}$/.test(value)) { // ${ENV_VAR} var name = value.substring(2,value.length-1); - result = getSetting(node, name); + result = getSetting(node, name, flow); } else if (!/\${\S+}/.test(value)) { // ENV_VAR - result = getSetting(node, value); + result = getSetting(node, value, flow); } else { // FOO${ENV_VAR}BAR return value.replace(/\${([^}]+)}/g, function(match, name) { - var val = getSetting(node, name); + var val = getSetting(node, name, flow); return (val === undefined)?"":val; }); } @@ -668,7 +734,7 @@ function prepareJSONataExpression(value,node) { return node.context().global.get(val, store); }); expr.assign('env', function(name) { - var val = getSetting(node, name); + var val = getSetting(node, name, node._flow); if (typeof val !== 'undefined') { return val; } else { @@ -976,5 +1042,6 @@ module.exports = { normaliseNodeTypeName: normaliseNodeTypeName, prepareJSONataExpression: prepareJSONataExpression, evaluateJSONataExpression: evaluateJSONataExpression, - parseContextStore: parseContextStore + parseContextStore: parseContextStore, + getSetting: getSetting }; diff --git a/test/nodes/core/function/15-change_spec.js b/test/nodes/core/function/15-change_spec.js index fc0c14600..06d1e3774 100644 --- a/test/nodes/core/function/15-change_spec.js +++ b/test/nodes/core/function/15-change_spec.js @@ -542,6 +542,77 @@ describe('change Node', function() { changeNode1.receive({payload:"123",topic:"ABC"}); }); }); + + it('sets the value using env property from tab', function(done) { + var flow = [ + {"id":"tab1","type":"tab","env":[ + {"name":"NR_TEST_A", "value":"bar", "type": "str"} + ]}, + {"id":"changeNode1","type":"change","z":"tab1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]} + ]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("bar"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.receive({payload:"123",topic:"ABC"}); + }); + }); + + it('sets the value using env property from group', function(done) { + var flow = [ + {"id":"group1","type":"group","env":[ + {"name":"NR_TEST_A", "value":"bar", "type": "str"} + ]}, + {"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]} + ]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("bar"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.receive({payload:"123",topic:"ABC"}); + }); + }); + + it('sets the value using env property from nested group', function(done) { + var flow = [ + {"id":"group1","type":"group","env":[ + {"name":"NR_TEST_A", "value":"bar", "type": "str"} + ]}, + {"id":"group2","type":"group","g":"group1","env":[]}, + {"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]} + ]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("bar"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.receive({payload:"123",topic:"ABC"}); + }); + }); + }); diff --git a/test/nodes/subflow/subflow_spec.js b/test/nodes/subflow/subflow_spec.js index b60328296..d5b8d78ae 100644 --- a/test/nodes/subflow/subflow_spec.js +++ b/test/nodes/subflow/subflow_spec.js @@ -447,4 +447,124 @@ describe('subflow', function() { }); }); + it('should access env var of tab', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:"", env: [ + {name: "K", type: "str", value: "V"} + ]}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", env: [], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of group', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"g1", z:"t0", type:"group", env:[ + {name: "K", type: "str", value: "V"} + ]}, + {id:"n1", x:10, y:10, z:"t0", g:"g1", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", env: [], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of nested group', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"g1", z:"t0", type:"group", env:[ + {name: "K", type: "str", value: "V"} + ]}, + {id:"g2", z:"t0", g:"g1", type:"group", env:[]}, + {id:"n1", x:10, y:10, z:"t0", g:"g2", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", env: [], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + }); diff --git a/test/unit/@node-red/runtime/lib/flows/Flow_spec.js b/test/unit/@node-red/runtime/lib/flows/Flow_spec.js index 8caa31349..95086c296 100644 --- a/test/unit/@node-red/runtime/lib/flows/Flow_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/Flow_spec.js @@ -1228,4 +1228,54 @@ describe('Flow', function() { }) }) + describe("#env", function () { + it("can instantiate a node with environment variable property values of group and tab", function (done) { + try { + after(function() { + delete process.env.V0; + delete process.env.V1; + }) + process.env.V0 = "gv0"; + process.env.V1 = "gv1"; + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab",env:[ + {"name": "V0", value: "v0", type: "str"} + ]}, + {id:"g1",type:"group",z:"t1",env:[ + {"name": "V0", value: "v1", type: "str"}, + {"name": "V1", value: "v2", type: "str"} + ]}, + {id:"g2",type:"group",z:"t1",g:"g1",env:[ + {"name": "V1", value: "v3", type: "str"} + ]}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"$(V0)",wires:[]}, + {id:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V0)",wires:[]}, + {id:"3",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V1)",wires:[]}, + {id:"4",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"$(V1)",wires:[]}, + {id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(V1)",wires:[]}, + ]); + var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]); + flow.start(); + + var activeNodes = flow.getActiveNodes(); + + activeNodes["1"].foo.should.equal("v0"); + activeNodes["2"].foo.should.equal("v1"); + activeNodes["3"].foo.should.equal("v2"); + activeNodes["4"].foo.should.equal("v3"); + activeNodes["5"].foo.should.equal("gv1"); + + flow.stop().then(function() { + done(); + }); + } + catch (e) { + console.log(e.stack); + done(e); + } + + }); + + }); + }); From 630d2ca926bbd5fab3b4cea79e5030d010711129 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 23 Aug 2021 15:52:49 +0200 Subject: [PATCH 02/27] Give passport verfiy callback wrapper the same arity as the original callback passed in via options --- .../@node-red/editor-api/lib/auth/index.js | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index fb95ede7b..5552301d0 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -173,27 +173,30 @@ function genericStrategy(adminApp,strategy) { adminApp.use(passport.session()); var options = strategy.options; + var verify = function() { + var originalDone = arguments[arguments.length-1]; + if (options.verify) { + var args = Array.from(arguments); + args[args.length-1] = function(err,profile) { + if (err) { + return originalDone(err); + } else { + return completeVerify(profile,originalDone); + } + }; - passport.use(new strategy.strategy(options, - function() { - var originalDone = arguments[arguments.length-1]; - if (options.verify) { - var args = Array.from(arguments); - args[args.length-1] = function(err,profile) { - if (err) { - return originalDone(err); - } else { - return completeVerify(profile,originalDone); - } - }; - options.verify.apply(null,args); - } else { - var profile = arguments[arguments.length - 2]; - return completeVerify(profile,originalDone); - } - + options.verify.apply(null,args); + } else { + var profile = arguments[arguments.length - 2]; + return completeVerify(profile,originalDone); } - )); + }; + // Give our callback the same arity as the original one from options + if (options.verify) { + Object.defineProperty(verify, "length", { value: options.verify.length }) + } + + passport.use(new strategy.strategy(options, verify)); adminApp.get('/auth/strategy', passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }), From e13133fd2b4ff1e0cfffbf8f153435f767430956 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Aug 2021 21:21:19 +0100 Subject: [PATCH 03/27] Add align actions to editor --- .../editor-client/src/js/keymap.json | 8 +- .../editor-client/src/js/ui/view-tools.js | 109 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index c323b3e64..b9459e681 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -77,6 +77,12 @@ "right": "core:go-to-nearest-node-on-right", "left": "core:go-to-nearest-node-on-left", "up": "core:go-to-nearest-node-above", - "down": "core:go-to-nearest-node-below" + "down": "core:go-to-nearest-node-below", + "alt-a l": "core:align-nodes-to-left", + "alt-a r": "core:align-nodes-to-right", + "alt-a t": "core:align-nodes-to-top", + "alt-a b": "core:align-nodes-to-bottom", + "alt-a m": "core:align-nodes-to-middle", + "alt-a c": "core:align-nodes-to-center" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 6c3889832..001155519 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -427,8 +427,107 @@ RED.view.tools = (function() { } } } + } + function alignSelectionToEdge(direction) { + var selection = RED.view.selection(); + if (selection.nodes && selection.nodes.length > 1) { + var changedNodes = []; + var bounds = { + minX: Number.MAX_SAFE_INTEGER, + minY: Number.MAX_SAFE_INTEGER, + maxX: Number.MIN_SAFE_INTEGER, + maxY: Number.MIN_SAFE_INTEGER + } + selection.nodes.forEach(function(n) { + if (n.type === "group") { + bounds.minX = Math.min(bounds.minX, n.x); + bounds.minY = Math.min(bounds.minY, n.y); + bounds.maxX = Math.max(bounds.maxX, n.x + n.w); + bounds.maxY = Math.max(bounds.maxY, n.y + n.h); + } else { + bounds.minX = Math.min(bounds.minX, n.x - n.w/2); + bounds.minY = Math.min(bounds.minY, n.y - n.h/2); + bounds.maxX = Math.max(bounds.maxX, n.x + n.w/2); + bounds.maxY = Math.max(bounds.maxY, n.y + n.h/2); + } + }); + + bounds.midX = bounds.minX + (bounds.maxX - bounds.minX)/2; + bounds.midY = bounds.minY + (bounds.maxY - bounds.minY)/2; + + selection.nodes.forEach(function(n) { + var targetX; + var targetY; + var isGroup = n.type==="group"; + switch(direction) { + case 'top': + targetX = n.x; + targetY = bounds.minY + (isGroup?0:(n.h/2)); + break; + case 'bottom': + targetX = n.x; + targetY = bounds.maxY - (isGroup?n.h:(n.h/2)); + break; + case 'left': + targetX = bounds.minX + (isGroup?0:(n.w/2)); + targetY = n.y; + break; + case 'right': + targetX = bounds.maxX - (isGroup?n.w:(n.w/2)); + targetY = n.y; + break; + case 'middle': + targetX = n.x; + targetY = bounds.midY - (isGroup?n.h/2:0) + break; + case 'center': + targetX = bounds.midX - (isGroup?n.w/2:0) + targetY = n.y; + break; + } + + if (n.x !== targetX || n.y !== targetY) { + if (!isGroup) { + changedNodes.push({ + n:n, + ox: n.x, + oy: n.y, + moved: n.moved + }); + n.x = targetX; + n.y = targetY; + n.dirty = true; + n.moved = true; + } else { + var groupNodes = RED.group.getNodes(n, true); + var deltaX = n.x - targetX; + var deltaY = n.y - targetY; + groupNodes.forEach(function(gn) { + if (gn.type !== "group" ) { + changedNodes.push({ + n:gn, + ox: gn.x, + oy: gn.y, + moved: gn.moved + }); + gn.x = gn.x - deltaX; + gn.y = gn.y - deltaY; + gn.dirty = true; + gn.moved = true; + } + }) + + } + } + }); + if (changedNodes.length > 0) { + RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); + RED.nodes.dirty(true); + RED.view.redraw(true); + } + } } @@ -474,6 +573,16 @@ RED.view.tools = (function() { RED.actions.add("core:go-to-nearest-node-on-right", function() { gotoNearestNode('right')}) RED.actions.add("core:go-to-nearest-node-above", function() { gotoNearestNode('up') }) RED.actions.add("core:go-to-nearest-node-below", function() { gotoNearestNode('down') }) + + RED.actions.add("core:align-nodes-to-left", function() { alignSelectionToEdge('left') }) + RED.actions.add("core:align-nodes-to-right", function() { alignSelectionToEdge('right') }) + RED.actions.add("core:align-nodes-to-top", function() { alignSelectionToEdge('top') }) + RED.actions.add("core:align-nodes-to-bottom", function() { alignSelectionToEdge('bottom') }) + RED.actions.add("core:align-nodes-to-middle", function() { alignSelectionToEdge('middle') }) + RED.actions.add("core:align-nodes-to-center", function() { alignSelectionToEdge('center') }) + + + // RED.actions.add("core:add-node", function() { addNode() }) }, /** From 56121203bfdacf8f39f568b24b54b8105ca118c5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Aug 2021 21:34:30 +0100 Subject: [PATCH 04/27] Rename align actions to match existing align-selection-to-grid --- .../@node-red/editor-client/src/js/keymap.json | 13 +++++++------ .../editor-client/src/js/ui/view-tools.js | 15 +++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index b9459e681..621fdaf41 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -78,11 +78,12 @@ "left": "core:go-to-nearest-node-on-left", "up": "core:go-to-nearest-node-above", "down": "core:go-to-nearest-node-below", - "alt-a l": "core:align-nodes-to-left", - "alt-a r": "core:align-nodes-to-right", - "alt-a t": "core:align-nodes-to-top", - "alt-a b": "core:align-nodes-to-bottom", - "alt-a m": "core:align-nodes-to-middle", - "alt-a c": "core:align-nodes-to-center" + "alt-a g": "core:align-selection-to-grid", + "alt-a l": "core:align-selection-to-left", + "alt-a r": "core:align-selection-to-right", + "alt-a t": "core:align-selection-to-top", + "alt-a b": "core:align-selection-to-bottom", + "alt-a m": "core:align-selection-to-middle", + "alt-a c": "core:align-selection-to-center" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 001155519..367e173fd 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -536,8 +536,6 @@ RED.view.tools = (function() { RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); }) - RED.actions.add("core:align-selection-to-grid", alignToGrid); - RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());}); RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);}); RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());}); @@ -574,12 +572,13 @@ RED.view.tools = (function() { RED.actions.add("core:go-to-nearest-node-above", function() { gotoNearestNode('up') }) RED.actions.add("core:go-to-nearest-node-below", function() { gotoNearestNode('down') }) - RED.actions.add("core:align-nodes-to-left", function() { alignSelectionToEdge('left') }) - RED.actions.add("core:align-nodes-to-right", function() { alignSelectionToEdge('right') }) - RED.actions.add("core:align-nodes-to-top", function() { alignSelectionToEdge('top') }) - RED.actions.add("core:align-nodes-to-bottom", function() { alignSelectionToEdge('bottom') }) - RED.actions.add("core:align-nodes-to-middle", function() { alignSelectionToEdge('middle') }) - RED.actions.add("core:align-nodes-to-center", function() { alignSelectionToEdge('center') }) + RED.actions.add("core:align-selection-to-grid", alignToGrid); + RED.actions.add("core:align-selection-to-left", function() { alignSelectionToEdge('left') }) + RED.actions.add("core:align-selection-to-right", function() { alignSelectionToEdge('right') }) + RED.actions.add("core:align-selection-to-top", function() { alignSelectionToEdge('top') }) + RED.actions.add("core:align-selection-to-bottom", function() { alignSelectionToEdge('bottom') }) + RED.actions.add("core:align-selection-to-middle", function() { alignSelectionToEdge('middle') }) + RED.actions.add("core:align-selection-to-center", function() { alignSelectionToEdge('center') }) From d2cdc67ec7317e21042f952aa084f5ca4a31d8b5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Aug 2021 21:29:21 +0100 Subject: [PATCH 05/27] Add distribute-horizontally/vertically actions to editor --- .../editor-client/src/js/keymap.json | 4 +- .../editor-client/src/js/ui/view-tools.js | 161 ++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index 621fdaf41..c50508389 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -84,6 +84,8 @@ "alt-a t": "core:align-selection-to-top", "alt-a b": "core:align-selection-to-bottom", "alt-a m": "core:align-selection-to-middle", - "alt-a c": "core:align-selection-to-center" + "alt-a c": "core:align-selection-to-center", + "alt-a h": "core:distribute-selection-horizontally", + "alt-a v": "core:distribute-selection-vertically" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 367e173fd..ede8d3442 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -531,6 +531,165 @@ RED.view.tools = (function() { } + function distributeSelection(direction) { + var selection = RED.view.selection(); + + if (selection.nodes && selection.nodes.length > 2) { + var changedNodes = []; + var bounds = { + minX: Number.MAX_SAFE_INTEGER, + minY: Number.MAX_SAFE_INTEGER, + maxX: Number.MIN_SAFE_INTEGER, + maxY: Number.MIN_SAFE_INTEGER + } + var startAnchors = []; + var endAnchors = []; + + selection.nodes.forEach(function(n) { + var nx,ny; + if (n.type === "group") { + nx = n.x + n.w/2; + ny = n.y + n.h/2; + } else { + nx = n.x; + ny = n.y; + } + if (direction === "h") { + if (nx < bounds.minX) { + startAnchors = []; + bounds.minX = nx; + } + if (nx === bounds.minX) { + startAnchors.push(n); + } + if (nx > bounds.maxX) { + endAnchors = []; + bounds.maxX = nx; + } + if (nx === bounds.maxX) { + endAnchors.push(n); + } + } else { + if (ny < bounds.minY) { + startAnchors = []; + bounds.minY = ny; + } + if (ny === bounds.minY) { + startAnchors.push(n); + } + if (ny > bounds.maxY) { + endAnchors = []; + bounds.maxY = ny; + } + if (ny === bounds.maxY) { + endAnchors.push(n); + } + } + }); + + var startAnchor = startAnchors[0]; + var endAnchor = endAnchors[0]; + + var nodeSpace = 0; + var nodesToMove = selection.nodes.filter(function(n) { + if (n.id !== startAnchor.id && n.id !== endAnchor.id) { + nodeSpace += direction === 'h'?n.w:n.h; + return true; + } + return false; + }).sort(function(A,B) { + if (direction === 'h') { + return A.x - B.x + } else { + return A.y - B.y + } + }) + + var saX = startAnchor.x + startAnchor.w/2; + var saY = startAnchor.y + startAnchor.h/2; + if (startAnchor.type === "group") { + saX = startAnchor.x + startAnchor.w; + saY = startAnchor.y + startAnchor.h; + } + var eaX = endAnchor.x; + var eaY = endAnchor.y; + if (endAnchor.type !== "group") { + eaX -= endAnchor.w/2; + eaY -= endAnchor.h/2; + } + var spaceToFill = direction === 'h'?(eaX - saX - nodeSpace): (eaY - saY - nodeSpace); + var spaceBetweenNodes = spaceToFill / (nodesToMove.length + 1); + + var tx = saX; + var ty = saY; + while(nodesToMove.length > 0) { + if (direction === 'h') { + tx += spaceBetweenNodes; + } else { + ty += spaceBetweenNodes; + } + var nextNode = nodesToMove.shift(); + var isGroup = nextNode.type==="group"; + + var nx = nextNode.x; + var ny = nextNode.y; + if (!isGroup) { + tx += nextNode.w/2; + ty += nextNode.h/2; + } + if ((direction === 'h' && nx !== tx) || (direction === 'v' && ny !== ty)) { + if (!isGroup) { + changedNodes.push({ + n:nextNode, + ox: nextNode.x, + oy: nextNode.y, + moved: nextNode.moved + }); + if (direction === 'h') { + nextNode.x = tx; + } else { + nextNode.y = ty; + } + nextNode.dirty = true; + nextNode.moved = true; + } else { + var groupNodes = RED.group.getNodes(nextNode, true); + var deltaX = direction === 'h'? nx - tx : 0; + var deltaY = direction === 'v'? ny - ty : 0; + groupNodes.forEach(function(gn) { + if (gn.type !== "group" ) { + changedNodes.push({ + n:gn, + ox: gn.x, + oy: gn.y, + moved: gn.moved + }); + gn.x = gn.x - deltaX; + gn.y = gn.y - deltaY; + gn.dirty = true; + gn.moved = true; + } + }) + } + } + if (isGroup) { + tx += nextNode.w; + ty += nextNode.h; + } else { + tx += nextNode.w/2; + ty += nextNode.h/2; + } + } + + if (changedNodes.length > 0) { + RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); + RED.nodes.dirty(true); + RED.view.redraw(true); + } + } + } + + return { init: function() { RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) @@ -580,6 +739,8 @@ RED.view.tools = (function() { RED.actions.add("core:align-selection-to-middle", function() { alignSelectionToEdge('middle') }) RED.actions.add("core:align-selection-to-center", function() { alignSelectionToEdge('center') }) + RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') }) + RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') }) // RED.actions.add("core:add-node", function() { addNode() }) From 8e89b1bdf2db6365b6dca4d66fd3c65b455823db Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Aug 2021 16:22:58 +0100 Subject: [PATCH 06/27] Fix typo in ko editor.json Fixes #3119 --- .../node_modules/@node-red/editor-client/locales/ko/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ko/editor.json b/packages/node_modules/@node-red/editor-client/locales/ko/editor.json index 3c12e116a..9e0c74ea8 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ko/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ko/editor.json @@ -56,7 +56,7 @@ "displayConfig": "설정노드 보기", "import": "가져오기", "export": "내보내기", - "search": "플로우 겅색", + "search": "플로우 검색", "searchInput": "플로우 검색", "subflows": "보조 플로우", "createSubflow": "보조 플로우 생성", From 26d83bb9eaa762165c14970eb8bfb1fbc04dab73 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Aug 2021 16:32:43 +0100 Subject: [PATCH 07/27] Ensure treeList row has suitable min-height when no content Fixes #3109 --- .../@node-red/editor-client/src/sass/ui/common/treeList.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss index 54e37b57c..a89fe1b84 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss @@ -101,6 +101,9 @@ } .red-ui-treeList-label-text { margin-left: 4px; + &:empty { + min-height: 20px; + } } .red-ui-treeList-sublabel-text { top: 0; From d78e5932f94f0be0af1667e4b47b076525241631 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 30 Aug 2021 08:00:58 +0900 Subject: [PATCH 08/27] update according to PR comments --- .../@node-red/editor-client/src/js/nodes.js | 21 ++++- .../editor-client/src/js/ui/editor.js | 4 +- .../editor-client/src/js/ui/workspaces.js | 70 ++++++++++++--- .../@node-red/runtime/lib/api/flows.js | 4 +- .../@node-red/runtime/lib/flows/Flow.js | 90 ++++++++++++++++++- .../@node-red/runtime/lib/flows/Subflow.js | 50 +---------- .../@node-red/runtime/lib/flows/index.js | 3 + .../node_modules/@node-red/util/lib/util.js | 62 +------------ 8 files changed, 176 insertions(+), 128 deletions(-) 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 3ba5e5961..e623b3b0f 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 @@ -691,6 +691,21 @@ RED.nodes = (function() { node[d] = n[d]; } } + var credentialSet = {}; + if (n.credentials) { + for (var tabCred in n.credentials) { + if (n.credentials.hasOwnProperty(tabCred)) { + if (!n.credentials._ || + n.credentials["has_"+tabCred] != n.credentials._["has_"+tabCred] || + (n.credentials["has_"+tabCred] && n.credentials[tabCred])) { + credentialSet[tabCred] = n.credentials[tabCred]; + } + } + } + if (Object.keys(credentialSet).length > 0) { + node.credentials = credentialSet; + } + } return node; } /** @@ -740,8 +755,10 @@ RED.nodes = (function() { } if (exportCreds) { var credentialSet = {}; - if (/^subflow:/.test(node.type) && n.credentials) { - // A subflow instance node can have arbitrary creds + if ((/^subflow:/.test(node.type) || + (node.type === "group")) && + n.credentials) { + // A subflow instance/group node can have arbitrary creds for (var sfCred in n.credentials) { if (n.credentials.hasOwnProperty(sfCred)) { if (!n.credentials._ || 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 0f24408ba..d1cc3ed80 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 @@ -507,7 +507,7 @@ RED.editor = (function() { done(); } } - if (definition.credentials || /^subflow:/.test(definition.type)) { + if (definition.credentials || /^subflow:/.test(definition.type) || (node.type === "group")) { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); @@ -1467,7 +1467,7 @@ var buildingEditDialog = false; changes.env = editing_node.env; changed = true; } - } + } if (changed) { var wasChanged = editing_node.changed; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 507447686..04c904c8b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -120,6 +120,36 @@ RED.workspaces = (function() { dialogForm.i18n(); } + function getNodeCredentials(type, id, done) { + var timeoutNotification; + var intialTimeout = setTimeout(function() { + timeoutNotification = RED.notify($('

        ').i18n(),{fixed: true}) + },800); + + $.ajax({ + url: "credentials/tab/" + id, + dataType: 'json', + success: function(data) { + if (timeoutNotification) { + timeoutNotification.close(); + timeoutNotification = null; + } + clearTimeout(intialTimeout); + done(data); + }, + error: function(jqXHR,status,error) { + if (timeoutNotification) { + timeoutNotification.close(); + timeoutNotification = null; + } + clearTimeout(intialTimeout); + RED.notify(RED._("editor.errors.credentialLoadFailed"),"error") + done(null); + }, + timeout: 30000, + }); + } + function showEditWorkspaceDialog(id) { var workspace = RED.nodes.workspace(id); if (!workspace) { @@ -180,7 +210,6 @@ RED.workspaces = (function() { var old_env = workspace.env; var new_env = RED.subflow.exportSubflowInstanceEnv(workspace); - console.log("; NE", new_env); if (new_env && (new_env.length > 0)) { new_env.forEach(function(prop) { if (prop.type === "cred") { @@ -285,20 +314,37 @@ RED.workspaces = (function() { }).appendTo(editorContent).hide(), iconClass: "fa fa-list", }; - RED.subflow.buildPropertiesForm(workspace); - editorTabs.addTab(tabPropertiesTab); - if (!workspace.hasOwnProperty("disabled")) { - workspace.disabled = false; + function cb() { + RED.subflow.buildPropertiesForm(workspace); + editorTabs.addTab(tabPropertiesTab); + + if (!workspace.hasOwnProperty("disabled")) { + workspace.disabled = false; + } + + $('').prop("checked",workspace.disabled).appendTo(trayFooterLeft).toggleButton({ + enabledIcon: "fa-circle-thin", + disabledIcon: "fa-ban", + invertState: true + }) + finishedBuilding = true; + RED.tray.resize(); + } + + if (workspace.credentials) { + cb(); + } + else { + getNodeCredentials("tab", workspace.id, function(data) { + if (data) { + workspace.credentials = data; + workspace.credentials._ = $.extend(true,{},data); + } + cb(); + }); } - $('').prop("checked",workspace.disabled).appendTo(trayFooterLeft).toggleButton({ - enabledIcon: "fa-circle-thin", - disabledIcon: "fa-ban", - invertState: true - }) - finishedBuilding = true; - RED.tray.resize(); }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 811d7b5cb..d28a0479a 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -233,7 +233,9 @@ var api = module.exports = { } var sendCredentials = {}; var cred; - if (/^subflow(:|$)/.test(opts.type)) { + if (/^subflow(:|$)/.test(opts.type) || + (opts.type === "tab") || + (opts.type === "group")) { for (cred in credentials) { if (credentials.hasOwnProperty(cred)) { sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== ''; diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index 3ef75b4c6..9cf9b1cbb 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -20,6 +20,7 @@ const events = require("@node-red/util").events; var flowUtil = require("./util"); const context = require('../nodes/context'); const hooks = require("@node-red/util").hooks; +const credentials = require("../nodes/credentials"); var Subflow; var Log; @@ -422,11 +423,22 @@ class Flow { */ getFlowSetting(name) { const flow = this.flow; + if (flow.credentials === undefined) { + flow.credentials = credentials.get(flow.id) || {}; + } if (flow.env) { if (!name.startsWith("$parent.")) { if (!flow._env) { const envs = flow.env; - const entries = envs.map((env) => [env.name, env]); + const entries = envs.map((env) => { + if (env.type === "cred") { + const cred = flow.credentials; + if (cred.hasOwnProperty(env.name)) { + env.value = cred[env.name]; + } + } + return [env.name, env] + }); flow._env = Object.fromEntries(entries); } const env = flow._env[name]; @@ -466,6 +478,82 @@ class Flow { return null; } + /*! + * Get value of environment variable defined in group node. + * @param {String} group - group node + * @param {String} name - name of variable + * @return {Object} object containing the value in val property or null if not defined + */ + getGroupEnvSetting(node, group, name) { + if (group) { + if (group.credentials === undefined) { + group.credentials = credentials.get(group.id) || {}; + } + if (!name.startsWith("$parent.")) { + if (group.env) { + if (!group._env) { + const envs = group.env; + const entries = envs.map((env) => { + if (env.type === "cred") { + const cred = group.credentials; + if (cred.hasOwnProperty(env.name)) { + env.value = cred[env.name]; + } + } + return [env.name, env]; + + return [env.name, env]; + }); + group._env = Object.fromEntries(entries); + } + const env = group._env[name]; + if (env) { + let value = env.value; + const type = env.type; + if ((type !== "env") || + (value !== name)) { + if (type === "env") { + value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); + } + if (type === "bool") { + const val + = ((value === "true") || + (value === true)); + return { + val: val + }; + } + if (type === "cred") { + return { + val: value + }; + } + try { + var val = redUtil.evaluateNodeProperty(value, type, node, null, null); + return { + val: val + }; + } + catch (e) { + this.error(e); + return null; + } + } + } + } + } + else { + name = name.substring(8); + } + if (group.g) { + const parent = this.getGroupNode(group.g); + return this.getGroupEnvSetting(node, parent, name); + } + } + return null; + } + + /** * Get a flow setting value. This currently automatically defers to the parent * flow which, as defined in ./index.js returns `process.env[key]`. diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index b4e8cd99a..0f3aefe26 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -43,54 +43,6 @@ function evaluateInputValue(value, type, node) { return redUtil.evaluateNodeProperty(value, type, node, null, null); } -/*! - * Get value of environment variable defined in group node. - * @param {String} group - group node - * @param {String} name - name of variable - * @return {Object} object containing the value in val property or null if not defined - */ -function getGroupSetting(node, group, flow, name) { - if (group) { - if (!name.startsWith("$parent.")) { - if (group.env) { - if (!group._env) { - const envs = group.env; - const entries = envs.map((env) => [env.name, env]); - group._env = Object.fromEntries(entries); - } - const env = group._env[name]; - if (env) { - let value = env.value; - const type = env.type; - if ((type !== "env") || (value !== name)) { - if (type === "env") { - value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); - } - try { - const val = evaluateInputValue(value, type, node); - return { - val: val - }; - } - catch (e) { - this.error(e); - return null; - } - } - } - } - } - else { - name = name.substring(8); - } - if (group.g && flow) { - const parent = flow.getGroupNode(group.g); - return getGroupSetting(node, parent, flow, name); - } - } - return null; -} - /** * This class represents a subflow - which is handled as a special type of Flow */ @@ -421,7 +373,7 @@ class Subflow extends Flow { const node = this.subflowInstance; if (node.g) { const group = this.getGroupNode(node.g); - const result = getGroupSetting(node, group, this, name); + const result = this.getGroupEnvSetting(node, group, name); if (result) { return result.val; } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index c6bdcea1c..ae9131ec0 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -703,6 +703,9 @@ async function updateFlow(id,newFlow, user) { if (newFlow.hasOwnProperty('env')) { tabNode.env = newFlow.env; } + if (newFlow.hasOwnProperty('credentials')) { + tabNode.credentials = newFlow.credentials; + } nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]); nodes.forEach(function(n) { diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index 893870fd6..8f123cf25 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -515,66 +515,6 @@ function setObjectProperty(msg,prop,value,createMissing) { return true; } -/*! - * Get value of environment variable defined in group node. - * @param {String} group - group node - * @param {String} name - name of variable - * @return {Object} object containing the value in val property or null if not defined - */ -function getGroupSetting(node, group, flow, name) { - if (group) { - if (!name.startsWith("$parent.")) { - if (group.env) { - if (!group._env) { - const envs = group.env; - const entries = envs.map((env) => [env.name, env]); - group._env = Object.fromEntries(entries); - } - const env = group._env[name]; - if (env) { - let value = env.value; - const type = env.type; - if ((type !== "env") || (value !== name)) { - if (type === "env") { - value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); - } - try { - if (type === "bool") { - const val = ((value === "true") || - (value === true)); - return { - val: val - }; - } - if (type === "cred") { - return { - val: value - }; - } - var val = evaluateNodeProperty(value, type, node, null, null); - return { - val: val - }; - } - catch (e) { - this.error(e); - return null; - } - } - } - } - } - else { - name = name.substring(8); - } - if (group.g && flow) { - const parent = flow.getGroupNode(group.g); - return getGroupSetting(node, parent, flow, name); - } - } - return null; -} - /*! * Get value of environment variable. * @param {Node} node - accessing node @@ -586,7 +526,7 @@ function getSetting(node, name, flow_) { if (flow) { if (node && node.g) { const group = flow.getGroupNode(node.g); - const result = getGroupSetting(node, group, flow, name); + const result = flow.getGroupEnvSetting(node, group, name); if (result) { return result.val; } From 67404a327d64c5f307bfc483482d718c6967aa29 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Wed, 1 Sep 2021 22:26:31 +0900 Subject: [PATCH 09/27] merge getFlowSetting to getSetting --- .../@node-red/runtime/lib/flows/Flow.js | 110 +++++++----------- 1 file changed, 45 insertions(+), 65 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index 9cf9b1cbb..23f41e0ed 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -416,68 +416,6 @@ class Flow { return this.activeNodes; } - /** - * Get a flow setting value deined in this flow. - * @param {String} key - * @return {Object} result containinig val property or null - */ - getFlowSetting(name) { - const flow = this.flow; - if (flow.credentials === undefined) { - flow.credentials = credentials.get(flow.id) || {}; - } - if (flow.env) { - if (!name.startsWith("$parent.")) { - if (!flow._env) { - const envs = flow.env; - const entries = envs.map((env) => { - if (env.type === "cred") { - const cred = flow.credentials; - if (cred.hasOwnProperty(env.name)) { - env.value = cred[env.name]; - } - } - return [env.name, env] - }); - flow._env = Object.fromEntries(entries); - } - const env = flow._env[name]; - if (env) { - let value = env.value; - const type = env.type; - if ((type !== "env") || (value !== name)) { - if (type === "env") { - value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); - } - try { - if (type === "bool") { - const val = ((value === "true") || - (value === true)); - return { - val: val - }; - } - if (type === "cred") { - return { - val: value - }; - } - var val = redUtil.evaluateNodeProperty(value, type, null, null, null); - return { - val: val - }; - } - catch (e) { - this.error(e); - return null; - } - } - } - } - } - return null; - } - /*! * Get value of environment variable defined in group node. * @param {String} group - group node @@ -562,9 +500,51 @@ class Flow { * @return {[type]} [description] */ getSetting(key) { - const result = this.getFlowSetting(key); - if (result) { - return result.val; + const flow = this.flow; + if (flow.credentials === undefined) { + flow.credentials = credentials.get(flow.id) || {}; + } + if (flow.env) { + if (!key.startsWith("$parent.")) { + if (!flow._env) { + const envs = flow.env; + const entries = envs.map((env) => { + if (env.type === "cred") { + const cred = flow.credentials; + if (cred.hasOwnProperty(env.name)) { + env.value = cred[env.name]; + } + } + return [env.name, env] + }); + flow._env = Object.fromEntries(entries); + } + const env = flow._env[key]; + if (env) { + let value = env.value; + const type = env.type; + if ((type !== "env") || (value !== key)) { + if (type === "env") { + value = value.replace(new RegExp("\\${"+key+"}","g"),"${$parent."+key+"}"); + } + try { + if (type === "bool") { + const val = ((value === "true") || + (value === true)); + return val; + } + if (type === "cred") { + return value; + } + var val = redUtil.evaluateNodeProperty(value, type, null, null, null); + return val; + } + catch (e) { + this.error(e); + } + } + } + } } return this.parent.getSetting(key); } From 78899378c2f9e13b37c5f555ed7b5b97bc5d1e9b Mon Sep 17 00:00:00 2001 From: Amit Kumar Swami Date: Thu, 2 Sep 2021 13:19:40 +0800 Subject: [PATCH 10/27] Update tar to 6.1.9 to resolve vulnerability issue --- package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2296ad220..33dd27ca7 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "passport-oauth2-client-password": "0.1.2", "raw-body": "2.4.1", "semver": "7.3.5", - "tar": "6.1.2", + "tar": "6.1.9", "tough-cookie": "4.0.0", "uglify-js": "3.13.10", "uuid": "8.3.2", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 999352647..2513b8f33 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -20,7 +20,7 @@ "clone": "2.1.2", "fs-extra": "10.0.0", "semver": "7.3.5", - "tar": "6.1.2", + "tar": "6.1.9", "uglify-js": "3.13.10" } } From ee15e9acc54d7ebef65dc4d20a17d35c328cff43 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 08:21:10 +0100 Subject: [PATCH 11/27] Update tar to latest --- package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 33dd27ca7..b133d1f06 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "passport-oauth2-client-password": "0.1.2", "raw-body": "2.4.1", "semver": "7.3.5", - "tar": "6.1.9", + "tar": "6.1.11", "tough-cookie": "4.0.0", "uglify-js": "3.13.10", "uuid": "8.3.2", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 2513b8f33..6e3162b65 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -20,7 +20,7 @@ "clone": "2.1.2", "fs-extra": "10.0.0", "semver": "7.3.5", - "tar": "6.1.9", + "tar": "6.1.11", "uglify-js": "3.13.10" } } From 3b460fb8faedbacb588efb9b312c6c7bcffe708e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 09:00:54 +0100 Subject: [PATCH 12/27] Bump for 2.0.6 --- CHANGELOG.md | 14 +++++++++++ package.json | 24 +++++++++---------- .../@node-red/editor-api/package.json | 8 +++---- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 6 ++--- .../@node-red/registry/package.json | 6 ++--- .../@node-red/runtime/package.json | 8 +++---- .../node_modules/@node-red/util/package.json | 4 ++-- packages/node_modules/node-red/package.json | 10 ++++---- 9 files changed, 48 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e98ba74..d442405b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +#### 2.0.6: Maintenance Release + +Editor + + - Fix typo in ko editor.json Fixes #3119 + - Change fade color when hovering an inactive tab (#3106) @bonanitech + - Ensure treeList row has suitable min-height when no content Fixes #3109 + +Runtime + + - Update tar to latest (#3128) @aksswami + - Give passport verify callback the same arity as the original callback (#3117) @dschmidt + - Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers + #### 2.0.5: Maintenance Release Editor diff --git a/package.json b/package.json index b133d1f06..d52055bc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "2.0.5", + "version": "2.0.6", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -28,8 +28,8 @@ "dependencies": { "acorn": "8.4.1", "acorn-walk": "8.1.1", - "ajv": "8.6.0", - "async-mutex": "0.3.1", + "ajv": "8.6.2", + "async-mutex": "0.3.2", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -55,14 +55,14 @@ "is-utf8": "0.2.1", "js-yaml": "3.14.1", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.4", + "jsonata": "1.8.5", "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.6", "mime": "2.5.2", "moment-timezone": "0.5.33", "mqtt": "4.2.8", - "multer": "1.4.2", + "multer": "1.4.3", "mustache": "4.2.0", "node-red-admin": "^2.2.0", "nopt": "5.0.0", @@ -75,7 +75,7 @@ "semver": "7.3.5", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.13.10", + "uglify-js": "3.14.1", "uuid": "8.3.2", "ws": "7.5.1", "xml2js": "0.4.23" @@ -84,7 +84,7 @@ "bcrypt": "5.0.1" }, "devDependencies": { - "dompurify": "2.2.9", + "dompurify": "2.3.1", "grunt": "1.4.1", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.4.3", @@ -109,15 +109,15 @@ "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", "marked": "2.1.3", "minami": "1.2.3", - "mocha": "9.0.1", + "mocha": "9.1.1", "node-red-node-test-helper": "^0.2.7", - "nodemon": "2.0.8", + "nodemon": "2.0.12", "proxy": "^1.0.2", - "sass": "1.35.1", + "sass": "1.39.0", "should": "13.2.3", - "sinon": "11.1.1", + "sinon": "11.1.2", "stoppable": "^1.1.0", - "supertest": "6.1.3" + "supertest": "6.1.6" }, "engines": { "node": ">=12" diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index d3c342cba..73f512538 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "2.0.5", - "@node-red/editor-client": "2.0.5", + "@node-red/util": "2.0.6", + "@node-red/editor-client": "2.0.6", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", @@ -26,7 +26,7 @@ "express": "4.17.1", "memorystore": "1.6.6", "mime": "2.5.2", - "multer": "1.4.2", + "multer": "1.4.3", "mustache": "4.2.0", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index ee3f1d8ad..fc315b61d 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 540c97734..e082f3c73 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "repository": { "type": "git", @@ -17,7 +17,7 @@ "dependencies": { "acorn": "8.4.1", "acorn-walk": "8.1.1", - "ajv": "8.6.0", + "ajv": "8.6.2", "body-parser": "1.19.0", "cheerio": "1.0.0-rc.10", "content-type": "1.0.4", @@ -37,7 +37,7 @@ "js-yaml": "3.14.1", "media-typer": "1.1.0", "mqtt": "4.2.8", - "multer": "1.4.2", + "multer": "1.4.3", "mustache": "4.2.0", "on-headers": "1.0.2", "raw-body": "2.4.1", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 6e3162b65..d42cfd4ae 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,11 +16,11 @@ } ], "dependencies": { - "@node-red/util": "2.0.5", + "@node-red/util": "2.0.6", "clone": "2.1.2", "fs-extra": "10.0.0", "semver": "7.3.5", "tar": "6.1.11", - "uglify-js": "3.13.10" + "uglify-js": "3.14.1" } } diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 4925ebb8a..e2a288862 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,9 +16,9 @@ } ], "dependencies": { - "@node-red/registry": "2.0.5", - "@node-red/util": "2.0.5", - "async-mutex": "0.3.1", + "@node-red/registry": "2.0.6", + "@node-red/util": "2.0.6", + "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "10.0.0", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 8a569238c..d76c9f450 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "2.0.5", + "version": "2.0.6", "license": "Apache-2.0", "repository": { "type": "git", @@ -18,7 +18,7 @@ "fs-extra": "10.0.0", "i18next": "20.3.2", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.4", + "jsonata": "1.8.5", "lodash.clonedeep": "^4.5.0", "moment-timezone": "0.5.33" } diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index c70c08451..2543d896f 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "2.0.5", + "version": "2.0.6", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "2.0.5", - "@node-red/runtime": "2.0.5", - "@node-red/util": "2.0.5", - "@node-red/nodes": "2.0.5", + "@node-red/editor-api": "2.0.6", + "@node-red/runtime": "2.0.6", + "@node-red/util": "2.0.6", + "@node-red/nodes": "2.0.6", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", From 4f4d78bfab0fbd70b4e87d1f5b486a8ca478fa1e Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Sat, 21 Aug 2021 22:21:58 +0200 Subject: [PATCH 13/27] Key and certificate as string or buffer --- packages/node_modules/node-red/red.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 2b934fb56..5c40d7794 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -234,8 +234,13 @@ httpsPromise.then(function(startupHttps) { // Get the result of the function, because createServer doesn't accept functions as input Promise.resolve(settings.https()).then(function(refreshedHttps) { if (refreshedHttps) { + // The key/cert needs to be updated in the NodeJs http(s) server, when no key/cert is yet available or when the key/cert has changed. + // Note that the refreshed key/cert can be supplied as a string or a buffer. + var updateKey = (server.key == undefined || (Buffer.isBuffer(server.key) && !server.key.equals(refreshedHttps.key)) || (typeof server.key == "string" && server.key != refreshedHttps.key)); + var updateCert = (server.cert == undefined || (Buffer.isBuffer(server.cert) && !server.cert.equals(refreshedHttps.cert)) || (typeof server.cert == "string" && server.cert != refreshedHttps.cert)); + // Only update the credentials in the server when key or cert has changed - if(!server.key || !server.cert || !server.key.equals(refreshedHttps.key) || !server.cert.equals(refreshedHttps.cert)) { + if(updateKey || updateCert) { server.setSecureContext(refreshedHttps); RED.log.info(RED.log._("server.https.settings-refreshed")); } From f2a9887a12c9e8dff9ba3b1d125cd557a07d99e2 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 10 Aug 2021 14:21:54 -0400 Subject: [PATCH 14/27] Place close tab link in front of fade --- .../@node-red/editor-client/src/js/ui/common/tabs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index 0dfb72ffd..41c6e704c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -664,6 +664,8 @@ RED.tabs = (function() { link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); }) + $('').appendTo(li); + if (tab.closeable) { li.addClass("red-ui-tabs-closeable") var closeLink = $("",{href:"#",class:"red-ui-tab-close"}).appendTo(li); @@ -674,8 +676,6 @@ RED.tabs = (function() { }); } - $('').appendTo(li); - var badges = $('').appendTo(li); if (options.onselect) { $('').appendTo(badges); From bdf54f6cffa8720fd6caface1bc5020bf897fa21 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 10 Aug 2021 14:26:41 -0400 Subject: [PATCH 15/27] Change fade color when hovering an inactive tab --- .../node_modules/@node-red/editor-client/src/sass/tabs.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 662aaa6ae..0b038932a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -131,6 +131,9 @@ &:not(.active) a:hover { color: $workspace-button-color-hover; background: $tab-background-hover; + &+.red-ui-tabs-fade { + background-image: linear-gradient(to right, change-color($tab-background-hover, $alpha: 0.001), $tab-background-hover); + } } } } From 9b83afae4272c63075f48e57355e706009251ae0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Aug 2021 16:22:58 +0100 Subject: [PATCH 16/27] Fix typo in ko editor.json Fixes #3119 --- .../node_modules/@node-red/editor-client/locales/ko/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ko/editor.json b/packages/node_modules/@node-red/editor-client/locales/ko/editor.json index 3c12e116a..9e0c74ea8 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ko/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ko/editor.json @@ -56,7 +56,7 @@ "displayConfig": "설정노드 보기", "import": "가져오기", "export": "내보내기", - "search": "플로우 겅색", + "search": "플로우 검색", "searchInput": "플로우 검색", "subflows": "보조 플로우", "createSubflow": "보조 플로우 생성", From 39aafc5007383c6ff4cdf3ba18e6c241f915c00e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Aug 2021 16:32:43 +0100 Subject: [PATCH 17/27] Ensure treeList row has suitable min-height when no content Fixes #3109 --- .../@node-red/editor-client/src/sass/ui/common/treeList.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss index 54e37b57c..a89fe1b84 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss @@ -101,6 +101,9 @@ } .red-ui-treeList-label-text { margin-left: 4px; + &:empty { + min-height: 20px; + } } .red-ui-treeList-sublabel-text { top: 0; From e910f3915daeef9ef33cb378d72ed0563682457e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 10:33:43 +0100 Subject: [PATCH 18/27] Initial refactor of edit dialogs to separate panes --- Gruntfile.js | 2 +- .../@node-red/editor-client/src/js/nodes.js | 19 +- .../editor-client/src/js/ui/editor.js | 1852 ++++------------- .../js/ui/{common => editors}/colorPicker.js | 2 +- .../src/js/ui/editors/iconPicker.js | 99 + .../src/js/ui/editors/panes/appearance.js | 515 +++++ .../src/js/ui/editors/panes/description.js | 71 + .../src/js/ui/editors/panes/properties.js | 190 ++ .../src/js/ui/editors/panes/subflowModule.js | 181 ++ .../js/ui/editors/panes/subflowProperties.js | 68 + .../editor-client/src/js/ui/group.js | 6 +- .../editor-client/src/js/ui/library.js | 2 +- .../editor-client/src/js/ui/subflow.js | 191 +- 13 files changed, 1519 insertions(+), 1679 deletions(-) rename packages/node_modules/@node-red/editor-client/src/js/ui/{common => editors}/colorPicker.js (99%) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js diff --git a/Gruntfile.js b/Gruntfile.js index df9939a8c..261f0c72b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -162,7 +162,6 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js", - "packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js", @@ -182,6 +181,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index e623b3b0f..2d442eea9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -592,18 +592,17 @@ RED.nodes = (function() { inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, oneditprepare: function() { - RED.subflow.buildEditForm("subflow",this); - RED.subflow.buildPropertiesForm(this); + if (this.type !== 'subflow') { + // Whilst this definition is used for both template and instance + // nodes, the work to build the edit form for the template nodes + // is handled elsewhere. + RED.subflow.buildEditForm("subflow",this); + } }, oneditresize: function(size) { - // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); - var height = size.height; - // for (var i=0; idiv.node-input-env-container-row"); - // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("ol.red-ui-editor-subflow-env-list").editableList('height',height); + if (this.type === 'subflow') { + $("#node-input-env-container").editableList('height',size.height - 80); + } }, set:{ module: "node-red" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index d1cc3ed80..a2841cdde 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -20,19 +20,16 @@ RED.editor = (function() { var editStack = []; + var buildingEditDialog = false; var editing_node = null; var editing_config_node = null; - var subflowEditor; var customEditTypes = {}; + var editPanes = {}; + var filteredEditPanes = []; var editTrayWidthCache = {}; - function getCredentialsURL(nodeType, nodeID) { - var dashedType = nodeType.replace(/\s+/g, '-'); - return 'credentials/' + dashedType + "/" + nodeID; - } - /** * Validate a node * @param node - the node being validated @@ -160,7 +157,6 @@ RED.editor = (function() { return valid; } - function validateNodeEditor(node,prefix) { for (var prop in node._def.defaults) { if (node._def.defaults.hasOwnProperty(prop)) { @@ -398,74 +394,95 @@ RED.editor = (function() { } } - /** - * Update the node credentials from the edit form - * @param node - the node containing the credentials - * @param credDefinition - definition of the credentials - * @param prefix - prefix of the input fields - * @return {boolean} whether anything has changed - */ - function updateNodeCredentials(node, credDefinition, prefix) { - var changed = false; - if (!node.credentials) { - node.credentials = {_:{}}; - } else if (!node.credentials._) { - node.credentials._ = {}; - } - - for (var cred in credDefinition) { - if (credDefinition.hasOwnProperty(cred)) { - var input = $("#" + prefix + '-' + cred); - if (input.length > 0) { - var value = input.val(); - if (credDefinition[cred].type == 'password') { - node.credentials['has_' + cred] = (value !== ""); - if (value == '__PWRD__') { - continue; - } - changed = true; - - } - node.credentials[cred] = value; - if (value != node.credentials._[cred]) { - changed = true; - } - } - } - } - return changed; - } - /** * Prepare all of the editor dialog fields + * @param trayBody - the tray body to populate + * @param nodeEditPanes - array of edit pane ids to add to the dialog * @param node - the node being edited * @param definition - the node definition * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) + * @param default - the id of the tab to show by default */ - function prepareEditDialog(node,definition,prefix,done) { - for (var d in definition.defaults) { - if (definition.defaults.hasOwnProperty(d)) { - if (definition.defaults[d].type) { - if (!definition.defaults[d]._type.array) { - var configTypeDef = RED.nodes.getType(definition.defaults[d].type); - if (configTypeDef && configTypeDef.category === 'config') { - if (configTypeDef.exclusive) { - prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix); - } else { - prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix); - } - } else { - console.log("Unknown type:", definition.defaults[d].type); - preparePropertyEditor(node,d,prefix,definition.defaults); - } - } - } else { - preparePropertyEditor(node,d,prefix,definition.defaults); - } - attachPropertyChangeHandler(node,definition.defaults,d,prefix); - } - } + function prepareEditDialog(trayBody, nodeEditPanes, node, definition, prefix, defaultTab, done) { + var finishedBuilding = false; var completePrepare = function() { + + var editorTabEl = $('
          ').appendTo(trayBody); + var editorContent = $('
          ').appendTo(trayBody); + + var editorTabs = RED.tabs.create({ + element:editorTabEl, + onchange:function(tab) { + editorContent.children().hide(); + if (tab.onchange) { + tab.onchange.call(tab); + } + tab.content.show(); + if (finishedBuilding) { + RED.tray.resize(); + } + }, + collapsible: true, + menu: false + }); + + var activeEditPanes = []; + nodeEditPanes.forEach(function(id) { + try { + var editPaneDefinition = editPanes[id]; + if (editPaneDefinition) { + if (typeof editPaneDefinition === 'function') { + editPaneDefinition = editPaneDefinition.call(editPaneDefinition); + } + var editTab = { + id: id, + label: editPaneDefinition.label, + name: editPaneDefinition.name, + iconClass: editPaneDefinition.iconClass, + content: editPaneDefinition.create.call(editPaneDefinition,editorContent,node).hide(), + onchange: function() { + if (editPaneDefinition.onchange) { + editPaneDefinition.onchange.call(editPaneDefinition,node) + } + } + } + editorTabs.addTab(editTab); + activeEditPanes.push(editPaneDefinition); + } else { + console.warn("Unregisted edit pane:",id) + } + } catch(err) { + console.log(id,err); + } + }); + + for (var d in definition.defaults) { + if (definition.defaults.hasOwnProperty(d)) { + if (definition.defaults[d].type) { + if (!definition.defaults[d]._type.array) { + var configTypeDef = RED.nodes.getType(definition.defaults[d].type); + if (configTypeDef && configTypeDef.category === 'config') { + if (configTypeDef.exclusive) { + prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix); + } else { + prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix); + } + } else { + console.log("Unknown type:", definition.defaults[d].type); + preparePropertyEditor(node,d,prefix,definition.defaults); + } + } + } else { + preparePropertyEditor(node,d,prefix,definition.defaults); + } + attachPropertyChangeHandler(node,definition.defaults,d,prefix); + } + } + + if (!/^subflow:/.test(definition.type)) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + } + if (definition.oneditprepare) { try { definition.oneditprepare.call(node); @@ -474,6 +491,7 @@ RED.editor = (function() { console.log(err.stack); } } + // Now invoke any change handlers added to the fields - passing true // to prevent full node validation from being triggered each time for (var d in definition.defaults) { @@ -503,8 +521,12 @@ RED.editor = (function() { } } validateNodeEditor(node,prefix); + finishedBuilding = true; + if (defaultTab) { + editorTabs.activateTab(defaultTab); + } if (done) { - done(); + done(activeEditPanes); } } if (definition.credentials || /^subflow:/.test(definition.type) || (node.type === "group")) { @@ -512,13 +534,14 @@ RED.editor = (function() { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); } else { - getNodeCredentials(node.type, node.id, function(data) { + var nodeType = node.type; + if (/^subflow:/.test(nodeType)) { + nodeType = "subflow" + } + getNodeCredentials(nodeType, node.id, function(data) { if (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); - if (!/^subflow:/.test(definition.type)) { - populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); - } } completePrepare(); }); @@ -598,13 +621,6 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if (type === "subflow-template") { - // This is the 'edit properties' dialog for a subflow template - // TODO: this needs to happen later in the dialog open sequence - // so that credentials can be loaded prior to building the form - RED.subflow.buildEditForm(type,node); - } - // Add dummy fields to prevent 'Enter' submitting the form in some // cases, and also prevent browser auto-fill of password // - the elements cannot be hidden otherwise Chrome will ignore them. @@ -617,506 +633,147 @@ RED.editor = (function() { return dialogForm; } - function refreshLabelForm(container,node) { - - var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); - var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); - - var inputsDiv = $("#red-ui-editor-node-label-form-inputs"); - var outputsDiv = $("#red-ui-editor-node-label-form-outputs"); - - var inputCount; - var formInputs = $("#node-input-inputs").val(); - if (formInputs === undefined) { - if (node.type === 'subflow') { - inputCount = node.in.length; - } else { - inputCount = node.inputs || node._def.inputs || 0; - } - } else { - inputCount = Math.min(1,Math.max(0,parseInt(formInputs))); - if (isNaN(inputCount)) { - inputCount = 0; - } - } - - var children = inputsDiv.children(); - var childCount = children.length; - if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) { - childCount--; - } - - if (childCount < inputCount) { - if (childCount === 0) { - // remove the 'none' placeholder - $(children[0]).remove(); - } - for (i = childCount;i inputCount) { - for (i=inputCount;i B.__label__) { + return 1; + } + return 0; + } + + function updateConfigNodeSelect(name,type,value,prefix) { + // if prefix is null, there is no config select to update + if (prefix) { + var button = $("#"+prefix+"-edit-"+name); + if (button.length) { + if (value) { + button.text(RED._("editor.configEdit")); } else { - row.detach(); + button.text(RED._("editor.configAdd")); } - if (outputMap[p] !== -1) { - outputCount++; - rows.push({i:parseInt(outputMap[p]),r:row}); - } - }); - rows.sort(function(A,B) { - return A.i-B.i; - }) - rows.forEach(function(r,i) { - r.r.find("label").text((i+1)+"."); - r.r.appendTo(outputsDiv); - }) - if (rows.length === 0) { - buildLabelRow("output",i,"").appendTo(outputsDiv); + $("#"+prefix+"-"+name).val(value); } else { - } - } else { - outputCount = Math.max(0,parseInt(formOutputs)); - } - children = outputsDiv.children(); - childCount = children.length; - if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) { - childCount--; - } - if (childCount < outputCount) { - if (childCount === 0) { - // remove the 'none' placeholder - $(children[0]).remove(); - } - for (i = childCount;i outputCount) { - for (i=outputCount;i',{class:"red-ui-editor-node-label-form-row"}); - if (type === undefined) { - $('').text(RED._("editor.noDefaultLabel")).appendTo(result); - result.addClass("red-ui-editor-node-label-form-none"); - } else { - result.addClass(""); - var id = "red-ui-editor-node-label-form-"+type+"-"+index; - $('
          '); - var searchDiv = $("
          ",{class:"red-ui-search-container"}).appendTo(picker); - searchInput = $('').attr("placeholder",RED._("editor.searchIcons")).appendTo(searchDiv).searchBox({ - delay: 50, - change: function() { - var searchTerm = $(this).val().trim(); - if (searchTerm === "") { - iconList.find(".red-ui-icon-list-module").show(); - iconList.find(".red-ui-icon-list-icon").show(); - } else { - iconList.find(".red-ui-icon-list-module").hide(); - iconList.find(".red-ui-icon-list-icon").each(function(i,n) { - if ($(n).data('icon').indexOf(searchTerm) === -1) { - $(n).hide(); - } else { - $(n).show(); - } - }); + var select = $("#"+prefix+"-"+name); + var node_def = RED.nodes.getType(type); + select.children().remove(); + + var activeWorkspace = RED.nodes.workspace(RED.workspaces.active()); + if (!activeWorkspace) { + activeWorkspace = RED.nodes.subflow(RED.workspaces.active()); } - } - }); - var row = $('
          ').appendTo(picker); - var iconList = $('
          ').appendTo(picker); - var metaRow = $('
          ').appendTo(picker); - var summary = $('').appendTo(metaRow); - var resetButton = $('').appendTo(metaRow).on("click", function(e) { - e.preventDefault(); - iconPanel.hide(); - done(null); - }); - if (!backgroundColor && faOnly) { - iconList.addClass("red-ui-icon-list-dark"); - } - setTimeout(function() { - var iconSets = RED.nodes.getIconSets(); - 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)); - var header = $('
          ').text(moduleName).appendTo(iconList); - $('').prependTo(header); - 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 icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon; - iconDiv.data('icon',icon_url); - if (backgroundColor) { - nodeDiv.css({ - 'backgroundColor': backgroundColor - }); - var borderColor = RED.utils.getDarkerColor(backgroundColor); - if (borderColor !== backgroundColor) { - nodeDiv.css('border-color',borderColor) - } + var configNodes = []; - } - var iconContainer = $('
          ',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); - RED.utils.createIconElement(icon_url, iconContainer, true); - - if (iconPath.module === moduleName && iconPath.file === icon) { - iconDiv.addClass("selected"); - } - iconDiv.on("mouseover", function() { - summary.text(icon); - }) - iconDiv.on("mouseout", function() { - summary.html(" "); - }) - iconDiv.on("click", function() { - iconPanel.hide(); - done(moduleName+"/"+icon); - }) - }) - } - }); - setTimeout(function() { - spinner.remove(); - },50); - },300); - var spinner = RED.utils.addSpinnerOverlay(iconList,true); - var iconPanel = RED.popover.panel(picker); - iconPanel.show({ - target: container - }) - - - picker.slideDown(100); - searchInput.trigger("focus"); - } - - function buildAppearanceForm(container,node) { - var dialogForm = $('
          ').appendTo(container); - - var i,row; - - if (node.type === "subflow") { - var categoryRow = $("
          ", { - class: "form-row" - }).appendTo(dialogForm); - $("
          ", { - class: "form-row" - }).appendTo(dialogForm); - $("
          ',{class:"red-ui-icon-list-icon"}).appendTo(iconList); + var nodeDiv = $('
          ',{class:"red-ui-search-result-node"}).appendTo(iconDiv); + var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon; + iconDiv.data('icon',icon_url); + if (backgroundColor) { + nodeDiv.css({ + 'backgroundColor': backgroundColor + }); + var borderColor = RED.utils.getDarkerColor(backgroundColor); + if (borderColor !== backgroundColor) { + nodeDiv.css('border-color',borderColor) + } + + } + var iconContainer = $('
          ',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); + RED.utils.createIconElement(icon_url, iconContainer, true); + + if (iconPath.module === moduleName && iconPath.file === icon) { + iconDiv.addClass("selected"); + } + iconDiv.on("mouseover", function() { + summary.text(icon); + }) + iconDiv.on("mouseout", function() { + summary.html(" "); + }) + iconDiv.on("click", function() { + iconPanel.hide(); + done(moduleName+"/"+icon); + }) + }) + } + }); + setTimeout(function() { + spinner.remove(); + },50); + },300); + var spinner = RED.utils.addSpinnerOverlay(iconList,true); + var iconPanel = RED.popover.panel(picker); + iconPanel.show({ + target: container + }) + + + picker.slideDown(100); + searchInput.trigger("focus"); + } + return { + show: showIconPicker + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js new file mode 100644 index 000000000..2d8c3ca00 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -0,0 +1,515 @@ +;(function() { + + RED.editor.registerEditorPane("editor-tab-appearance", function() { + return { + label: RED._("editor-tab.appearance"), + name: RED._("editor-tab.appearance"), + iconClass: "fa fa-object-group", + create: function(container, node) { + this.content = $('
          ', {class:"red-ui-tray-content"}).appendTo(container); + buildAppearanceForm(this.content,node); + + if (node.type === 'subflow') { + this.defaultIcon = "node-red/subflow.svg"; + } else { + var iconPath = RED.utils.getDefaultNodeIcon(node._def,node); + this.defaultIcon = iconPath.module+"/"+iconPath.file; + if (node.icon && node.icon !== this.defaultIcon) { + this.isDefaultIcon = false; + } else { + this.isDefaultIcon = true; + } + } + return this.content; + }, + resize: function(node, size) { + + }, + close: function(node) { + + }, + show: function(node) { + refreshLabelForm(this.content, node); + }, + apply: function(node, editState) { + if (updateLabels(node, editState.changes, editState.outputMap)) { + editState.changed = true; + } + if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) { + var icon = $("#red-ui-editor-node-icon").val()||"" + if (!this.isDefaultIcon) { + if (icon !== node.icon) { + editState.changes.icon = node.icon; + node.icon = icon; + editState.changed = true; + } + } else { + if (icon !== "" && icon !== this.defaultIcon) { + editState.changes.icon = node.icon; + node.icon = icon; + editState.changed = true; + } else { + var iconPath = RED.utils.getDefaultNodeIcon(node._def, node); + var currentDefaultIcon = iconPath.module+"/"+iconPath.file; + if (this.defaultIcon !== currentDefaultIcon) { + editState.changes.icon = node.icon; + node.icon = currentDefaultIcon; + editState.changed = true; + } + } + } + } + if (node.type === "subflow") { + var newCategory = $("#subflow-appearance-input-category").val().trim(); + if (newCategory === "_custom_") { + newCategory = $("#subflow-appearance-input-custom-category").val().trim(); + if (newCategory === "") { + newCategory = node.category; + } + } + if (newCategory === 'subflows') { + newCategory = ''; + } + if (newCategory != node.category) { + editState.changes['category'] = node.category; + node.category = newCategory; + editState.changed = true; + } + + var oldColor = node.color; + var newColor = $("#red-ui-editor-node-color").val(); + if (oldColor !== newColor) { + editState.changes.color = node.color; + node.color = newColor; + editState.changed = true; + RED.utils.clearNodeColorCache(); + if (node.type === "subflow") { + var nodeDefinition = RED.nodes.getType( + "subflow:" + node.id + ); + nodeDefinition["color"] = newColor; + } + } + + + + } + if (!$("#node-input-show-label").prop('checked')) { + // Not checked - hide label + if (!/^link (in|out)$/.test(node.type)) { + // Not a link node - default state is true + if (node.l !== false) { + editState.changes.l = node.l + editState.changed = true; + } + node.l = false; + } else { + // A link node - default state is false + if (node.hasOwnProperty('l') && node.l) { + editState.changes.l = node.l + editState.changed = true; + } + delete node.l; + } + } else { + // Checked - show label + if (!/^link (in|out)$/.test(node.type)) { + // Not a link node - default state is true + if (node.hasOwnProperty('l') && !node.l) { + editState.changes.l = node.l + editState.changed = true; + } + delete node.l; + } else { + if (!node.l) { + editState.changes.l = node.l + editState.changed = true; + } + node.l = true; + } + } + } + } + }); + + function buildAppearanceForm(container,node) { + var dialogForm = $('
          ').appendTo(container); + + var i,row; + + if (node.type === "subflow") { + var categoryRow = $("
          ", { + class: "form-row" + }).appendTo(dialogForm); + $("
          ", { + class: "form-row" + }).appendTo(dialogForm); + $("
          ', {class:"red-ui-tray-content"}).appendTo(container); + this.editor = buildDescriptionForm(content,node); + return content; + }, + resize: function(node, size) { + this.editor.resize(); + }, + close: function(node) { + this.editor.destroy(); + this.editor = null; + }, + show: function() { + this.editor.focus(); + }, + apply: function(node, editState) { + var oldInfo = node.info; + var newInfo = this.editor.getValue(); + if (!!oldInfo) { + // Has existing info property + if (newInfo.trim() === "") { + // New value is blank - remove the property + editState.changed = true; + editState.changes.info = oldInfo; + delete node.info; + } else if (newInfo !== oldInfo) { + // New value is different + editState.changed = true; + editState.changes.info = oldInfo; + node.info = newInfo; + } + } else { + // No existing info + if (newInfo.trim() !== "") { + // New value is not blank + editState.changed = true; + editState.changes.info = undefined; + node.info = newInfo; + } + } + } + } + }); + + function buildDescriptionForm(container,node) { + var dialogForm = $('
          ').appendTo(container); + var toolbarRow = $('
          ').appendTo(dialogForm); + var row = $('
          ').appendTo(dialogForm); + var editorId = "node-info-input-info-editor-"+Math.floor(1000*Math.random()); + $('
          ').appendTo(row); + var nodeInfoEditor = RED.editor.createEditor({ + id: editorId, + mode: 'ace/mode/markdown', + value: "" + }); + if (node.info) { + nodeInfoEditor.getSession().setValue(node.info, -1); + } + node.infoEditor = nodeInfoEditor; + return nodeInfoEditor; + } + +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js new file mode 100644 index 000000000..4c568649d --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js @@ -0,0 +1,190 @@ +;(function() { + + RED.editor.registerEditorPane("editor-tab-properties", function() { + return { + label: RED._("editor-tab.properties"), + name: RED._("editor-tab.properties"), + iconClass: "fa fa-cog", + create: function(container, node) { + var content = $('
          ', {class:"red-ui-tray-content"}).appendTo(container); + + var nodeType = node.type; + if (node.type === "subflow") { + nodeType = "subflow-template"; + } else if (node.type.substring(0,8) == "subflow:") { + nodeType = "subflow"; + } + + var i18nNamespace; + if (node._def.set.module === "node-red") { + i18nNamespace = "node-red"; + } else { + i18nNamespace = node._def.set.id; + } + + var formStyle = "dialog-form"; + this.inputClass = "node-input"; + if (node._def.category === "config" && nodeType !== "group") { + this.inputClass = "node-config-input"; + formStyle = "node-config-dialog-edit-form"; + } + RED.editor.buildEditForm(content,formStyle,nodeType,i18nNamespace,node); + + if (nodeType === "subflow-template") { + // This is the 'edit properties' dialog for a subflow template + // TODO: this needs to happen later in the dialog open sequence + // so that credentials can be loaded prior to building the form + RED.subflow.buildEditForm("subflow-template", node); + } + + return content; + }, + resize: function(node, size) { + if (node && node._def.oneditresize) { + try { + node._def.oneditresize.call(node,size); + } catch(err) { + console.log("oneditresize",node.id,node.type,err.toString()); + } + } + }, + close: function(node) { + + }, + apply: function(editing_node, editState) { + var newValue; + if (editing_node._def.defaults) { + for (d in editing_node._def.defaults) { + if (editing_node._def.defaults.hasOwnProperty(d)) { + var input = $("#"+this.inputClass+"-"+d); + if (input.attr('type') === "checkbox") { + newValue = input.prop('checked'); + } else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") { + // An empty select-multiple box returns null. + // Need to treat that as an empty array. + newValue = input.val(); + if (newValue == null) { + newValue = []; + } + } else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") { + newValue = input.text(); + } else { + newValue = input.val(); + } + if (newValue != null) { + if (d === "outputs") { + if (newValue.trim() === "") { + continue; + } + if (isNaN(newValue)) { + editState.outputMap = JSON.parse(newValue); + var outputCount = 0; + var outputsChanged = false; + var keys = Object.keys(editState.outputMap); + keys.forEach(function(p) { + if (isNaN(p)) { + // New output; + outputCount ++; + delete editState.outputMap[p]; + } else { + editState.outputMap[p] = editState.outputMap[p]+""; + if (editState.outputMap[p] !== "-1") { + outputCount++; + if (editState.outputMap[p] !== p) { + // Output moved + outputsChanged = true; + } else { + delete editState.outputMap[p]; + } + } else { + // Output removed + outputsChanged = true; + } + } + }); + + newValue = outputCount; + if (outputsChanged) { + editState.changed = true; + } + } else { + newValue = parseInt(newValue); + } + } + if (editing_node._def.defaults[d].type) { + if (newValue == "_ADD_") { + newValue = ""; + } + } + if (editing_node[d] != newValue) { + if (editing_node._def.defaults[d].type) { + // Change to a related config node + var configNode = RED.nodes.node(editing_node[d]); + if (configNode) { + var users = configNode.users; + users.splice(users.indexOf(editing_node),1); + RED.events.emit("nodes:change",configNode); + } + configNode = RED.nodes.node(newValue); + if (configNode) { + configNode.users.push(editing_node); + RED.events.emit("nodes:change",configNode); + } + } + editState.changes[d] = editing_node[d]; + editing_node[d] = newValue; + editState.changed = true; + } + } + } + } + } + if (editing_node._def.credentials) { + var credDefinition = editing_node._def.credentials; + var credsChanged = updateNodeCredentials(editing_node,credDefinition,this.inputClass); + editState.changed = editState.changed || credsChanged; + } + } + } + }); + + /** + * Update the node credentials from the edit form + * @param node - the node containing the credentials + * @param credDefinition - definition of the credentials + * @param prefix - prefix of the input fields + * @return {boolean} whether anything has changed + */ + function updateNodeCredentials(node, credDefinition, prefix) { + var changed = false; + if (!node.credentials) { + node.credentials = {_:{}}; + } else if (!node.credentials._) { + node.credentials._ = {}; + } + + for (var cred in credDefinition) { + if (credDefinition.hasOwnProperty(cred)) { + var input = $("#" + prefix + '-' + cred); + if (input.length > 0) { + var value = input.val(); + if (credDefinition[cred].type == 'password') { + node.credentials['has_' + cred] = (value !== ""); + if (value == '__PWRD__') { + continue; + } + changed = true; + + } + node.credentials[cred] = value; + if (value != node.credentials._[cred]) { + changed = true; + } + } + } + } + return changed; + } + + +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js new file mode 100644 index 000000000..4636862f6 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js @@ -0,0 +1,181 @@ +(function() { + var _subflowModulePaneTemplate = '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '+ + ''+ + ''+ + '
          '+ + '
          '; + + RED.editor.registerEditorPane("editor-tab-subflow-module", function() { + return { + label: RED._("editor-tab.module"), + name: RED._("editor-tab.module"), + iconClass: "fa fa-cube", + create: function(container, node) { + var content = $('
          ', {class:"red-ui-tray-content"}).appendTo(container); + buildModuleForm(content, node); + return content; + }, + resize: function(node, size) { + }, + close: function(node) { + + }, + apply: function(node, editState) { + var newMeta = exportSubflowModuleProperties(node); + if (!isSameObj(node.meta,newMeta)) { + editState.changes.meta = node.meta; + node.meta = newMeta; + editState.changed = true; + } + } + } + }); + + function isSameObj(env0, env1) { + return (JSON.stringify(env0) === JSON.stringify(env1)); + } + + 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', + 'type', + 'version', + 'author', + 'desc', + 'keywords', + 'license' + ].forEach(function(property) { + $("#subflow-input-module-"+property).val(moduleProps[property]||"") + }) + $("#subflow-input-module-type").attr("placeholder",node.id); + + 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 === "none" ? RED._("editor:subflow.licenseNone") : 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', + 'type', + '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; + } + +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js new file mode 100644 index 000000000..f9071fac8 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js @@ -0,0 +1,68 @@ +;(function() { + + RED.editor.registerEditorPane("editor-tab-envProperties", function() { + return { + label: RED._("editor-tab.envProperties"), + name: RED._("editor-tab.envProperties"), + iconClass: "fa fa-list", + create: function(container, node) { + var content = $('
          ', {class:"red-ui-tray-content"}).appendTo(container); + var form = $('
          ').appendTo(content); + var listContainer = $('
          ').appendTo(form); + this.list = $('
            ').appendTo(listContainer); + RED.subflow.buildPropertiesList(this.list, node); + return content; + }, + resize: function(node, size) { + this.list.editableList('height',size.height); + }, + close: function(node) { + + }, + apply: function(node, editState) { + var old_env = node.env; + var new_env = RED.subflow.exportSubflowInstanceEnv(node); + + // Get the values from the Properties table tab + var items = this.list.editableList('items'); + items.each(function (i,el) { + var data = el.data('data'); + var item; + if (data.nameField && data.valueField) { + item = { + name: data.nameField.val(), + value: data.valueField.typedInput("value"), + type: data.valueField.typedInput("type") + } + if (item.name.trim() !== "") { + new_env.push(item); + } + } + }); + + + if (new_env && new_env.length > 0) { + new_env.forEach(function(prop) { + if (prop.type === "cred") { + node.credentials = node.credentials || {_:{}}; + node.credentials[prop.name] = prop.value; + node.credentials['has_'+prop.name] = (prop.value !== ""); + if (prop.value !== '__PWRD__') { + editState.changed = true; + } + delete prop.value; + } + }); + } + if (!isSameObj(old_env, new_env)) { + node.env = new_env; + editState.changes.env = node.env; + editState.changed = true; + } + } + } + }); + function isSameObj(env0, env1) { + return (JSON.stringify(env0) === JSON.stringify(env1)); + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 7a56de563..114bb7207 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -100,7 +100,7 @@ RED.group = (function() { RED.subflow.buildPropertiesForm(this); var style = this.style || {}; - RED.colorPicker.create({ + RED.editor.colorPicker.create({ id:"node-input-style-stroke", value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4", palette: colorPalette, @@ -111,7 +111,7 @@ RED.group = (function() { none: true, opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0) }).appendTo("#node-input-row-style-stroke"); - RED.colorPicker.create({ + RED.editor.colorPicker.create({ id:"node-input-style-fill", value: style.fill || defaultGroupStyle.fill ||"none", palette: colorPalette, @@ -128,7 +128,7 @@ RED.group = (function() { value:style["label-position"] || "nw" }).appendTo("#node-input-row-style-label-position"); - RED.colorPicker.create({ + RED.editor.colorPicker.create({ id:"node-input-style-color", value: style.color || defaultGroupStyle.color ||"#a4a4a4", palette: colorPalette, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index 8e77ae0f5..ee176dee2 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -536,7 +536,7 @@ RED.library = (function() { // evt.preventDefault(); // var icon = libraryFields['icon'].input.val() || ""; // var iconPath = (icon ? RED.utils.separateIconPath(icon) : {}); - // RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) { + // RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) { // iconButton.empty(); // var path = newIcon || ""; // var newPath = RED.utils.separateIconPath(path); 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 e2a982462..de40ea939 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 @@ -37,7 +37,7 @@ RED.subflow = (function() { '
            '+ '
            '+ '
            '+ - '
              '+ + '
                '+ '
                '+ '
                '+ '
                '+ @@ -47,37 +47,6 @@ RED.subflow = (function() { '
                '+ ''; - var _subflowModulePaneTemplate = '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '+ - ''+ - ''+ - '
                '+ - '
                '; - function findAvailableSubflowIOPosition(subflow,isInput) { var pos = {x:50,y:30}; if (!isInput) { @@ -909,7 +878,6 @@ RED.subflow = (function() { * Create interface for controlling env var UI definition */ function buildEnvControl(envList,node) { - var tabs = RED.tabs.create({ id: "subflow-env-tabs", onchange: function(tab) { @@ -1174,7 +1142,7 @@ RED.subflow = (function() { evt.preventDefault(); var icon = ui.icon || ""; var iconPath = (icon ? RED.utils.separateIconPath(icon) : {}); - RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) { + RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) { iconButton.empty(); var path = newIcon || ""; var newPath = RED.utils.separateIconPath(path); @@ -1898,25 +1866,11 @@ RED.subflow = (function() { env.push(item); } } - }) - } - // Second, get the values from the Properties table tab - var kind = isGroup ? "group" : (isTab ? "tab" : "subflow"); - var items = $("#red-ui-editor-"+kind+"-env-list").editableList('items'); - items.each(function (i,el) { - var data = el.data('data'); - var item; - if (data.nameField && data.valueField) { - item = { - name: data.nameField.val(), - value: data.valueField.typedInput("value"), - type: data.valueField.typedInput("type") - } - if (item.name.trim() !== "") { + if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { env.push(item); } } - }); + }) return env; } @@ -1948,6 +1902,8 @@ RED.subflow = (function() { function buildEditForm(type,node) { if (type === "subflow-template") { + // This is called by the `properties` edit pane when + // editing a subflow template. buildPropertiesList($('#node-input-env-container'), node); } else if (type === "subflow") { // This gets called by the subflow type `oneditprepare` function @@ -1956,135 +1912,6 @@ RED.subflow = (function() { } } - function buildPropertiesForm(node) { - var kind = (node.type === "group") ? "group" : ((node.type === "tab") ? "tab": "subflow"); - var container = $("#editor-"+kind+"-envProperties-content"); - var form = $('
                ').appendTo(container); - var listContainer = $('
                ').appendTo(form); - var list = $('
                  ').appendTo(listContainer); - 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', - 'type', - 'version', - 'author', - 'desc', - 'keywords', - 'license' - ].forEach(function(property) { - $("#subflow-input-module-"+property).val(moduleProps[property]||"") - }) - $("#subflow-input-module-type").attr("placeholder",node.id); - - 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 === "none" ? RED._("editor:subflow.licenseNone") : 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', - 'type', - '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, @@ -2095,14 +1922,10 @@ RED.subflow = (function() { removeOutput: removeSubflowOutput, removeStatus: removeSubflowStatus, - buildEditForm: buildEditForm, - buildPropertiesForm: buildPropertiesForm, - buildModuleForm: buildModuleForm, + buildPropertiesList: buildPropertiesList, exportSubflowTemplateEnv: exportEnvList, exportSubflowInstanceEnv: exportSubflowInstanceEnv, - exportSubflowModuleProperties: exportSubflowModuleProperties - } })(); From 741fe3dd90d773f4b7f38ce9320ad1e169795679 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 14:29:58 +0100 Subject: [PATCH 19/27] Move tab edit dialog into editor and use new edit panes --- .../editor-client/src/js/ui/editor.js | 135 ++++++++- ...bflowProperties.js => envVarProperties.js} | 5 +- .../src/js/ui/editors/panes/flowProperties.js | 63 ++++ .../editor-client/src/js/ui/group.js | 2 - .../editor-client/src/js/ui/subflow.js | 49 ++- .../editor-client/src/js/ui/workspaces.js | 281 +----------------- 6 files changed, 225 insertions(+), 310 deletions(-) rename packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/{subflowProperties.js => envVarProperties.js} (94%) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js 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 a2841cdde..3b61b861a 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 @@ -529,7 +529,7 @@ RED.editor = (function() { done(activeEditPanes); } } - if (definition.credentials || /^subflow:/.test(definition.type) || (node.type === "group")) { + if (definition.credentials || /^subflow:/.test(definition.type) || node.type === "group" || node.type === "tab") { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); @@ -1646,6 +1646,7 @@ RED.editor = (function() { var nodeEditPanes = [ 'editor-tab-properties', + 'editor-tab-envProperties', 'editor-tab-description' ]; prepareEditDialog(trayBody, nodeEditPanes, group,group._def,"node-input", null, function(_activeEditPanes) { @@ -1679,6 +1680,137 @@ RED.editor = (function() { RED.tray.show(trayOptions); } + function showEditFlowDialog(workspace) { + if (buildingEditDialog) { return } + buildingEditDialog = true; + var activeEditPanes = []; + RED.view.state(RED.state.EDITING); + var trayOptions = { + title: RED._("workspace.editFlow",{name:RED.utils.sanitize(workspace.label)}), + buttons: [ + { + id: "node-dialog-delete", + class: 'leftButton'+((RED.workspaces.count() === 1)?" disabled":""), + text: RED._("common.label.delete"), //'', + click: function() { + RED.workspaces.delete(workspace); + RED.tray.close(); + } + }, + { + id: "node-dialog-cancel", + text: RED._("common.label.cancel"), + click: function() { + RED.tray.close(); + } + }, + { + id: "node-dialog-ok", + class: "primary", + text: RED._("common.label.done"), + click: function() { + var editState = { + changes: {}, + changed: false, + outputMap: null + } + var wasDirty = RED.nodes.dirty(); + + activeEditPanes.forEach(function(pane) { + if (pane.apply) { + pane.apply.call(pane, workspace, editState); + } + }) + + var disabled = $("#node-input-disabled").prop("checked"); + if (workspace.disabled !== disabled) { + editState.changes.disabled = workspace.disabled; + editState.changed = true; + workspace.disabled = disabled; + } + + + if (editState.changed) { + var historyEvent = { + t: "edit", + changes: editState.changes, + node: workspace, + dirty: wasDirty + } + workspace.changed = true; + RED.history.push(historyEvent); + RED.nodes.dirty(true); + if (editState.changes.hasOwnProperty('disabled')) { + RED.nodes.eachNode(function(n) { + if (n.z === workspace.id) { + n.dirty = true; + } + }); + RED.view.redraw(); + } + RED.workspaces.refresh(); + RED.events.emit("flows:change",workspace); + } + RED.tray.close(); + } + } + ], + resize: function(dimensions) { + $(".red-ui-tray-content").height(dimensions.height - 50); + var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40); + var size = {width:form.width(),height:form.height()}; + activeEditPanes.forEach(function(pane) { + if (pane.resize) { + pane.resize.call(pane,editing_node, size); + } + }) + }, + open: function(tray, done) { + var trayFooter = tray.find(".red-ui-tray-footer"); + var trayBody = tray.find('.red-ui-tray-body'); + trayBody.parent().css('overflow','hidden'); + var trayFooterLeft = $('').appendTo(trayFooter) + + var nodeEditPanes = [ + 'editor-tab-flow-properties', + 'editor-tab-envProperties' + ]; + + if (!workspace.hasOwnProperty("disabled")) { + workspace.disabled = false; + } + $('').prop("checked",workspace.disabled).appendTo(trayFooterLeft).toggleButton({ + enabledIcon: "fa-circle-thin", + disabledIcon: "fa-ban", + invertState: true + }) + + prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", null, function(_activeEditPanes) { + activeEditPanes = _activeEditPanes; + trayBody.i18n(); + trayFooter.i18n(); + buildingEditDialog = false; + done(); + }); + }, + close: function() { + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { + RED.view.state(RED.state.DEFAULT); + } + activeEditPanes.forEach(function(pane) { + if (pane.close) { + pane.close.call(pane,editing_node); + } + }) + var selection = RED.view.selection(); + if (!selection.nodes && !selection.links && workspace.id === RED.workspaces.active()) { + RED.sidebar.info.refresh(workspace); + } + } + } + RED.tray.show(trayOptions); + } + function showTypeEditor(type, options) { if (customEditTypes.hasOwnProperty(type)) { if (editStack.length > 0) { @@ -1713,6 +1845,7 @@ RED.editor = (function() { }, edit: showEditDialog, editConfig: showEditConfigNodeDialog, + editFlow: showEditFlowDialog, editSubflow: showEditSubflowDialog, editGroup: showEditGroupDialog, editJavaScript: function(options) { showTypeEditor("_js",options) }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js similarity index 94% rename from packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js rename to packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js index f9071fac8..9d6fda1ea 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js @@ -21,7 +21,10 @@ }, apply: function(node, editState) { var old_env = node.env; - var new_env = RED.subflow.exportSubflowInstanceEnv(node); + var new_env = []; + if (/^subflow:/.test(node.type)) { + new_env = RED.subflow.exportSubflowInstanceEnv(node); + } // Get the values from the Properties table tab var items = this.list.editableList('items'); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js new file mode 100644 index 000000000..c72eed7a6 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js @@ -0,0 +1,63 @@ +;(function() { + + RED.editor.registerEditorPane("editor-tab-flow-properties", function() { + return { + label: RED._("editor-tab.properties"), + name: RED._("editor-tab.properties"), + iconClass: "fa fa-cog", + create: function(container, node) { + var content = $('
                  ', {class:"red-ui-tray-content"}).appendTo(container); + + var dialogForm = $('
                  ').appendTo(content); + $('
                  '+ + ''+ + ''+ + '
                  ').appendTo(dialogForm); + + var row = $('
                  '+ + ''+ + '
                  '+ + '
                  ').appendTo(dialogForm); + this.tabflowEditor = RED.editor.createEditor({ + id: 'node-input-info', + mode: 'ace/mode/markdown', + value: "" + }); + + $('').prependTo(dialogForm); + dialogForm.on("submit", function(e) { e.preventDefault();}); + + $("#node-input-name").val(node.label); + RED.text.bidi.prepareInput($("#node-input-name")); + this.tabflowEditor.getSession().setValue(node.info || "", -1); + return content; + }, + resize: function(node, size) { + $("#node-input-info").css("height", (size.height-70)+"px"); + this.tabflowEditor.resize(); + }, + close: function(node) { + this.tabflowEditor.destroy(); + }, + apply: function(workspace, editState) { + var label = $( "#node-input-name" ).val(); + + if (workspace.label != label) { + editState.changes.label = workspace.label; + editState.changed = true; + workspace.label = label; + } + + var info = this.tabflowEditor.getValue(); + if (workspace.info !== info) { + editState.changes.info = workspace.info; + editState.changed = true; + workspace.info = info; + } + $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); + $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + + } + } + }); +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 114bb7207..27b5f9f5a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -97,8 +97,6 @@ RED.group = (function() { }, category: "config", oneditprepare: function() { - RED.subflow.buildPropertiesForm(this); - var style = this.style || {}; RED.editor.colorPicker.create({ id:"node-input-style-stroke", 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 de40ea939..c236e4f89 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 @@ -948,9 +948,6 @@ RED.subflow = (function() { function buildPropertiesList(envContainer, node) { var isTemplateNode = (node.type === "subflow"); - var isGroup = (node.type === "group"); - var isTab = (node.type === "tab"); - var kind = isGroup ? "group" : (isTab ? "tab" : "subflow"); if (isTemplateNode) { buildEnvControl(envContainer, node); @@ -1812,30 +1809,26 @@ RED.subflow = (function() { function exportSubflowInstanceEnv(node) { var env = []; - var isGroup = (node.type === "group"); - var isTab = (node.type === "tab"); - - if (!isGroup && !isTab) { - // First, get the values for the SubflowTemplate defined properties - // - these are the ones with custom UI elements - var parentEnv = getSubflowInstanceParentEnv(node); - parentEnv.forEach(function(data) { - var item; - var ui = data.ui || {}; - if (!ui.type) { - if (data.parent && data.parent.type === "cred") { - ui.type = "cred"; - } else { - ui.type = "input"; - ui.opts = {types:DEFAULT_ENV_TYPE_LIST} - } + // First, get the values for the SubflowTemplate defined properties + // - these are the ones with custom UI elements + var parentEnv = getSubflowInstanceParentEnv(node); + parentEnv.forEach(function(data) { + var item; + var ui = data.ui || {}; + if (!ui.type) { + if (data.parent && data.parent.type === "cred") { + ui.type = "cred"; } else { - ui.opts = ui.opts || {}; + ui.type = "input"; + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} } - var input = $("#"+getSubflowEnvPropertyName(data.name)); - if (input.length || ui.type === "cred") { - item = { name: data.name }; - switch(ui.type) { + } else { + ui.opts = ui.opts || {}; + } + var input = $("#"+getSubflowEnvPropertyName(data.name)); + if (input.length || ui.type === "cred") { + item = { name: data.name }; + switch(ui.type) { case "input": if (ui.opts.types && ui.opts.types.length > 0) { item.value = input.typedInput('value'); @@ -1861,10 +1854,6 @@ RED.subflow = (function() { item.type = 'bool'; item.value = ""+input.prop("checked"); break; - } - if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { - env.push(item); - } } if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { env.push(item); @@ -1926,6 +1915,6 @@ RED.subflow = (function() { buildPropertiesList: buildPropertiesList, exportSubflowTemplateEnv: exportEnvList, - exportSubflowInstanceEnv: exportSubflowInstanceEnv, + exportSubflowInstanceEnv: exportSubflowInstanceEnv } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 04c904c8b..3f151a851 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -75,81 +75,6 @@ RED.workspaces = (function() { RED.sidebar.config.refresh(); } - var tabflowEditor; - - function buildProperties(container, workspace) { - var dialogForm = $('
                  ').appendTo(container); - $('
                  '+ - ''+ - ''+ - '
                  ').appendTo(dialogForm); - - var row = $('
                  '+ - ''+ - '
                  '+ - '
                  ').appendTo(dialogForm); - tabflowEditor = RED.editor.createEditor({ - id: 'node-input-info', - mode: 'ace/mode/markdown', - value: "" - }); - - $('#node-info-input-info-expand').on("click", function(e) { - e.preventDefault(); - var value = tabflowEditor.getValue(); - RED.editor.editMarkdown({ - value: value, - width: "Infinity", - cursor: tabflowEditor.getCursorPosition(), - complete: function(v,cursor) { - tabflowEditor.setValue(v, -1); - tabflowEditor.gotoLine(cursor.row+1,cursor.column,false); - setTimeout(function() { - tabflowEditor.focus(); - },300); - } - }) - }); - - $('').prependTo(dialogForm); - dialogForm.on("submit", function(e) { e.preventDefault();}); - - $("#node-input-name").val(workspace.label); - RED.text.bidi.prepareInput($("#node-input-name")); - tabflowEditor.getSession().setValue(workspace.info || "", -1); - dialogForm.i18n(); - } - - function getNodeCredentials(type, id, done) { - var timeoutNotification; - var intialTimeout = setTimeout(function() { - timeoutNotification = RED.notify($('

                  ').i18n(),{fixed: true}) - },800); - - $.ajax({ - url: "credentials/tab/" + id, - dataType: 'json', - success: function(data) { - if (timeoutNotification) { - timeoutNotification.close(); - timeoutNotification = null; - } - clearTimeout(intialTimeout); - done(data); - }, - error: function(jqXHR,status,error) { - if (timeoutNotification) { - timeoutNotification.close(); - timeoutNotification = null; - } - clearTimeout(intialTimeout); - RED.notify(RED._("editor.errors.credentialLoadFailed"),"error") - done(null); - }, - timeout: 30000, - }); - } - function showEditWorkspaceDialog(id) { var workspace = RED.nodes.workspace(id); if (!workspace) { @@ -157,207 +82,9 @@ RED.workspaces = (function() { if (subflow) { RED.editor.editSubflow(subflow); } - return; + } else { + RED.editor.editFlow(workspace); } - RED.view.state(RED.state.EDITING); - var trayOptions = { - title: RED._("workspace.editFlow",{name:RED.utils.sanitize(workspace.label)}), - buttons: [ - { - id: "node-dialog-delete", - class: 'leftButton'+((workspaceTabCount === 1)?" disabled":""), - text: RED._("common.label.delete"), //'', - click: function() { - deleteWorkspace(workspace); - RED.tray.close(); - } - }, - { - id: "node-dialog-cancel", - text: RED._("common.label.cancel"), - click: function() { - RED.tray.close(); - } - }, - { - id: "node-dialog-ok", - class: "primary", - text: RED._("common.label.done"), - click: function() { - var label = $( "#node-input-name" ).val(); - var changed = false; - var changes = {}; - if (workspace.label != label) { - changes.label = workspace.label; - changed = true; - workspace.label = label; - workspace_tabs.renameTab(workspace.id,label); - } - var disabled = $("#node-input-disabled").prop("checked"); - if (workspace.disabled !== disabled) { - changes.disabled = workspace.disabled; - changed = true; - workspace.disabled = disabled; - } - var info = tabflowEditor.getValue(); - if (workspace.info !== info) { - changes.info = workspace.info; - changed = true; - workspace.info = info; - } - $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); - $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); - - var old_env = workspace.env; - var new_env = RED.subflow.exportSubflowInstanceEnv(workspace); - if (new_env && (new_env.length > 0)) { - new_env.forEach(function(prop) { - if (prop.type === "cred") { - workspace.credentials = workspace.credentials || {_:{}}; - workspace.credentials[prop.name] = prop.value; - workspace.credentials['has_'+prop.name] = (prop.value !== ""); - if (prop.value !== '__PWRD__') { - changed = true; - } - delete prop.value; - } - }); - } - if (!isSameObj(old_env, new_env)) { - workspace.env = new_env; - changes.env = old_env; - changed = true; - } - - if (changed) { - var historyEvent = { - t: "edit", - changes:changes, - node: workspace, - dirty: RED.nodes.dirty() - } - workspace.changed = true; - RED.history.push(historyEvent); - RED.nodes.dirty(true); - RED.sidebar.config.refresh(); - if (changes.hasOwnProperty('disabled')) { - RED.nodes.eachNode(function(n) { - if (n.z === workspace.id) { - n.dirty = true; - } - }); - RED.view.redraw(); - } - RED.events.emit("flows:change",workspace); - } - RED.tray.close(); - } - } - ], - resize: function(dimensions) { - var height = dimensions.height; - - var rows = $("#dialog-form>div:not(.node-text-editor-row)"); - var editorRow = $("#dialog-form>div.node-text-editor-row"); - for (var i=0; i
                  ').appendTo(trayFooter) - - var editorTabEl = $('
                    ').appendTo(trayBody); - var editorContent = $('
                    ').appendTo(trayBody); - - var finishedBuilding = false; - - var editorTabs = RED.tabs.create({ - element:editorTabEl, - onchange:function(tab) { - editorContent.children().hide(); - if (tab.onchange) { - tab.onchange.call(tab); - } - tab.content.show(); - if (finishedBuilding) { - RED.tray.resize(); - } - }, - collapsible: true, - menu: false - }); - - var nodePropertiesTab = { - id: "editor-tab-properties", - label: RED._("editor-tab.properties"), - name: RED._("editor-tab.properties"), - content: $('
                    ', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), - iconClass: "fa fa-cog" - }; - buildProperties(nodePropertiesTab.content, workspace); - editorTabs.addTab(nodePropertiesTab); - - var tabPropertiesTab = { - id: "editor-tab-envProperties", - label: RED._("editor-tab.envProperties"), - name: RED._("editor-tab.envProperties"), - content: $('
                    ', { - id: "editor-tab-envProperties-content", - class: "red-ui-tray-content" - }).appendTo(editorContent).hide(), - iconClass: "fa fa-list", - }; - - function cb() { - RED.subflow.buildPropertiesForm(workspace); - editorTabs.addTab(tabPropertiesTab); - - if (!workspace.hasOwnProperty("disabled")) { - workspace.disabled = false; - } - - $('').prop("checked",workspace.disabled).appendTo(trayFooterLeft).toggleButton({ - enabledIcon: "fa-circle-thin", - disabledIcon: "fa-ban", - invertState: true - }) - finishedBuilding = true; - RED.tray.resize(); - } - - if (workspace.credentials) { - cb(); - } - else { - getNodeCredentials("tab", workspace.id, function(data) { - if (data) { - workspace.credentials = data; - workspace.credentials._ = $.extend(true,{},data); - } - cb(); - }); - } - - }, - close: function() { - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { - RED.view.state(RED.state.DEFAULT); - } - var selection = RED.view.selection(); - if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { - RED.sidebar.info.refresh(workspace); - } - tabflowEditor.destroy(); - } - } - RED.tray.show(trayOptions); } @@ -566,7 +293,6 @@ RED.workspaces = (function() { } } - function removeWorkspace(ws) { if (!ws) { deleteWorkspace(RED.nodes.workspace(activeWorkspace)); @@ -595,7 +321,10 @@ RED.workspaces = (function() { return { init: init, add: addWorkspace, + // remove: remove workspace without editor history etc remove: removeWorkspace, + // delete: remove workspace and update editor history + delete: deleteWorkspace, order: setWorkspaceOrder, edit: editWorkspace, contains: function(id) { From 4463a7d4bae93f88351af53ae32d8b3a96c4b17b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Sep 2021 16:14:35 +0100 Subject: [PATCH 20/27] Move envVar list component to own file --- .../@node-red/editor-client/src/js/nodes.js | 43 +- .../src/js/ui/editors/envVarList.js | 616 +++++++++++++++++ .../js/ui/editors/panes/envVarProperties.js | 2 +- .../src/js/ui/editors/panes/properties.js | 7 - .../js/ui/editors/panes/subflowProperties.js | 63 ++ .../editor-client/src/js/ui/subflow.js | 634 +----------------- 6 files changed, 721 insertions(+), 644 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowProperties.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2d442eea9..8afff55b2 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 @@ -593,10 +593,11 @@ RED.nodes = (function() { outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, oneditprepare: function() { if (this.type !== 'subflow') { - // Whilst this definition is used for both template and instance - // nodes, the work to build the edit form for the template nodes - // is handled elsewhere. + // A subflow instance node RED.subflow.buildEditForm("subflow",this); + } else { + // A subflow template node + RED.subflow.buildEditForm("subflow-template", this); } }, oneditresize: function(size) { @@ -681,7 +682,13 @@ RED.nodes = (function() { } - function convertWorkspace(n) { + function convertWorkspace(n,opts) { + var exportCreds = true; + if (opts) { + if (opts.hasOwnProperty("credentials")) { + exportCreds = opts.credentials; + } + } var node = {}; node.id = n.id; node.type = n.type; @@ -690,19 +697,21 @@ RED.nodes = (function() { node[d] = n[d]; } } - var credentialSet = {}; - if (n.credentials) { - for (var tabCred in n.credentials) { - if (n.credentials.hasOwnProperty(tabCred)) { - if (!n.credentials._ || - n.credentials["has_"+tabCred] != n.credentials._["has_"+tabCred] || - (n.credentials["has_"+tabCred] && n.credentials[tabCred])) { - credentialSet[tabCred] = n.credentials[tabCred]; + if (exportCreds) { + var credentialSet = {}; + if (n.credentials) { + for (var tabCred in n.credentials) { + if (n.credentials.hasOwnProperty(tabCred)) { + if (!n.credentials._ || + n.credentials["has_"+tabCred] != n.credentials._["has_"+tabCred] || + (n.credentials["has_"+tabCred] && n.credentials[tabCred])) { + credentialSet[tabCred] = n.credentials[tabCred]; + } } } - } - if (Object.keys(credentialSet).length > 0) { - node.credentials = credentialSet; + if (Object.keys(credentialSet).length > 0) { + node.credentials = credentialSet; + } } } return node; @@ -725,7 +734,7 @@ RED.nodes = (function() { } if (n.type === 'tab') { - return convertWorkspace(n); + return convertWorkspace(n, { credentials: exportCreds }); } var node = {}; node.id = n.id; @@ -1047,7 +1056,7 @@ RED.nodes = (function() { var i; for (i=0;i
                    '):undefined, + addItem: function(container, i, opt) { + // If this is an instance node, these are properties unique to + // this instance - ie opt.parent will not be defined. + + if (isTemplateNode) { + container.addClass("red-ui-editor-subflow-env-editable") + } + + var envRow = $('
                    ').appendTo(container); + var nameField = null; + var valueField = null; + + 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:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); + valueField.typedInput('type', opt.type); + if (opt.type === "cred") { + if (opt.value) { + valueField.typedInput('value', opt.value); + } else if (node.credentials && node.credentials[opt.name]) { + valueField.typedInput('value', node.credentials[opt.name]); + } else if (node.credentials && node.credentials['has_'+opt.name]) { + valueField.typedInput('value', "__PWRD__"); + } else { + valueField.typedInput('value', ""); + } + } else { + valueField.typedInput('value', opt.value); + } + + + opt.nameField = nameField; + opt.valueField = valueField; + + 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. + if (opt.type === 'cred') { + opt.ui = opt.ui || { + icon: "", + type: "cred" + } + opt.ui.type = "cred"; + } else { + opt.ui = opt.ui || { + icon: "", + type: "input", + opts: {types:DEFAULT_ENV_TYPE_LIST} + } + } + 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:DEFAULT_ENV_TYPE_LIST}, + 'select': {opts:[]}, + 'spinner': {}, + 'cred': {} + }; + 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.iconPicker.show(iconButton, 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:"120px"}).text(RED._("languages."+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:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ + {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, + {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, + {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, + {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, + {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, + {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"}, + {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"} + ], + default: DEFAULT_ENV_TYPE_LIST, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
                    ').appendTo(container); + + var input = $('
                    ').appendTo(innerContainer); + $('').appendTo(input); + if (value.length) { + value.forEach(function(v) { + if (!/^fa /.test(v.icon)) { + $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + } else { + var s = $('',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + $("",{class: v.icon}).appendTo(s); + } + }) + } else { + $('').text(RED._("editor.selectType")).appendTo(input); + } + } + }, + { + value: "cred", + label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
                    ').css({ + "border-top-right-radius": "4px", + "border-bottom-right-radius": "4px" + }).appendTo(container); + $('
                    ').html("••••••••").appendTo(innerContainer); + } + }, + { + value:"select", + label:RED._("editor.inputs.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); + // $('