From a42d7d867e83c4a5b65be7df59ca4efd844cd667 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Fri, 26 Jul 2019 11:36:22 +0900 Subject: [PATCH 001/366] Add a libe break function --- .../@node-red/editor-client/src/js/ui/view.js | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 57cce2852..62282e624 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1805,7 +1805,15 @@ RED.view = (function() { } function calculateTextWidth(str, className, offset) { - return calculateTextDimensions(str,className,offset,0)[0]; + var result=convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i0?7:0))/20)) ); } } + if (hideLabel) { + node_height = 30; + } else { + node_height = 6 + 24 * convertLineBreakCharacter(l).length; + } d.h = Math.max(node_height,(d.outputs||0) * 15); // if (d._def.badge) { @@ -2706,7 +2742,7 @@ RED.view = (function() { .attr("rx",5) .attr("ry",5) .attr("width",32) - .attr("height",node_height-4); + .attr("height",26); nodeButtonGroup.append("rect") .attr("class","red-ui-flow-node-button-button") .attr("x",function(d) { return d._def.align == "right"? 11:5}) @@ -2714,7 +2750,7 @@ RED.view = (function() { .attr("rx",4) .attr("ry",4) .attr("width",16) - .attr("height",node_height-12) + .attr("height",18) .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) .attr("cursor","pointer") .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) @@ -2925,19 +2961,25 @@ RED.view = (function() { var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; dirtyNodes[d.id] = d; //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette + var l = RED.utils.getNodeLabel(d); if (d.resize) { - var l = RED.utils.getNodeLabel(d); var ow = d.w; if (hideLabel) { - d.w = node_height; + d.w = 30; } else { d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); - d.h = Math.max(node_height,(d.outputs||0) * 15); d.x += (d.w-ow)/2; d.resize = false; } + if (hideLabel) { + node_height = 30; + } else { + node_height = 6 + 24 * convertLineBreakCharacter(l).length; + } + d.h = Math.max(node_height,(d.outputs || 0) * 15); + var thisNode = d3.select(this); thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); thisNode.classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }) @@ -2958,10 +3000,11 @@ RED.view = (function() { thisNode.selectAll(".red-ui-flow-node-icon-group").classed("red-ui-flow-node-icon-group-right", false); thisNode.selectAll(".red-ui-flow-node-label").classed("red-ui-flow-node-label-right", false).attr("text-anchor", "start"); } + var gx; thisNode.selectAll(".red-ui-flow-node-icon-group").attr("transform", function (d) { return "translate(0, 0)"; }); - thisNode.selectAll(".red-ui-flow-node-label").attr("x", function (d) { return 38; }); + thisNode.selectAll(".red-ui-flow-node-label").attr("x", function (d) { gx=38; return 38; }); thisNode.selectAll(".red-ui-flow-node-icon-group-right").attr("transform", function(d){return "translate("+(d.w-30)+",0)"}); - thisNode.selectAll(".red-ui-flow-node-label-right").attr("x", function(d){return d.w-38}); + thisNode.selectAll(".red-ui-flow-node-label-right").attr("x", function(d){ gx=d.w-38; return d.w-38}); //thisNode.selectAll(".red-ui-flow-node-icon-right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3035,7 +3078,7 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").text(function(d,i){ + thisNode.selectAll("text.red-ui-flow-node-label").html(function(d,i){ var l = ""; if (d._def.label) { l = d._def.label; @@ -3047,7 +3090,22 @@ RED.view = (function() { l = d.type; } } - return l; + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var ic = 0; + var st = ""; + var yp = d.h/2-(sn/2)*24+16 + var yn = 0; + var dy = ".3px"; + for (ic=0; ic"+sa[ic]+""; + } + if (sn!=1) { + return st; + } else { + return sa[0]!=null ? sa[0]:l; + } }) .attr("y", function(d){return (d.h/2)-1;}) .attr("class",function(d){ From 17653761b9b2a31b2466e79802ed907f3edd3b94 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Tue, 3 Sep 2019 18:43:46 +0900 Subject: [PATCH 002/366] Update a line break function --- .../@node-red/editor-client/src/js/ui/view.js | 192 ++++++++++-------- 1 file changed, 109 insertions(+), 83 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 62282e624..ffd30af59 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1803,7 +1803,7 @@ RED.view = (function() { RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } - + function calculateTextWidth(str, className, offset) { var result=convertLineBreakCharacter(str); var width = 0; @@ -1831,26 +1831,35 @@ RED.view = (function() { return [offsetW+w,offsetH+h]; } + var separateTextByLineBreak = []; function convertLineBreakCharacter(str) { var result = []; - var temp = str.split('\\n'); - var result_temp = null; - for (var i = 0;i < temp.length;i++) { - if (result_temp == null || result_temp == '') { - result_temp = temp[i]; - } - var chr = temp[i].charAt(temp[i].length-1); - if (i < temp.length - 1) { - if (chr == '\\') { - result_temp += 'n' + temp[i+1]; - } else { + var count = 0; + var result_temp = ''; + for (var i = 0;i < str.length;i++) { + if (str.charAt(i) == '\\') { + if (str.charAt(i+1) == '\\') { + result_temp += str.charAt(i); + i++; + } else if (str.charAt(i+1) == 'n') { result.push(result_temp); - result_temp = null; - } + if (i+1 == str.length-1) { + result.push(''); + } + result_temp = ''; + count = i+2; + i++; + } else { + result_temp += str.charAt(i); + } } else { - result.push(result_temp); + result_temp += str.charAt(i); } } + if (count == 0 || count < str.length) { + result.push(result_temp); + } + separateTextByLineBreak = result; return result; } @@ -2708,21 +2717,19 @@ RED.view = (function() { var isLink = (d.type === "link in" || d.type === "link out") var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); - var l = RED.utils.getNodeLabel(d); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); if (d.resize || d.w === undefined) { if (hideLabel) { - d.w = 30; + d.w = node_height; } else { - d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } } if (hideLabel) { - node_height = 30; + d.h = Math.max(node_height,(d.outputs || 0) * 15); } else { - node_height = 6 + 24 * convertLineBreakCharacter(l).length; + d.h = Math.max(6+24*separateTextByLineBreak.length, (d.outputs || 0) * 15, 30); } - d.h = Math.max(node_height,(d.outputs||0) * 15); - // if (d._def.badge) { // var badge = node.append("svg:g").attr("class","node_badge_group"); // var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); @@ -2742,7 +2749,7 @@ RED.view = (function() { .attr("rx",5) .attr("ry",5) .attr("width",32) - .attr("height",26); + .attr("height",node_height-4); nodeButtonGroup.append("rect") .attr("class","red-ui-flow-node-button-button") .attr("x",function(d) { return d._def.align == "right"? 11:5}) @@ -2750,7 +2757,7 @@ RED.view = (function() { .attr("rx",4) .attr("ry",4) .attr("width",16) - .attr("height",18) + .attr("height",node_height-12) .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) .attr("cursor","pointer") .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) @@ -2908,17 +2915,21 @@ RED.view = (function() { //icon.style("pointer-events","none"); icon_group.style("pointer-events","none"); } - var text = node.append("svg:text") + var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + for(var i=0;i0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); d.x += (d.w-ow)/2; d.resize = false; } if (hideLabel) { - node_height = 30; + d.h = Math.max(node_height,(d.outputs || 0) * 15); } else { - node_height = 6 + 24 * convertLineBreakCharacter(l).length; + d.h = Math.max(6+24*separateTextByLineBreak.length,(d.outputs || 0) * 15, 30); } - d.h = Math.max(node_height,(d.outputs || 0) * 15); var thisNode = d3.select(this); thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); @@ -2993,6 +3004,65 @@ RED.view = (function() { .attr("height",function(d){return d.h}) .classed("red-ui-flow-node-highlighted",function(d) { return d.highlighted; }) ; + var l = ""; + if (d._def.label) { + l = d._def.label; + try { + l = (typeof l === "function" ? l.call(d) : l)||""; + l = RED.text.bidi.enforceTextDirectionWithUCC(l); + } catch(err) { + console.log("Definition error: "+d.type+".label",err); + l = d.type; + } + } + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var st = ""; + var yp = d.h / 2 - (sn / 2) * 24 + 16 + if(labelLineNumber0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3078,50 +3148,6 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").html(function(d,i){ - var l = ""; - if (d._def.label) { - l = d._def.label; - try { - l = (typeof l === "function" ? l.call(d) : l)||""; - l = RED.text.bidi.enforceTextDirectionWithUCC(l); - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - l = d.type; - } - } - var sa = convertLineBreakCharacter(l); - var sn = sa.length; - var ic = 0; - var st = ""; - var yp = d.h/2-(sn/2)*24+16 - var yn = 0; - var dy = ".3px"; - for (ic=0; ic"+sa[ic]+""; - } - if (sn!=1) { - return st; - } else { - return sa[0]!=null ? sa[0]:l; - } - }) - .attr("y", function(d){return (d.h/2)-1;}) - .attr("class",function(d){ - var s = ""; - if (d._def.labelStyle) { - s = d._def.labelStyle; - try { - s = (typeof s === "function" ? s.call(d) : s)||""; - } catch(err) { - console.log("Definition error: "+d.type+".labelStyle",err); - s = ""; - } - s = " "+s; - } - return "red-ui-flow-node-label"+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")+s; - }).classed("hide",hideLabel); if (d._def.icon) { var icon = thisNode.select(".red-ui-flow-node-icon"); var faIcon = thisNode.select(".fa-lg"); From 7957ec43692f00277f96ed23de509a7e841e86a5 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Fri, 27 Sep 2019 19:17:17 +0900 Subject: [PATCH 003/366] Modify id --- .../@node-red/editor-client/src/js/ui/view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index ffd30af59..9e2a7258f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -2916,10 +2916,11 @@ RED.view = (function() { icon_group.style("pointer-events","none"); } var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + var labelId = d.id.replace(".","-"); for(var i=0;i Date: Mon, 11 Nov 2019 18:25:36 +0900 Subject: [PATCH 004/366] Add node installation from other than public site --- .../@node-red/editor-api/lib/admin/nodes.js | 1 + .../editor-client/src/js/ui/palette-editor.js | 13 ++++--- .../@node-red/registry/lib/installer.js | 8 +++-- .../@node-red/runtime/lib/api/nodes.js | 11 +++--- .../@node-red/runtime/lib/nodes/index.js | 4 +-- .../editor-api/lib/admin/nodes_spec.js | 5 +-- .../@node-red/registry/lib/installer_spec.js | 34 +++++++++++++++++++ 7 files changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js index 2787a5c36..b78de9d75 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js @@ -44,6 +44,7 @@ module.exports = { user: req.user, module: req.body.module, version: req.body.version, + url: req.body.url, req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.addModule(opts).then(function(info) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 074a54469..a89003856 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -75,13 +75,16 @@ RED.palette.editor = (function() { }); }) } - function installNodeModule(id,version,callback) { + function installNodeModule(id,version,url,callback) { var requestBody = { module: id }; if (version) { requestBody.version = version; } + if (url) { + requestBody.url = url; + } $.ajax({ url:"nodes", type: "POST", @@ -622,7 +625,7 @@ RED.palette.editor = (function() { if ($(this).hasClass('disabled')) { return; } - update(entry,loadedIndex[entry.name].version,container,function(err){}); + update(entry,loadedIndex[entry.name].version,loadedIndex[entry.name].pkg_url,container,function(err){}); }) @@ -872,7 +875,7 @@ RED.palette.editor = (function() { $('
').appendTo(installTab); } - function update(entry,version,container,done) { + function update(entry,version,url,container,done) { if (RED.settings.theme('palette.editable') === false) { done(new Error('Palette not editable')); return; @@ -898,7 +901,7 @@ RED.palette.editor = (function() { RED.actions.invoke("core:show-event-log"); }); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version); - installNodeModule(entry.name,version,function(xhr) { + installNodeModule(entry.name,version,url,function(xhr) { spinner.remove(); if (xhr) { if (xhr.responseJSON) { @@ -1023,7 +1026,7 @@ RED.palette.editor = (function() { RED.actions.invoke("core:show-event-log"); }); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version); - installNodeModule(entry.id,entry.version,function(xhr) { + installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) { spinner.remove(); if (xhr) { if (xhr.responseJSON) { diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 3562dfb47..e70952832 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -32,6 +32,7 @@ var paletteEditorEnabled = false; var settings; var moduleRe = /^(@[^/]+?[/])?[^/]+?$/; var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; +var pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; function init(runtime) { events = runtime.events; @@ -76,14 +77,17 @@ function checkExistingModule(module,version) { } return false; } -function installModule(module,version) { +function installModule(module,version,url) { activePromise = activePromise.then(() => { //TODO: ensure module is 'safe' return new Promise((resolve,reject) => { var installName = module; var isUpgrade = false; try { - if (moduleRe.test(module)) { + if (url && pkgurlRe.test(url)) { + // Git remote url or Tarball url - check the valid package url + installName = url; + } else if (moduleRe.test(module)) { // Simple module name - assume it can be npm installed if (version) { installName += "@"+version; diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index ee4d3bc1a..fb16cc631 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -159,6 +159,7 @@ var api = module.exports = { * @param {User} opts.user - the user calling the api * @param {String} opts.module - the id of the module to install * @param {String} opts.version - (optional) the version of the module to install + * @param {String} opts.url - (optional) url to install * @param {Object} opts.req - the request to log (optional) * @return {Promise} - the node module info * @memberof @node-red/runtime_nodes @@ -183,20 +184,20 @@ var api = module.exports = { return reject(err); } } - runtime.nodes.installModule(opts.module,opts.version).then(function(info) { - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}, opts.req); + runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) { + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req); return resolve(info); }).catch(function(err) { if (err.code === 404) { - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}, opts.req); + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req); // TODO: code/status err.status = 404; } else if (err.code) { err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}, opts.req); + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); } else { err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); } return reject(err); }) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index fcb153fe1..09aebe6eb 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -150,10 +150,10 @@ function reportNodeStateChange(info,enabled) { } } -function installModule(module,version) { +function installModule(module,version,url) { var existingModule = registry.getModuleInfo(module); var isUpgrade = !!existingModule; - return registry.installModule(module,version).then(function(info) { + return registry.installModule(module,version,url).then(function(info) { if (isUpgrade) { events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}}); } else { diff --git a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js index ef98fb677..0d373b8d0 100644 --- a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js @@ -227,7 +227,7 @@ describe("api/admin/nodes", function() { }); request(app) .post('/nodes') - .send({module: 'foo',version:"1.2.3"}) + .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"}) .expect(200) .end(function(err,res) { if (err) { @@ -238,6 +238,7 @@ describe("api/admin/nodes", function() { res.body.nodes[0].should.have.property("id","123"); opts.should.have.property("module","foo"); opts.should.have.property("version","1.2.3"); + opts.should.have.property("url","https://example/foo-1.2.3.tgz"); done(); }); }); @@ -256,7 +257,7 @@ describe("api/admin/nodes", function() { }); request(app) .post('/nodes') - .send({module: 'foo',version:"1.2.3"}) + .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"}) .expect(400) .end(function(err,res) { if (err) { diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js index f3bae8b3a..045a81e0d 100644 --- a/test/unit/@node-red/registry/lib/installer_spec.js +++ b/test/unit/@node-red/registry/lib/installer_spec.js @@ -121,6 +121,17 @@ describe('nodes/registry/installer', function() { done(); }); }); + it("rejects when update requested to existing version and url", function(done) { + sinon.stub(typeRegistry,"getModuleInfo", function() { + return { + version: "0.1.1" + } + }); + installer.installModule("this_wont_exist","0.1.1","https://example/foo-0.1.1.tgz").catch(function(err) { + err.code.should.be.eql('module_already_loaded'); + done(); + }); + }); it("rejects with generic error", function(done) { var res = { code: 1, @@ -201,6 +212,29 @@ describe('nodes/registry/installer', function() { done(err); }); }); + it("succeeds when url is valid node-red module", function(done) { + var nodeInfo = {nodes:{module:"foo",types:["a"]}}; + + var res = { + code: 0, + stdout:"", + stderr:"" + } + var p = Promise.resolve(res); + p.catch((err)=>{}); + initInstaller(p) + + var addModule = sinon.stub(registry,"addModule",function(md) { + return when.resolve(nodeInfo); + }); + + installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) { + info.should.eql(nodeInfo); + done(); + }).catch(function(err) { + done(err); + }); + }); }); describe("uninstalls module", function() { From a54ca699b5c7d845bee97099b28ccfdee3be6a3d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 13 Nov 2019 10:06:25 +0000 Subject: [PATCH 005/366] Scroll the view with WASD/Cursor keys when nothing selected --- .../@node-red/editor-client/src/js/keymap.json | 8 ++++++++ .../@node-red/editor-client/src/js/ui/view-tools.js | 12 ++++++++++++ .../@node-red/editor-client/src/js/ui/view.js | 4 ++++ 3 files changed, 24 insertions(+) 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 2677321b7..b7d25e160 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 @@ -44,6 +44,14 @@ "ctrl-y": "core:redo", "ctrl-a": "core:select-all-nodes", "shift-?": "core:show-help", + "w": "core:scroll-view-up", + "d": "core:scroll-view-right", + "s": "core:scroll-view-down", + "a": "core:scroll-view-left", + "shift-w": "core:step-view-up", + "shift-d": "core:step-view-right", + "shift-s": "core:step-view-down", + "shift-a": "core:step-view-left", "up": "core:move-selection-up", "right": "core:move-selection-right", "down": "core:move-selection-down", 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 0814892be..539fb763d 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 @@ -105,6 +105,8 @@ RED.view.tools = (function() { } } RED.view.redraw(); + } else { + RED.view.scroll(dx*10,dy*10); } } @@ -112,6 +114,16 @@ RED.view.tools = (function() { init: function() { 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());}); + RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);}); + + RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());}); + RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);}); + RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());}); + RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);}); + RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);}); RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);}); RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);}); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 0967c630e..534475e09 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3846,6 +3846,10 @@ RED.view = (function() { type: "compact", buttons: buttons }) + }, + scroll: function(x,y) { + chart.scrollLeft(chart.scrollLeft()+x); + chart.scrollTop(chart.scrollTop()+y) } }; })(); From 021df83c3f30ce0081d5754d60778addf7e8582c Mon Sep 17 00:00:00 2001 From: Amo DelBello Date: Fri, 29 Nov 2019 18:50:30 -0700 Subject: [PATCH 006/366] Replace 'clone' with 'lodash.clonedeep' --- .gitignore | 1 + package.json | 1 + packages/node_modules/@node-red/util/lib/util.js | 5 +++-- packages/node_modules/@node-red/util/package.json | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c3fa9624a..7b1f1f219 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ packages/node_modules/@node-red/editor-client/public !test/**/node_modules docs !packages/node_modules/**/docs +.vscode \ No newline at end of file diff --git a/package.json b/package.json index b0b30df07..984f0f00d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", "jsonata": "1.7.0", + "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.1", "mime": "2.4.4", diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index ef43739e6..e223fc84b 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -19,8 +19,8 @@ * @mixin @node-red/util_util */ - const clone = require("clone"); +const clonedeep = require("lodash.clonedeep"); const jsonata = require("jsonata"); const safeJSONStringify = require("json-stringify-safe"); const util = require("util"); @@ -87,7 +87,8 @@ function cloneMessage(msg) { var res = msg.res; delete msg.req; delete msg.res; - var m = clone(msg); + + var m = clonedeep(msg); if (req) { m.req = req; msg.req = req; diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 174db5bfd..b89559d1f 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -19,6 +19,7 @@ "i18next": "15.1.2", "json-stringify-safe": "5.0.1", "jsonata": "1.7.0", + "lodash.clonedeep": "^4.5.0", "when": "3.7.8" } } From d017dd75cd1f7394bd32f2a0e11ae946697ca7e3 Mon Sep 17 00:00:00 2001 From: Amo DelBello Date: Fri, 29 Nov 2019 20:15:56 -0700 Subject: [PATCH 007/366] Remove 'clone' from util --- packages/node_modules/@node-red/util/lib/util.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index e223fc84b..387bc0728 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -19,7 +19,6 @@ * @mixin @node-red/util_util */ -const clone = require("clone"); const clonedeep = require("lodash.clonedeep"); const jsonata = require("jsonata"); const safeJSONStringify = require("json-stringify-safe"); From fe0d4f08f35e06412cc83f68a41c2f99fe07117b Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Wed, 25 Dec 2019 06:21:55 +0300 Subject: [PATCH 008/366] Allow to know particular session from status node The rationale is to keep own list of active sessions. As a workaround for https://discourse.nodered.org/t/tcp-connection-pool-better-separation/19432 TIA --- .../node_modules/@node-red/nodes/core/network/31-tcpin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js index e52e9c382..655e72c1e 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js @@ -74,7 +74,7 @@ module.exports = function(RED) { buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : ""; node.connected = true; node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port})); - node.status({fill:"green",shape:"dot",text:"common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}}); }); client.setKeepAlive(true,120000); connectionPool[id] = client; @@ -121,7 +121,7 @@ module.exports = function(RED) { client.on('close', function() { delete connectionPool[id]; node.connected = false; - node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); + node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}}); if (!node.closing) { if (end) { // if we were asked to close then try to reconnect once very quick. end = false; From 74a015c329703fdf7ede44a207410d59c295855a Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 15 Jan 2020 11:40:48 +0900 Subject: [PATCH 009/366] Change types from text/x-red to text/html in node html files --- .../@node-red/nodes/core/common/20-inject.html | 2 +- .../@node-red/nodes/core/common/21-debug.html | 2 +- .../@node-red/nodes/core/common/24-complete.html | 2 +- .../@node-red/nodes/core/common/25-catch.html | 2 +- .../@node-red/nodes/core/common/25-status.html | 2 +- .../@node-red/nodes/core/common/60-link.html | 4 ++-- .../@node-red/nodes/core/common/90-comment.html | 2 +- .../@node-red/nodes/core/common/98-unknown.html | 2 +- .../@node-red/nodes/core/function/10-function.html | 2 +- .../@node-red/nodes/core/function/10-switch.html | 2 +- .../@node-red/nodes/core/function/15-change.html | 2 +- .../@node-red/nodes/core/function/16-range.html | 2 +- .../@node-red/nodes/core/function/80-template.html | 2 +- .../@node-red/nodes/core/function/89-delay.html | 2 +- .../@node-red/nodes/core/function/89-trigger.html | 2 +- .../@node-red/nodes/core/function/90-exec.html | 2 +- .../@node-red/nodes/core/network/05-tls.html | 2 +- .../@node-red/nodes/core/network/06-httpproxy.html | 2 +- .../@node-red/nodes/core/network/10-mqtt.html | 6 +++--- .../@node-red/nodes/core/network/21-httpin.html | 4 ++-- .../@node-red/nodes/core/network/21-httprequest.html | 2 +- .../@node-red/nodes/core/network/22-websocket.html | 8 ++++---- .../@node-red/nodes/core/network/31-tcpin.html | 6 +++--- .../@node-red/nodes/core/network/32-udp.html | 4 ++-- .../@node-red/nodes/core/parsers/70-CSV.html | 2 +- .../@node-red/nodes/core/parsers/70-HTML.html | 2 +- .../@node-red/nodes/core/parsers/70-JSON.html | 2 +- .../@node-red/nodes/core/parsers/70-XML.html | 2 +- .../@node-red/nodes/core/parsers/70-YAML.html | 2 +- .../@node-red/nodes/core/sequence/17-split.html | 4 ++-- .../@node-red/nodes/core/sequence/18-sort.html | 2 +- .../@node-red/nodes/core/sequence/19-batch.html | 2 +- .../@node-red/nodes/core/storage/10-file.html | 4 ++-- .../@node-red/nodes/core/storage/23-watch.html | 2 +- .../@node-red/nodes/locales/de/common/20-inject.html | 2 +- .../@node-red/nodes/locales/de/common/21-debug.html | 2 +- .../@node-red/nodes/locales/de/common/25-catch.html | 2 +- .../@node-red/nodes/locales/de/common/25-status.html | 2 +- .../@node-red/nodes/locales/de/common/60-link.html | 4 ++-- .../@node-red/nodes/locales/de/common/90-comment.html | 2 +- .../@node-red/nodes/locales/de/common/98-unknown.html | 2 +- .../nodes/locales/de/function/10-function.html | 2 +- .../@node-red/nodes/locales/de/function/10-switch.html | 2 +- .../@node-red/nodes/locales/de/function/15-change.html | 2 +- .../@node-red/nodes/locales/de/function/16-range.html | 2 +- .../nodes/locales/de/function/80-template.html | 2 +- .../@node-red/nodes/locales/de/function/89-delay.html | 2 +- .../nodes/locales/de/function/89-trigger.html | 2 +- .../@node-red/nodes/locales/de/function/90-exec.html | 2 +- .../@node-red/nodes/locales/de/network/05-tls.html | 2 +- .../nodes/locales/de/network/06-httpproxy.html | 2 +- .../@node-red/nodes/locales/de/network/10-mqtt.html | 6 +++--- .../@node-red/nodes/locales/de/network/21-httpin.html | 4 ++-- .../nodes/locales/de/network/21-httprequest.html | 2 +- .../nodes/locales/de/network/22-websocket.html | 10 +++++----- .../@node-red/nodes/locales/de/network/31-tcpin.html | 6 +++--- .../@node-red/nodes/locales/de/network/32-udp.html | 4 ++-- .../@node-red/nodes/locales/de/parsers/70-CSV.html | 2 +- .../@node-red/nodes/locales/de/parsers/70-HTML.html | 2 +- .../@node-red/nodes/locales/de/parsers/70-JSON.html | 2 +- .../@node-red/nodes/locales/de/parsers/70-XML.html | 2 +- .../@node-red/nodes/locales/de/parsers/70-YAML.html | 2 +- .../@node-red/nodes/locales/de/sequence/17-split.html | 4 ++-- .../@node-red/nodes/locales/de/sequence/18-sort.html | 2 +- .../@node-red/nodes/locales/de/sequence/19-batch.html | 2 +- .../@node-red/nodes/locales/de/storage/10-file.html | 4 ++-- .../@node-red/nodes/locales/de/storage/23-watch.html | 2 +- .../nodes/locales/en-US/common/20-inject.html | 2 +- .../@node-red/nodes/locales/en-US/common/21-debug.html | 2 +- .../nodes/locales/en-US/common/24-complete.html | 2 +- .../@node-red/nodes/locales/en-US/common/25-catch.html | 2 +- .../nodes/locales/en-US/common/25-status.html | 2 +- .../@node-red/nodes/locales/en-US/common/60-link.html | 4 ++-- .../nodes/locales/en-US/common/90-comment.html | 2 +- .../nodes/locales/en-US/common/98-unknown.html | 2 +- .../nodes/locales/en-US/function/10-function.html | 2 +- .../nodes/locales/en-US/function/10-switch.html | 2 +- .../nodes/locales/en-US/function/15-change.html | 2 +- .../nodes/locales/en-US/function/16-range.html | 2 +- .../nodes/locales/en-US/function/80-template.html | 2 +- .../nodes/locales/en-US/function/89-delay.html | 2 +- .../nodes/locales/en-US/function/89-trigger.html | 2 +- .../nodes/locales/en-US/function/90-exec.html | 2 +- .../@node-red/nodes/locales/en-US/network/05-tls.html | 2 +- .../nodes/locales/en-US/network/06-httpproxy.html | 2 +- .../@node-red/nodes/locales/en-US/network/10-mqtt.html | 6 +++--- .../nodes/locales/en-US/network/21-httpin.html | 4 ++-- .../nodes/locales/en-US/network/21-httprequest.html | 2 +- .../nodes/locales/en-US/network/22-websocket.html | 8 ++++---- .../nodes/locales/en-US/network/31-tcpin.html | 6 +++--- .../@node-red/nodes/locales/en-US/network/32-udp.html | 4 ++-- .../@node-red/nodes/locales/en-US/parsers/70-CSV.html | 2 +- .../@node-red/nodes/locales/en-US/parsers/70-HTML.html | 2 +- .../@node-red/nodes/locales/en-US/parsers/70-JSON.html | 2 +- .../@node-red/nodes/locales/en-US/parsers/70-XML.html | 2 +- .../@node-red/nodes/locales/en-US/parsers/70-YAML.html | 2 +- .../nodes/locales/en-US/sequence/17-split.html | 4 ++-- .../nodes/locales/en-US/sequence/18-sort.html | 2 +- .../nodes/locales/en-US/sequence/19-batch.html | 2 +- .../@node-red/nodes/locales/en-US/storage/10-file.html | 4 ++-- .../nodes/locales/en-US/storage/23-watch.html | 2 +- .../@node-red/nodes/locales/ja/common/20-inject.html | 2 +- .../@node-red/nodes/locales/ja/common/21-debug.html | 2 +- .../@node-red/nodes/locales/ja/common/24-complete.html | 2 +- .../@node-red/nodes/locales/ja/common/25-catch.html | 2 +- .../@node-red/nodes/locales/ja/common/25-status.html | 2 +- .../@node-red/nodes/locales/ja/common/60-link.html | 4 ++-- .../@node-red/nodes/locales/ja/common/90-comment.html | 2 +- .../@node-red/nodes/locales/ja/common/98-unknown.html | 2 +- .../nodes/locales/ja/function/10-function.html | 2 +- .../@node-red/nodes/locales/ja/function/10-switch.html | 2 +- .../@node-red/nodes/locales/ja/function/15-change.html | 2 +- .../@node-red/nodes/locales/ja/function/16-range.html | 2 +- .../nodes/locales/ja/function/80-template.html | 2 +- .../@node-red/nodes/locales/ja/function/89-delay.html | 2 +- .../nodes/locales/ja/function/89-trigger.html | 2 +- .../@node-red/nodes/locales/ja/function/90-exec.html | 2 +- .../@node-red/nodes/locales/ja/network/05-tls.html | 2 +- .../nodes/locales/ja/network/06-httpproxy.html | 2 +- .../@node-red/nodes/locales/ja/network/10-mqtt.html | 6 +++--- .../@node-red/nodes/locales/ja/network/21-httpin.html | 4 ++-- .../nodes/locales/ja/network/21-httprequest.html | 2 +- .../nodes/locales/ja/network/22-websocket.html | 8 ++++---- .../@node-red/nodes/locales/ja/network/31-tcpin.html | 6 +++--- .../@node-red/nodes/locales/ja/network/32-udp.html | 4 ++-- .../@node-red/nodes/locales/ja/parsers/70-CSV.html | 2 +- .../@node-red/nodes/locales/ja/parsers/70-HTML.html | 2 +- .../@node-red/nodes/locales/ja/parsers/70-JSON.html | 2 +- .../@node-red/nodes/locales/ja/parsers/70-XML.html | 2 +- .../@node-red/nodes/locales/ja/parsers/70-YAML.html | 2 +- .../@node-red/nodes/locales/ja/sequence/17-split.html | 4 ++-- .../@node-red/nodes/locales/ja/sequence/18-sort.html | 2 +- .../@node-red/nodes/locales/ja/sequence/19-batch.html | 2 +- .../@node-red/nodes/locales/ja/storage/10-file.html | 4 ++-- .../@node-red/nodes/locales/ja/storage/23-watch.html | 2 +- .../@node-red/nodes/locales/ko/common/20-inject.html | 2 +- .../@node-red/nodes/locales/ko/common/21-debug.html | 2 +- .../@node-red/nodes/locales/ko/common/25-catch.html | 2 +- .../@node-red/nodes/locales/ko/common/25-status.html | 2 +- .../@node-red/nodes/locales/ko/common/60-link.html | 2 +- .../@node-red/nodes/locales/ko/common/90-comment.html | 2 +- .../@node-red/nodes/locales/ko/common/98-unknown.html | 2 +- .../nodes/locales/ko/function/10-function.html | 2 +- .../@node-red/nodes/locales/ko/function/10-switch.html | 2 +- .../@node-red/nodes/locales/ko/function/15-change.html | 2 +- .../@node-red/nodes/locales/ko/function/16-range.html | 2 +- .../nodes/locales/ko/function/80-template.html | 2 +- .../@node-red/nodes/locales/ko/function/89-delay.html | 2 +- .../nodes/locales/ko/function/89-trigger.html | 2 +- .../@node-red/nodes/locales/ko/function/90-exec.html | 2 +- .../@node-red/nodes/locales/ko/network/05-tls.html | 2 +- .../nodes/locales/ko/network/06-httpproxy.html | 2 +- .../@node-red/nodes/locales/ko/network/10-mqtt.html | 6 +++--- .../@node-red/nodes/locales/ko/network/21-httpin.html | 4 ++-- .../nodes/locales/ko/network/21-httprequest.html | 2 +- .../nodes/locales/ko/network/22-websocket.html | 8 ++++---- .../@node-red/nodes/locales/ko/network/31-tcpin.html | 6 +++--- .../@node-red/nodes/locales/ko/network/32-udp.html | 4 ++-- .../@node-red/nodes/locales/ko/parsers/70-CSV.html | 2 +- .../@node-red/nodes/locales/ko/parsers/70-HTML.html | 2 +- .../@node-red/nodes/locales/ko/parsers/70-JSON.html | 2 +- .../@node-red/nodes/locales/ko/parsers/70-XML.html | 2 +- .../@node-red/nodes/locales/ko/parsers/70-YAML.html | 2 +- .../@node-red/nodes/locales/ko/sequence/17-split.html | 4 ++-- .../@node-red/nodes/locales/ko/sequence/18-sort.html | 2 +- .../@node-red/nodes/locales/ko/sequence/19-batch.html | 2 +- .../@node-red/nodes/locales/ko/storage/10-file.html | 4 ++-- .../@node-red/nodes/locales/ko/storage/23-watch.html | 2 +- 168 files changed, 228 insertions(+), 228 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 87ec3f86b..77ced7d66 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 9202d96ba..c602995e5 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -1,5 +1,5 @@ - - - - - - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html index 228da487b..1ece7efe0 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html index 2b22418dc..51ec15c6f 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html @@ -14,13 +14,13 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html index b17012e72..12bb3684c 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html index e8cb29ffc..31bdc4907 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html @@ -14,14 +14,14 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html index 03beddb8d..24b15aa29 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html index efe82cbb1..1ab60b163 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html @@ -14,12 +14,12 @@ limitations under the License. --> - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html index 4357c7f2b..cdfccd799 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html index 1c665bb8d..53c61e679 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html @@ -14,12 +14,12 @@ limitations under the License. --> - - - - - - diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 5dfe45fee..d0a350e6a 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -25,6 +25,7 @@ module.exports = function(RED) { this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; this.second = n.second || false; + this.property = n.property || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -112,7 +113,7 @@ module.exports = function(RED) { }); var processMessage = function(msg) { - var topic = msg.topic || "_none"; + var topic = RED.util.getMessageProperty(msg,node.property) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html index 836cabc6f..3caa0ab0a 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 5eb83f9d1..5774d5dac 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -302,7 +302,7 @@ "wait-for": "wait for", "wait-loop": "resend it every", "for": "Handling", - "bytopics": "each msg.topic independently", + "bytopics": "each", "alltopics": "all messages", "duration": { "ms": "Milliseconds", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index adc033390..be98d50e3 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -302,7 +302,7 @@ "wait-for": "指定した時間待機", "wait-loop": "指定した時間間隔毎に送信を繰り返す", "for": "処理対象", - "bytopics": "msg.topic毎", + "bytopics": "毎", "alltopics": "全メッセージ", "duration": { "ms": "ミリ秒", diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index ebf1c8db9..063fec47c 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -378,6 +378,51 @@ describe('trigger node', function() { }); }); + it('should handle multiple other properties individually if asked to do so', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", property:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "B"); + } + else if (c === 3) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "C"); + } + else if (c === 4) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "A"); + } + else if (c === 5) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "B"); + } + else if (c === 6) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "C"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,foo:"A"}); + n1.emit("input", {payload:2,foo:"B"}); + n1.emit("input", {payload:3,foo:"C"}); + }); + }); + it('should be able to return things from flow and global context variables', function(done) { var spy = sinon.stub(RED.util, 'evaluateNodeProperty', function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } } From 87aacb4270f1db52952e6257e115cc359f8350f9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 30 Jan 2020 22:20:55 +0000 Subject: [PATCH 017/366] change property name to leave space if we want to also do main payload property --- .../@node-red/nodes/core/function/89-trigger.html | 10 +++++----- .../@node-red/nodes/core/function/89-trigger.js | 4 ++-- test/nodes/core/function/89-trigger_spec.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 5e250be28..187a87190 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -66,8 +66,8 @@ - - + +
@@ -93,7 +93,7 @@ reset: {value:""}, bytopic: {value:"all"}, outputs: {value:1}, - property: {value:"topic",required:true} + topic: {value:"topic",required:true} }, inputs:1, outputs:1, @@ -121,9 +121,9 @@ $("#node-input-bytopic").on("change", function() { console.log("BYT",$("#node-input-bytopic").val()); if ($("#node-input-bytopic").val() === "all") { - $("#node-trigger-property").hide(); + $("#node-stream-topic").hide(); } else { - $("#node-trigger-property").show(); + $("#node-stream-topic").show(); } }); diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index d0a350e6a..dab7a83ab 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -25,7 +25,7 @@ module.exports = function(RED) { this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; this.second = n.second || false; - this.property = n.property || "topic"; + this.topic = n.topic || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -113,7 +113,7 @@ module.exports = function(RED) { }); var processMessage = function(msg) { - var topic = RED.util.getMessageProperty(msg,node.property) || "_none"; + var topic = RED.util.getMessageProperty(msg,node.topic) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 063fec47c..582b47904 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -379,7 +379,7 @@ describe('trigger node', function() { }); it('should handle multiple other properties individually if asked to do so', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", property:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); From 88e729664afa71b275c0f1ce38ede42adc81ef2b Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 31 Jan 2020 17:56:06 +0000 Subject: [PATCH 018/366] complete tidy up of trigger node remove unnecessary console.log --- .../@node-red/nodes/core/function/89-trigger.html | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 187a87190..79b022519 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -92,8 +92,8 @@ units: {value:"ms"}, reset: {value:""}, bytopic: {value:"all"}, - outputs: {value:1}, - topic: {value:"topic",required:true} + topic: {value:"topic",required:true}, + outputs: {value:1} }, inputs:1, outputs:1, @@ -114,12 +114,9 @@ }, oneditprepare: function() { var that = this; - if (this.property === undefined) { - $("#node-input-property").val("topic"); - } - $("#node-input-property").typedInput({default:'msg',types:['msg']}); + if (this.topic === undefined) { $("#node-input-topic").val("topic"); } + $("#node-input-topic").typedInput({default:'msg',types:['msg']}); $("#node-input-bytopic").on("change", function() { - console.log("BYT",$("#node-input-bytopic").val()); if ($("#node-input-bytopic").val() === "all") { $("#node-stream-topic").hide(); } else { From fcf757f7155b0a39285e79b882bdf29010f5168c Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 31 Jan 2020 18:11:58 +0000 Subject: [PATCH 019/366] catch mode signals to allow clean context flush on shutdown (yes the name is intentionally ironic) Code pattern copied from https://nodejs.org/api/process.html#process_signal_events --- packages/node_modules/node-red/red.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 26b6d1fc8..e758a00f3 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -342,8 +342,14 @@ process.on('uncaughtException',function(err) { process.exit(1); }); -process.on('SIGINT', function () { +function brexit() { RED.stop().then(function() { process.exit(); }); -}); +} + +process.on('SIGINT', brexit); +process.on('SIGTERM', brexit); +process.on('SIGHUP', brexit); +process.on('SIGUSR2', brexit); // for nodemon restart +process.on('SIGBREAK', brexit); // for windows ctrl-break From 36bf2a3c3897430021a9b134d1dddf5c6fbced27 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 3 Feb 2020 12:59:12 +0900 Subject: [PATCH 020/366] add support for examples of core nodes --- .../node_modules/@node-red/registry/lib/localfilesystem.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index 947972031..c66d7d080 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -286,6 +286,10 @@ function getNodeFiles(disableNodePathScan) { nodeFiles.forEach(function(node) { nodeList["node-red"].nodes[node.name] = node; }); + if (settings.coreNodesDir) { + var examplesDir = path.join(settings.coreNodesDir,"examples"); + nodeList["node-red"].examples = {path: examplesDir}; + } if (!disableNodePathScan) { var moduleFiles = scanTreeForNodesModules(); From 272fbc0cb0f3646f0294ad24bc549a32976f501f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 4 Feb 2020 09:45:23 +0900 Subject: [PATCH 021/366] add examples of batch node --- .../@node-red/nodes/examples/batch/1_number-mode.json | 1 + .../node_modules/@node-red/nodes/examples/batch/2_time-mode.json | 1 + .../@node-red/nodes/examples/batch/3_concatenate-mode.json | 1 + 3 files changed, 3 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json diff --git a/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json new file mode 100644 index 000000000..b7b919c9e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json @@ -0,0 +1 @@ +[{"id":"bf16276d.2f1758","type":"tab","label":"Example: Number-based Group Mode","disabled":false,"info":"*Number-based Group mode* of batch node can be used to create new message sequences from incoming messages. Recently received *N*-messages are grouped to a sequence. Creating message sequences that has overwrap with adjacent message group is possible.\n"},{"id":"f5a82278.78d6c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":232,"wires":[["b1e514ed.44f328"]]},{"id":"43720065.2891d","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages","info":"","x":170,"y":60,"wires":[]},{"id":"b1e514ed.44f328","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":232,"wires":[["457e5970.8ceaa8"]]},{"id":"457e5970.8ceaa8","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":232,"wires":[]},{"id":"8e3d3ceb.bd0fe","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages","info":"","x":490,"y":272,"wires":[]},{"id":"fbe20ae3.cbb6f8","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":192,"wires":[]},{"id":"7ebafe58.2a112","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":152,"wires":[["589603c4.1cb5fc"]]},{"id":"589603c4.1cb5fc","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":152,"wires":[["f5a82278.78d6c"]]},{"id":"9b59b72c.fcd8f8","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":112,"wires":[]},{"id":"6421756f.6abd5c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":"1","interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":500,"wires":[["199cf232.743e7e"]]},{"id":"657b6a53.2a1fc4","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages with overlap of 1 msg","info":"","x":240,"y":328,"wires":[]},{"id":"199cf232.743e7e","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":500,"wires":[["91d29dda.d8a4f"]]},{"id":"91d29dda.d8a4f","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":500,"wires":[]},{"id":"8bd4d407.aa2af8","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages with overlap of 1 msg","info":"","x":560,"y":540,"wires":[]},{"id":"a49ea57d.8d2458","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":460,"wires":[]},{"id":"f689d1b3.90e4b","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":420,"wires":[["c021cf24.ad03e"]]},{"id":"c021cf24.ad03e","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":420,"wires":[["6421756f.6abd5c"]]},{"id":"8da6e576.901a18","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":380,"wires":[]}] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json new file mode 100644 index 000000000..87fe70f0a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json @@ -0,0 +1 @@ +[{"id":"82a01f29.86de","type":"tab","label":"Example: Time-based Group Mode","disabled":false,"info":"*Time-based Group mode* of batch node can be used to create new message sequences from incoming messages received within specified time range. \n"},{"id":"9a7f6539.6e36d8","type":"batch","z":"82a01f29.86de","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":350,"y":260,"wires":[["e54a3b57.3677f8"]]},{"id":"71dc607e.98aab","type":"comment","z":"82a01f29.86de","name":"Group messages received within 5s","info":"","x":180,"y":80,"wires":[]},{"id":"e54a3b57.3677f8","type":"join","z":"82a01f29.86de","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":490,"y":260,"wires":[["1e263cac.6641f3"]]},{"id":"1e263cac.6641f3","type":"debug","z":"82a01f29.86de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650,"y":260,"wires":[]},{"id":"324c7b93.0db734","type":"comment","z":"82a01f29.86de","name":"↑ create message sequence received within 5s","info":"","x":480,"y":300,"wires":[]},{"id":"fb112bae.fbe428","type":"comment","z":"82a01f29.86de","name":"↓ join sequence to array","info":"","x":540,"y":220,"wires":[]},{"id":"34f8dda5.2864c2","type":"inject","z":"82a01f29.86de","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":160,"wires":[["c9e23ee4.ce535"]]},{"id":"c9e23ee4.ce535","type":"function","z":"82a01f29.86de","name":"send: 0-49","func":"for(var x = 0; x < 100; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":160,"wires":[["7026e0cc.4e3c3"]]},{"id":"7026e0cc.4e3c3","type":"delay","z":"82a01f29.86de","name":"","pauseType":"rate","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":490,"y":160,"wires":[["9a7f6539.6e36d8"]]},{"id":"40f8c766.6ed198","type":"comment","z":"82a01f29.86de","name":"↓ send sequence: 0-49","info":"","x":340,"y":120,"wires":[]}] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json new file mode 100644 index 000000000..819ae7966 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json @@ -0,0 +1 @@ +[{"id":"845b226d.a4b18","type":"tab","label":"Example: Concatenate Mode","disabled":false,"info":"*Concatenate mode* of batch node can be used to combine input message sequences to create a new message sequence. Order of the sequences can be specified using message topic assigned to each message in a sequence. Message sequence can be specified multiple times.\n"},{"id":"72afe7b0.38b9d8","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,2,3,4,5]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":100,"wires":[["6dea90dd.c442c"]]},{"id":"6dea90dd.c442c","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":100,"wires":[["3ac93a4b.ddbbc6"]]},{"id":"3ac93a4b.ddbbc6","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"SEQ"},{"topic":"SEQ"}],"x":470,"y":100,"wires":[["48ec7040.56f5f"]]},{"id":"48ec7040.56f5f","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":610,"y":100,"wires":[["902613c4.769b5"]]},{"id":"902613c4.769b5","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":100,"wires":[]},{"id":"a84cf2e1.65adc","type":"comment","z":"845b226d.a4b18","name":"Duplicate","info":"","x":100,"y":60,"wires":[]},{"id":"3256f015.45c36","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,-6,-8,7,2,-3]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":220,"wires":[["c308dcb2.621da"]]},{"id":"c308dcb2.621da","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":220,"wires":[["2222098b.7fd036"]]},{"id":"247a5fab.239cc","type":"comment","z":"845b226d.a4b18","name":"Filter & Concat","info":"","x":120,"y":180,"wires":[]},{"id":"2222098b.7fd036","type":"switch","z":"845b226d.a4b18","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":true,"outputs":2,"x":390,"y":280,"wires":[["56e3a974.2bfde8"],["86a1b43a.ff4cb8"]]},{"id":"cd00a796.e4e478","type":"comment","z":"845b226d.a4b18","name":"↑ Duplicate SEQ","info":"","x":500,"y":140,"wires":[]},{"id":"56e3a974.2bfde8","type":"change","z":"845b226d.a4b18","name":"Topic←POS","rules":[{"t":"set","p":"topic","pt":"msg","to":"POS","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":240,"wires":[["fe90d65d.a6b548"]]},{"id":"86a1b43a.ff4cb8","type":"change","z":"845b226d.a4b18","name":"Topic←NEG","rules":[{"t":"set","p":"topic","pt":"msg","to":"NEG","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":300,"wires":[["fe90d65d.a6b548"]]},{"id":"fe90d65d.a6b548","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"NEG"},{"topic":"POS"}],"x":710,"y":280,"wires":[["5b089f16.62b96"]]},{"id":"2f46c0af.bff71","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":930,"y":220,"wires":[]},{"id":"5b089f16.62b96","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":770,"y":220,"wires":[["2f46c0af.bff71"]]},{"id":"e069eb28.6eb358","type":"comment","z":"845b226d.a4b18","name":"↑ Order sequence: negative→positive","info":"","x":810,"y":320,"wires":[]},{"id":"aeae162b.efd118","type":"comment","z":"845b226d.a4b18","name":"Filter pos/neg and make separate sequence↑ (but not a simple sort) ","info":"","x":320,"y":340,"wires":[]}] \ No newline at end of file From 0622be843bd6aaabe2b927abba05a7f7272b7b6d Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 4 Feb 2020 13:42:34 +0000 Subject: [PATCH 022/366] Add catcher for PM2 graceful shutdown --- packages/node_modules/node-red/red.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index e758a00f3..aa1f9f726 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -353,3 +353,6 @@ process.on('SIGTERM', brexit); process.on('SIGHUP', brexit); process.on('SIGUSR2', brexit); // for nodemon restart process.on('SIGBREAK', brexit); // for windows ctrl-break +process.on('message', function(m) { // for PM2 under window with --shutdown-with-message + if (m === 'shutdown') { brexit } +}); From b2f53a183e0ce5dfab81942d018c06132b84c3bf Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 5 Feb 2020 13:58:45 +0000 Subject: [PATCH 023/366] rename BreakingExit call (undo Brexit :-) --- packages/node_modules/node-red/red.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index aa1f9f726..95a42d9fd 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -342,17 +342,17 @@ process.on('uncaughtException',function(err) { process.exit(1); }); -function brexit() { +function exitWhenStopped() { RED.stop().then(function() { process.exit(); }); } -process.on('SIGINT', brexit); -process.on('SIGTERM', brexit); -process.on('SIGHUP', brexit); -process.on('SIGUSR2', brexit); // for nodemon restart -process.on('SIGBREAK', brexit); // for windows ctrl-break +process.on('SIGINT', exitWhenStopped); +process.on('SIGTERM', exitWhenStopped); +process.on('SIGHUP', exitWhenStopped); +process.on('SIGUSR2', exitWhenStopped); // for nodemon restart +process.on('SIGBREAK', exitWhenStopped); // for windows ctrl-break process.on('message', function(m) { // for PM2 under window with --shutdown-with-message - if (m === 'shutdown') { brexit } + if (m === 'shutdown') { exitWhenStopped } }); From d08e77cf36361f4c3747dd2ffc79bebb5a6a69da Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 14:13:36 +0000 Subject: [PATCH 024/366] Add credential type to TypedInput --- .../editor-client/locales/en-US/editor.json | 3 +- .../src/js/ui/common/typedInput.js | 82 ++++++++++++++++++- .../src/sass/ui/common/typedInput.scss | 7 +- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index a5f232aa6..7977a5752 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -757,7 +757,8 @@ "bin": "buffer", "date": "timestamp", "jsonata": "expression", - "env": "env variable" + "env": "env variable", + "cred": "credential" } }, "editableList": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index cb4de12d2..8ca601ec3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -164,6 +164,77 @@ } }) } + }, + cred:{ + value:"cred", + label:"credential", + icon:"fa fa-lock", + inputType: "password", + valueLabel: function(container,value) { + var that = this; + container.css("pointer-events","none"); + var buttons = $('
').css({ + position: "absolute", + right:"6px", + top: "6px", + "pointer-events":"all" + }).appendTo(container); + var eyeButton = $('').css({ + width:"20px" + }).appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + var currentType = that.input.attr("type"); + if (currentType === "text") { + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + } else { + that.input.attr("type","text"); + eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); + } + }).hide(); + var eyeCon = $('').css("margin-left","-1px").appendTo(eyeButton); + + if (value === "__PWRD__") { + var innerContainer = $('
').css({ + padding:"6px 6px", + borderRadius:"4px" + }).addClass("red-ui-typedInput-value-label-inactive").appendTo(container); + var editButton = $('').appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.hide(); + container.css("background","none"); + container.css("pointer-events","none"); + that.input.val(""); + that.element.val(""); + that.elementDiv.show(); + editButton.hide(); + cancelButton.show(); + eyeButton.show(); + setTimeout(function() { + that.input.focus(); + },50); + }); + var cancelButton = $('').css("margin-left","3px").appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.show(); + container.css("background",""); + that.input.val("__PWRD__"); + that.element.val("__PWRD__"); + that.elementDiv.hide(); + editButton.show(); + cancelButton.hide(); + eyeButton.hide(); + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + + }).hide(); + } else { + container.css("background","none"); + container.css("pointer-events","none"); + this.elementDiv.show(); + eyeButton.show(); + } + } } }; var nlsd = false; @@ -220,6 +291,8 @@ that.input.attr(d,m); }); + this.defaultInputType = this.input.attr('type'); + this.uiSelect.addClass("red-ui-typedInput-container"); this.element.attr('type','hidden'); @@ -687,7 +760,7 @@ $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); } else { - $('',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); + $('',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel); } } if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { @@ -822,6 +895,11 @@ if (this.optionSelectTrigger) { this.optionSelectTrigger.hide(); } + if (opt.inputType) { + this.input.attr('type',opt.inputType) + } else { + this.input.attr('type',this.defaultInputType) + } if (opt.hasValue === false) { this.oldValue = this.input.val(); this.input.val(""); @@ -830,8 +908,8 @@ } else if (opt.valueLabel) { this.valueLabelContainer.show(); this.valueLabelContainer.empty(); - opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); this.elementDiv.hide(); + opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); } else { if (this.oldValue !== undefined) { this.input.val(this.oldValue); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index be2d50674..f6c2c1dd7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -56,7 +56,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - + .red-ui-typedInput-value-label-inactive { + backgroundColor: $secondary-background-disabled; + color: $secondary-text-color-disabled; + } } } .red-ui-typedInput-options { @@ -123,7 +126,7 @@ button.red-ui-typedInput-option-trigger } &.disabled { cursor: default; - i.red-ui-typedInput-icon { + > i.red-ui-typedInput-icon { color: $secondary-text-color-disabled; } } From 33cbb2ada81f56259533f7da0938e7a6e71248b5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 15:06:27 +0000 Subject: [PATCH 025/366] Fixup typedInput cred css --- .../@node-red/editor-client/src/js/ui/common/typedInput.js | 1 + .../@node-red/editor-client/src/sass/ui/common/typedInput.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 8ca601ec3..dc8d8e387 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -173,6 +173,7 @@ valueLabel: function(container,value) { var that = this; container.css("pointer-events","none"); + this.elementDiv.hide(); var buttons = $('
').css({ position: "absolute", right:"6px", diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index f6c2c1dd7..598d4e7f9 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -57,7 +57,7 @@ overflow: hidden; text-overflow: ellipsis; .red-ui-typedInput-value-label-inactive { - backgroundColor: $secondary-background-disabled; + background: $secondary-background-disabled; color: $secondary-text-color-disabled; } } From bffcaa1c17ab92f745b912eb9cc805123a8a74a1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2020 11:16:19 +0000 Subject: [PATCH 026/366] Refocus credential typedInput when hide/show button clicked --- .../@node-red/editor-client/src/js/ui/common/typedInput.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index dc8d8e387..a3f066223 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -188,9 +188,15 @@ if (currentType === "text") { that.input.attr("type","password"); eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + setTimeout(function() { + that.input.focus(); + },50); } else { that.input.attr("type","text"); eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); + setTimeout(function() { + that.input.focus(); + },50); } }).hide(); var eyeCon = $('').css("margin-left","-1px").appendTo(eyeButton); From cd210d9fbf1aeefc5544ea61902f5c71d759cb5e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 14:14:48 +0000 Subject: [PATCH 027/366] Add support for credential-stored env var in subflow --- .../@node-red/editor-client/src/js/nodes.js | 59 +- .../editor-client/src/js/ui/editor.js | 67 +- .../editor-client/src/js/ui/subflow.js | 758 ++++++++++-------- .../@node-red/runtime/lib/api/flows.js | 26 +- .../runtime/lib/nodes/credentials.js | 69 +- .../runtime/lib/nodes/flows/Subflow.js | 23 +- 6 files changed, 606 insertions(+), 396 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 5c0778fba..76dc4d194 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 @@ -398,6 +398,10 @@ RED.nodes = (function() { paletteLabel: function() { return RED.nodes.subflow(sf.id).name }, 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); + }, oneditresize: function(size) { // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); var height = size.height; @@ -505,19 +509,33 @@ RED.nodes = (function() { node[d] = n[d]; } } - if(exportCreds && n.credentials) { + if (exportCreds) { var credentialSet = {}; - node.credentials = {}; - for (var cred in n._def.credentials) { - if (n._def.credentials.hasOwnProperty(cred)) { - if (n._def.credentials[cred].type == 'password') { + if (/^subflow:/.test(node.type) && n.credentials) { + // A subflow instance node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { if (!n.credentials._ || - n.credentials["has_"+cred] != n.credentials._["has_"+cred] || - (n.credentials["has_"+cred] && n.credentials[cred])) { + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + } else if (n.credentials) { + node.credentials = {}; + // All other nodes have a well-defined list of possible credentials + for (var cred in n._def.credentials) { + if (n._def.credentials.hasOwnProperty(cred)) { + if (n._def.credentials[cred].type == 'password') { + if (!n.credentials._ || + n.credentials["has_"+cred] != n.credentials._["has_"+cred] || + (n.credentials["has_"+cred] && n.credentials[cred])) { + credentialSet[cred] = n.credentials[cred]; + } + } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { credentialSet[cred] = n.credentials[cred]; } - } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { - credentialSet[cred] = n.credentials[cred]; } } } @@ -568,7 +586,8 @@ RED.nodes = (function() { return node; } - function convertSubflow(n) { + function convertSubflow(n, exportCreds) { + exportCreds = true; var node = {}; node.id = n.id; node.type = n.type; @@ -578,6 +597,24 @@ RED.nodes = (function() { node.in = []; node.out = []; node.env = n.env; + + if (exportCreds) { + var credentialSet = {}; + // A subflow node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { + if (!n.credentials._ || + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + if (Object.keys(credentialSet).length > 0) { + node.credentials = credentialSet; + } + } + node.color = n.color; n.in.forEach(function(p) { @@ -693,7 +730,7 @@ RED.nodes = (function() { } for (i in subflows) { if (subflows.hasOwnProperty(i)) { - nns.push(convertSubflow(subflows[i])); + nns.push(convertSubflow(subflows[i], exportCredentials)); } } for (i in configNodes) { 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 9fb29833a..2f5c4fe11 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 @@ -490,8 +490,7 @@ RED.editor = (function() { done(); } } - - if (definition.credentials) { + if (definition.credentials || /^subflow:/.test(definition.type)) { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); @@ -499,7 +498,9 @@ RED.editor = (function() { $.getJSON(getCredentialsURL(node.type, node.id), function (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); - populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + if (!/^subflow:/.test(definition.type)) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + } completePrepare(); }); } @@ -576,8 +577,11 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if (type === "subflow-template" || type === "subflow") { - RED.subflow.buildEditForm(dialogForm,type,node); + 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 @@ -1471,6 +1475,19 @@ RED.editor = (function() { if (type === "subflow") { 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; @@ -1599,12 +1616,13 @@ RED.editor = (function() { id: "editor-subflow-envProperties", label: RED._("editor-tab.envProperties"), name: RED._("editor-tab.envProperties"), - content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + content: $('
', {id:"editor-subflow-envProperties-content",class:"red-ui-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-list" }; - - RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node); editorTabs.addTab(subflowPropertiesTab); + // This tab is populated by the oneditprepare function of this + // subflow. That ensures it is done *after* any credentials + // have been loaded for the instance. } if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { @@ -2252,6 +2270,21 @@ RED.editor = (function() { var old_env = editing_node.env; var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); + + 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; @@ -2311,7 +2344,7 @@ RED.editor = (function() { $("#node-input-env-container").editableList('height',height-95); } }, - open: function(tray) { + open: function(tray, done) { var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
", { class: "red-ui-tray-footer-left" @@ -2362,7 +2395,6 @@ RED.editor = (function() { content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-cog" }; - buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); editorTabs.addTab(nodePropertiesTab); var descriptionTab = { @@ -2391,11 +2423,18 @@ RED.editor = (function() { buildAppearanceForm(appearanceTab.content,editing_node); editorTabs.addTab(appearanceTab); - $("#subflow-input-name").val(subflow.name); - RED.text.bidi.prepareInput($("#subflow-input-name")); + $.getJSON(getCredentialsURL("subflow", subflow.id), function (data) { + subflow.credentials = data; + subflow.credentials._ = $.extend(true,{},data); - trayBody.i18n(); - finishedBuilding = true; + buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); + $("#subflow-input-name").val(subflow.name); + RED.text.bidi.prepareInput($("#subflow-input-name")); + + trayBody.i18n(); + finishedBuilding = true; + done(); + }); }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { 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 9f1e7f3e0..01356c3f6 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 @@ -770,7 +770,7 @@ RED.subflow = (function() { /** * Create interface for controlling env var UI definition */ - function buildEnvControl(envList) { + function buildEnvControl(envList,node) { var tabs = RED.tabs.create({ id: "subflow-env-tabs", @@ -779,7 +779,7 @@ RED.subflow = (function() { var inputContainer = $("#subflow-input-ui"); var list = envList.editableList("items"); var exportedEnv = exportEnvList(list, true); - buildEnvUI(inputContainer, exportedEnv); + buildEnvUI(inputContainer, exportedEnv,node); } $("#subflow-env-tabs-content").children().hide(); $("#" + tab.id).show(); @@ -831,6 +831,9 @@ RED.subflow = (function() { }); } + var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; + var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred']; + /** * Create env var edit interface * @param container - container @@ -841,7 +844,7 @@ RED.subflow = (function() { var isTemplateNode = (node.type === "subflow"); if (isTemplateNode) { - buildEnvControl(envContainer); + buildEnvControl(envContainer, node); } envContainer .css({ @@ -851,6 +854,9 @@ RED.subflow = (function() { .editableList({ header: isTemplateNode?$('
'):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") } @@ -859,52 +865,64 @@ RED.subflow = (function() { var nameField = null; var valueField = null; - // if (opt.parent) { - // buildEnvUIRow(envRow,opt,opt.parent.ui||{}) - // } else { - nameField = $('', { - class: "node-input-env-name", - type: "text", - placeholder: RED._("common.label.name") - }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); - valueField = $('',{ - style: "width:100%", - class: "node-input-env-value", - type: "text", - }).attr("autocomplete","disable").appendTo(envRow) - valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); - valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); - valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); - // } + 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; - if (!opt.parent) { - var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); - $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); - var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); - actionButton.on("click", function(evt) { - evt.preventDefault(); - removeTip.close(); - container.parent().addClass("red-ui-editableList-item-deleting") - container.fadeOut(300, function() { - envContainer.editableList('removeItem',opt); - }); + var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); + $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); + var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); + actionButton.on("click", function(evt) { + evt.preventDefault(); + removeTip.close(); + container.parent().addClass("red-ui-editableList-item-deleting") + container.fadeOut(300, function() { + envContainer.editableList('removeItem',opt); }); - } + }); if (isTemplateNode) { // Add the UI customisation row // if `opt.ui` does not exist, then apply defaults. If these // defaults do not change then they will get stripped off // before saving. - opt.ui = opt.ui || { - icon: "", - label: {}, - type: "input", - opts: {types:['str','num','bool','json','bin','env']} + if (opt.type === 'cred') { + opt.ui = opt.ui || { + icon: "", + 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"; @@ -995,11 +1013,11 @@ RED.subflow = (function() { var row = $('
').appendTo(container); $('
').appendTo(row); - var typeOptions = { - 'input': {types:['str','num','bool','json','bin','env']}, + 'input': {types:DEFAULT_ENV_TYPE_LIST}, 'select': {opts:[]}, - 'spinner': {} + 'spinner': {}, + 'cred': {} }; if (ui.opts) { typeOptions[ui.type] = ui.opts; @@ -1054,7 +1072,7 @@ RED.subflow = (function() { } langs.forEach(function(l) { var row = $('
').appendTo(content); - $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); + $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); $('').text(ui.label[l]||"").appendTo(row); }); return content; @@ -1062,314 +1080,341 @@ RED.subflow = (function() { 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; + 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"} - ], - default: ['str','num','bool','json','bin','env'], - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + 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 = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
').appendTo(innerContainer); - $('').appendTo(input); - if (value.length) { - value.forEach(function(v) { - $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); - }) - } else { - $("").css({ - "color":"#aaa", - "padding-left": "4px" - }).text("select types...").appendTo(input); - } - } - }, - { - value:"select", - label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, - valueLabel: function(container,value) { - container.css("padding","0"); + 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 { + $("").css({ + "color":"#aaa", + "padding-left": "4px" + }).text("select types...").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({ + "background":"white", + "height":"100%", + "box-sizing": "border-box", + "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); - // $('
').appendTo(container); - var optList = $('
    ').appendTo(content).editableList({ - header:$("
    "+RED._("editor.select.label")+"
    "+RED._("editor.select.value")+"
    "), - addItem: function(row,index,itemData) { - var labelDiv = $('
    ').appendTo(row); - var label = lookupLabel(itemData.l, "", currentLocale); - itemData.label = $('').val(label).appendTo(labelDiv); - itemData.label.on('keydown', function(evt) { - if (evt.keyCode === 13) { - itemData.input.focus(); - evt.preventDefault(); - } - }); - var labelIcon = $('').appendTo(labelDiv); - RED.popover.tooltip(labelIcon,function() { - return currentLocale; - }) - itemData.input = $('').val(itemData.v).appendTo(row); + 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); + // $('
    ').appendTo(container); + var optList = $('
      ').appendTo(content).editableList({ + header:$("
      "+RED._("editor.select.label")+"
      "+RED._("editor.select.value")+"
      "), + addItem: function(row,index,itemData) { + var labelDiv = $('
      ').appendTo(row); + var label = lookupLabel(itemData.l, "", currentLocale); + itemData.label = $('').val(label).appendTo(labelDiv); + itemData.label.on('keydown', function(evt) { + if (evt.keyCode === 13) { + itemData.input.focus(); + evt.preventDefault(); + } + }); + var labelIcon = $('').appendTo(labelDiv); + RED.popover.tooltip(labelIcon,function() { + return currentLocale; + }) + itemData.input = $('').val(itemData.v).appendTo(row); - // Problem using a TI here: - // - this is in a popout panel - // - clicking the expand button in the TI will close the parent edit tray - // and open the type editor. - // - but it leaves the popout panel over the top. - // - there is no way to get back to the popout panel after closing the type editor - //.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']}); - itemData.input.on('keydown', function(evt) { - if (evt.keyCode === 13) { - // Enter or Tab - var index = optList.editableList('indexOf',itemData); - var length = optList.editableList('length'); - if (index + 1 === length) { - var newItem = {}; - optList.editableList('addItem',newItem); - setTimeout(function() { - if (newItem.label) { - newItem.label.focus(); - } - },100) - } else { - var nextItem = optList.editableList('getItemAt',index+1); - if (nextItem.label) { - nextItem.label.focus() - } - } - evt.preventDefault(); - } - }); - }, - sortable: true, - removable: true, - height: 160 - }) - if (ui.opts.opts.length > 0) { - ui.opts.opts.forEach(function(o) { - optList.editableList('addItem',$.extend(true,{},o)) - }) - } else { - optList.editableList('addItem',{}) - } - return { - onclose: function() { - var items = optList.editableList('items'); - var vals = []; - items.each(function (i,el) { - var data = el.data('data'); - var l = data.label.val().trim(); - var v = data.input.val(); - // var t = data.input.typedInput('type'); - // var v = data.input.typedInput('value'); - if (l.length > 0) { - data.l = data.l || {}; - data.l[currentLocale] = l; - } - data.v = v; + // Problem using a TI here: + // - this is in a popout panel + // - clicking the expand button in the TI will close the parent edit tray + // and open the type editor. + // - but it leaves the popout panel over the top. + // - there is no way to get back to the popout panel after closing the type editor + //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST}); + itemData.input.on('keydown', function(evt) { + if (evt.keyCode === 13) { + // Enter or Tab + var index = optList.editableList('indexOf',itemData); + var length = optList.editableList('length'); + if (index + 1 === length) { + var newItem = {}; + optList.editableList('addItem',newItem); + setTimeout(function() { + if (newItem.label) { + newItem.label.focus(); + } + },100) + } else { + var nextItem = optList.editableList('getItemAt',index+1); + if (nextItem.label) { + nextItem.label.focus() + } + } + evt.preventDefault(); + } + }); + }, + sortable: true, + removable: true, + height: 160 + }) + if (ui.opts.opts.length > 0) { + ui.opts.opts.forEach(function(o) { + optList.editableList('addItem',$.extend(true,{},o)) + }) + } else { + optList.editableList('addItem',{}) + } + return { + onclose: function() { + var items = optList.editableList('items'); + var vals = []; + items.each(function (i,el) { + var data = el.data('data'); + var l = data.label.val().trim(); + var v = data.input.val(); + // var t = data.input.typedInput('type'); + // var v = data.input.typedInput('value'); + if (l.length > 0) { + data.l = data.l || {}; + data.l[currentLocale] = l; + } + data.v = v; - if (l.length > 0 || v.length > 0) { - var val = {l:data.l,v:data.v}; - // if (t !== 'str') { - // val.t = t; - // } - vals.push(val); - } - }); - ui.opts.opts = vals; - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"checkbox", - label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - checkbox = $('').appendTo(container); - checkbox.on('change', function(evt) { - valueField.typedInput('value',$(this).prop('checked')?"true":"false"); - }) - checkbox.prop('checked',valueField.typedInput('value')==="true"); - } - }, - { - value:"spinner", - label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
      ').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + if (l.length > 0 || v.length > 0) { + var val = {l:data.l,v:data.v}; + // if (t !== 'str') { + // val.t = t; + // } + vals.push(val); + } + }); + ui.opts.opts = vals; + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"checkbox", + label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + checkbox = $('').appendTo(container); + checkbox.on('change', function(evt) { + valueField.typedInput('value',$(this).prop('checked')?"true":"false"); + }) + checkbox.prop('checked',valueField.typedInput('value')==="true"); + } + }, + { + value:"spinner", + label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
      ').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
      ').appendTo(innerContainer); - $('').appendTo(input); + var input = $('
      ').appendTo(innerContainer); + $('').appendTo(input); - var min = ui.opts && ui.opts.min; - var max = ui.opts && ui.opts.max; - var label = ""; - if (min !== undefined && max !== undefined) { - label = Math.min(min,max)+" - "+Math.max(min,max); - } else if (min !== undefined) { - label = "> "+min; - } else if (max !== undefined) { - label = "< "+max; - } - $('').css("margin-left","15px").text(label).appendTo(input); - }, - expand: { - icon: "fa-caret-down", - content: function(container) { - var content = $('
      ').appendTo(container); - content.css("padding","8px 5px") - var min = ui.opts.min; - var max = ui.opts.max; - var minInput = $(''); - minInput.val(min); - var maxInput = $(''); - maxInput.val(max); - $('
      ').append(minInput).appendTo(content); - $('
      ').append(maxInput).appendTo(content); - return { - onclose: function() { - var min = minInput.val().trim(); - var max = maxInput.val().trim(); - if (min !== "") { - ui.opts.min = parseInt(min); - } else { - delete ui.opts.min; - } - if (max !== "") { - ui.opts.max = parseInt(max); - } else { - delete ui.opts.max; - } - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"none", - label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false - }, - { - value:"hide", - label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false - } - ], - default: 'none' - }).on("typedinputtypechange", function(evt,type) { - ui.type = $(this).typedInput("type"); - ui.opts = typeOptions[ui.type]; - if (ui.type === 'input') { - // In the case of 'input' type, the typedInput uses the multiple-option - // mode. Its value needs to be set to a comma-separately list of the - // selected options. - inputCellInput.typedInput('value',ui.opts.types.join(",")) - } else { - // No other type cares about `value`, but doing this will - // force a refresh of the label now that `ui.opts` has - // been updated. - inputCellInput.typedInput('value',Date.now()) - } + var min = ui.opts && ui.opts.min; + var max = ui.opts && ui.opts.max; + var label = ""; + if (min !== undefined && max !== undefined) { + label = Math.min(min,max)+" - "+Math.max(min,max); + } else if (min !== undefined) { + label = "> "+min; + } else if (max !== undefined) { + label = "< "+max; + } + $('').css("margin-left","15px").text(label).appendTo(input); + }, + expand: { + icon: "fa-caret-down", + content: function(container) { + var content = $('
      ').appendTo(container); + content.css("padding","8px 5px") + var min = ui.opts.min; + var max = ui.opts.max; + var minInput = $(''); + minInput.val(min); + var maxInput = $(''); + maxInput.val(max); + $('
      ').append(minInput).appendTo(content); + $('
      ').append(maxInput).appendTo(content); + return { + onclose: function() { + var min = minInput.val().trim(); + var max = maxInput.val().trim(); + if (min !== "") { + ui.opts.min = parseInt(min); + } else { + delete ui.opts.min; + } + if (max !== "") { + ui.opts.max = parseInt(max); + } else { + delete ui.opts.max; + } + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"none", + label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false + }, + { + value:"hide", + label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false + } + ], + default: 'none' + }).on("typedinputtypechange", function(evt,type) { + ui.type = $(this).typedInput("type"); + ui.opts = typeOptions[ui.type]; + if (ui.type === 'input') { + // In the case of 'input' type, the typedInput uses the multiple-option + // mode. Its value needs to be set to a comma-separately list of the + // selected options. + inputCellInput.typedInput('value',ui.opts.types.join(",")) + } else { + // No other type cares about `value`, but doing this will + // force a refresh of the label now that `ui.opts` has + // been updated. + inputCellInput.typedInput('value',Date.now()) + } - switch (ui.type) { - case 'input': + switch (ui.type) { + case 'input': valueField.typedInput('types',ui.opts.types); break; case 'select': - valueField.typedInput('types',['str']); - break; - case 'checkbox': + valueField.typedInput('types',['str']); + break; + case 'checkbox': valueField.typedInput('types',['bool']); break; case 'spinner': - valueField.typedInput('types',['num']) + valueField.typedInput('types',['num']); + break; + case 'cred': + valueField.typedInput('types',['cred']); break; default: - valueField.typedInput('types',['str','num','bool','json','bin','env']) - } - if (ui.type === 'checkbox') { - valueField.typedInput('type','bool'); - } else if (ui.type === 'spinner') { - valueField.typedInput('type','num'); - } - if (ui.type !== 'checkbox') { - checkbox = null; - } + valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST) + } + if (ui.type === 'checkbox') { + valueField.typedInput('type','bool'); + } else if (ui.type === 'spinner') { + valueField.typedInput('type','num'); + } + if (ui.type !== 'checkbox') { + checkbox = null; + } - }).on("change", function(evt,type) { - if (ui.type === 'input') { - ui.opts.types = inputCellInput.typedInput('value').split(","); - valueField.typedInput('types',ui.opts.types); - } - }); - valueField.on("change", function(evt) { - if (checkbox) { - checkbox.prop('checked',$(this).typedInput('value')==="true") - } - }) - // Set the input to the right type. This will trigger the 'typedinputtypechange' - // event handler (just above ^^) to update the value if needed - inputCellInput.typedInput('type',ui.type) + }).on("change", function(evt,type) { + if (ui.type === 'input') { + ui.opts.types = inputCellInput.typedInput('value').split(","); + valueField.typedInput('types',ui.opts.types); + } + }); + valueField.on("change", function(evt) { + if (checkbox) { + checkbox.prop('checked',$(this).typedInput('value')==="true") + } + }) + // Set the input to the right type. This will trigger the 'typedinputtypechange' + // event handler (just above ^^) to update the value if needed + inputCellInput.typedInput('type',ui.type) } - function buildEnvUIRow(row, tenv, ui) { + function buildEnvUIRow(row, tenv, ui, node) { ui.label = ui.label||{}; - if (!ui.type) { + if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { + ui.type = "cred"; + ui.opts = {}; + } else if (!ui.type) { ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} } else { if (!ui.opts) { ui.opts = (ui.type === "select") ? {opts:[]} : {}; @@ -1467,6 +1512,24 @@ RED.subflow = (function() { input.spinner(spinnerOpts).parent().width('70%'); input.val(val.value); break; + case "cred": + input = $('').css('width','70%').appendTo(row); + if (node.credentials) { + if (node.credentials[tenv.name]) { + input.val(node.credentials[tenv.name]); + } else if (node.credentials['has_'+tenv.name]) { + input.val("__PWRD__") + } else { + input.val(""); + } + } else { + input.val(""); + } + input.typedInput({ + types: ['cred'], + default: 'cred' + }) + break; } if (input) { input.attr('id',getSubflowEnvPropertyName(tenv.name)) @@ -1478,7 +1541,7 @@ RED.subflow = (function() { * @param uiContainer - container for UI * @param envList - env var definitions of template */ - function buildEnvUI(uiContainer, envList) { + function buildEnvUI(uiContainer, envList,node) { uiContainer.empty(); var elementID = 0; for (var i = 0; i < envList.length; i++) { @@ -1487,7 +1550,7 @@ RED.subflow = (function() { continue; } var row = $("
      ", { class: "form-row" }).appendTo(uiContainer); - buildEnvUIRow(row,tenv, tenv.ui || {}); + buildEnvUIRow(row,tenv, tenv.ui || {}, node); // console.log(ui); } @@ -1525,7 +1588,7 @@ RED.subflow = (function() { // icon: "", // label: {}, // type: "input", - // opts: {types:['str','num','bool','json','bin','env']} + // opts: {types:DEFAULT_ENV_TYPE_LIST} // } if (!ui.icon) { delete ui.icon; @@ -1535,13 +1598,19 @@ RED.subflow = (function() { } switch (ui.type) { case "input": - if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) { + if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) { // This is the default input config. Delete it as it will // be applied automatically delete ui.type; delete ui.opts; } break; + case "cred": + if (envItem.type === "cred") { + delete ui.type; + } + delete ui.opts; + break; case "select": if (ui.opts && $.isEmptyObject(ui.opts.opts)) { // This is the default select config. @@ -1585,7 +1654,7 @@ RED.subflow = (function() { type: env.type, value: env.value }, - ui: env.ui + ui: $.extend(true,{},env.ui) } envList.push(item); parentEnv[env.name] = item; @@ -1621,13 +1690,17 @@ RED.subflow = (function() { var item; var ui = data.ui || {}; if (!ui.type) { - ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + if (data.parent && data.parent.type === "cred") { + ui.type = "cred"; + } else { + ui.type = "input"; + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} + } } else { ui.opts = ui.opts || {}; } var input = $("#"+getSubflowEnvPropertyName(data.name)); - if (input.length) { + if (input.length || ui.type === "cred") { item = { name: data.name }; switch(ui.type) { case "input": @@ -1639,6 +1712,10 @@ RED.subflow = (function() { item.type = 'str'; } break; + case "cred": + item.value = input.val(); + item.type = 'cred'; + break; case "spinner": item.value = input.val(); item.type = 'num'; @@ -1652,7 +1729,7 @@ RED.subflow = (function() { item.value = ""+input.prop("checked"); break; } - if (item.type !== data.parent.type || item.value !== data.parent.value) { + if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { env.push(item); } } @@ -1702,14 +1779,17 @@ RED.subflow = (function() { return defaultLabel; } - function buildEditForm(container,type,node) { + function buildEditForm(type,node) { if (type === "subflow-template") { buildPropertiesList($('#node-input-env-container'), node); } else if (type === "subflow") { - buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node)); + // This gets called by the subflow type `oneditprepare` function + // registered in nodes.js#addSubflow() + buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node); } } - function buildPropertiesForm(container, node) { + function buildPropertiesForm(node) { + var container = $('#editor-subflow-envProperties-content'); var form = $('
      ').appendTo(container); var listContainer = $('
      ').appendTo(form); var list = $('
        ').appendTo(listContainer); 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 1e6e72dc7..76e645b92 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -238,17 +238,25 @@ var api = module.exports = { if (!credentials) { return resolve({}); } - var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; - var sendCredentials = {}; - for (var cred in definition) { - if (definition.hasOwnProperty(cred)) { - if (definition[cred].type == "password") { - var key = 'has_' + cred; - sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; - continue; + var cred; + if (/^subflow(:|$)/.test(opts.type)) { + for (cred in credentials) { + if (credentials.hasOwnProperty(cred)) { + sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== ''; + } + } + } else { + var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; + for (cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (definition[cred].type == "password") { + var key = 'has_' + cred; + sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; + continue; + } + sendCredentials[cred] = credentials[cred] || ''; } - sendCredentials[cred] = credentials[cred] || ''; } } resolve(sendCredentials); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 355bd9bc5..153ef4b06 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -341,33 +341,62 @@ var api = module.exports = { extract: function(node) { var nodeID = node.id; var nodeType = node.type; + var cred; var newCreds = node.credentials; if (newCreds) { delete node.credentials; var savedCredentials = credentialCache[nodeID] || {}; - var dashedType = nodeType.replace(/\s+/g, '-'); - var definition = credentialsDef[dashedType]; - if (!definition) { - log.warn(log._("nodes.credentials.not-registered",{type:nodeType})); - return; - } + if (/^subflow(:|$)/.test(nodeType)) { + for (cred in newCreds) { + if (newCreds.hasOwnProperty(cred)) { + if (newCreds[cred] === "__PWRD__") { + continue; + } + if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { + delete savedCredentials[cred]; + dirty = true; + continue; + } + if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { + savedCredentials[cred] = newCreds[cred]; + dirty = true; + } - for (var cred in definition) { - if (definition.hasOwnProperty(cred)) { - if (newCreds[cred] === undefined) { - continue; } - if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') { - continue; + } + for (cred in savedCredentials) { + if (savedCredentials.hasOwnProperty(cred)) { + if (!newCreds.hasOwnProperty(cred)) { + delete savedCredentials[cred]; + dirty = true; + } } - if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { - delete savedCredentials[cred]; - dirty = true; - continue; - } - if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { - savedCredentials[cred] = newCreds[cred]; - dirty = true; + } + } else { + var dashedType = nodeType.replace(/\s+/g, '-'); + var definition = credentialsDef[dashedType]; + if (!definition) { + log.warn(log._("nodes.credentials.not-registered",{type:nodeType})); + return; + } + + for (cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (newCreds[cred] === undefined) { + continue; + } + if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') { + continue; + } + if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { + delete savedCredentials[cred]; + dirty = true; + continue; + } + if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { + savedCredentials[cred] = newCreds[cred]; + dirty = true; + } } } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index a7bec1234..c0bc63dd2 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -22,6 +22,9 @@ const util = require("util"); const redUtil = require("@node-red/util").util; const flowUtil = require("./util"); + +const credentials = require("../credentials"); + var Log; /** @@ -38,6 +41,9 @@ function evaluateInputValue(value, type, node) { if (type === "bool") { return (value === "true") || (value === true); } + if (type === "cred") { + return value; + } return redUtil.evaluateNodeProperty(value, type, node, null, null); } @@ -142,10 +148,16 @@ class Subflow extends Flow { this.node_map = node_map; this.path = parent.path+"/"+(subflowInstance._alias||subflowInstance.id); + this.templateCredentials = credentials.get(subflowDef.id); + this.instanceCredentials = credentials.get(this.id); + var env = []; if (this.subflowDef.env) { this.subflowDef.env.forEach(e => { env[e.name] = e; + if (e.type === "cred") { + e.value = this.templateCredentials[e.name]; + } }); } if (this.subflowInstance.env) { @@ -154,7 +166,14 @@ class Subflow extends Flow { var ui = old ? old.ui : null; env[e.name] = e; if (ui) { - env[e.name].ui = ui; + env[e.name].ui = ui; + } + if (e.type === "cred") { + if (!old || this.instanceCredentials.hasOwnProperty(e.name) ) { + e.value = this.instanceCredentials[e.name]; + } else if (old) { + e.value = this.templateCredentials[e.name]; + } } }); } @@ -324,7 +343,6 @@ class Subflow extends Flow { * @return {Object} val value of env var */ getSetting(name) { - this.trace("getSetting:"+name); if (!/^\$parent\./.test(name)) { var env = this.env; var is_info = name.endsWith("_info"); @@ -333,7 +351,6 @@ class Subflow extends Flow { if (env && env.hasOwnProperty(ename)) { var val = env[ename]; - if (is_type) { return val ? val.type : undefined; } From cc177533e8dd0d99e991e9b962c4c1f854aa4156 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 15:12:29 +0000 Subject: [PATCH 028/366] Dont export subflow template creds by default --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 - 1 file changed, 1 deletion(-) 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 76dc4d194..cdf2c8796 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 @@ -587,7 +587,6 @@ RED.nodes = (function() { } function convertSubflow(n, exportCreds) { - exportCreds = true; var node = {}; node.id = n.id; node.type = n.type; From 127b3619795970cb52f9378adddba46125e63d71 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 14 Feb 2020 20:13:37 -0500 Subject: [PATCH 029/366] change PR to only use a single property for the 2nd output --- .../@node-red/nodes/core/function/89-trigger.html | 4 +++- .../node_modules/@node-red/nodes/core/function/89-trigger.js | 2 +- test/nodes/core/function/89-trigger_spec.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 79b022519..8917f81d8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -88,7 +88,6 @@ op2type: {value:"val"}, duration: {value:"250",required:true,validate:RED.validators.number()}, extend: {value:"false"}, - second: {value:false}, units: {value:"ms"}, reset: {value:""}, bytopic: {value:"all"}, @@ -123,6 +122,9 @@ $("#node-stream-topic").show(); } }); + + if (this.outputs == 2) { $("#node-input-second").prop('checked', true) } + else { $("#node-input-second").prop('checked', false) } $("#node-input-second").change(function() { if ($("#node-input-second").is(":checked")) { diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index dab7a83ab..e3fca00aa 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -24,7 +24,7 @@ module.exports = function(RED) { this.op2 = n.op2 || "0"; this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; - this.second = n.second || false; + this.second = (n.outputs == 2) ? true : false; this.topic = n.topic || "topic"; if (this.op1type === 'val') { diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 582b47904..f13e23a5e 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -827,7 +827,7 @@ describe('trigger node', function() { }); it('should be able to send 2nd message to a 2nd output', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", second:true, wires:[["n2"],["n3"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] }, {id:"n2", type:"helper"}, {id:"n3", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); From c0d007ffa93800f1318553744f223e053ae13dcb Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 16 Feb 2020 23:07:05 +0900 Subject: [PATCH 030/366] add option support for overwriting settiings.js --- packages/node_modules/node-red/red.js | 56 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 26b6d1fc8..5fb99bffa 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -39,7 +39,8 @@ var knownOpts = { "title": String, "userDir": [path], "verbose": Boolean, - "safe": Boolean + "safe": Boolean, + "define": [String, Array] }; var shortHands = { "?":["--help"], @@ -49,7 +50,8 @@ var shortHands = { // doesn't get treated as --title "t":["--help"], "u":["--userDir"], - "v":["--verbose"] + "v":["--verbose"], + "D":["--define"] }; nopt.invalidHandler = function(k,v,t) { // TODO: console.log(k,v,t); @@ -57,6 +59,28 @@ nopt.invalidHandler = function(k,v,t) { var parsedArgs = nopt(knownOpts,shortHands,process.argv,2) +/** + * Set property of specified object. + * @param {Object} obj - target object + * @param {string} path - "." separated property path + * @param {Object} val - value to be set + */ +function setProperty(obj, path, val) { + var paths = path.split("."); + if (paths.length > 0) { + var o = obj; + for (var i = 0; i < paths.length -1; i++) { + var path = paths[i]; + if (!o.hasOwnProperty(path)) { + o[path] = {}; + } + o = o[path]; + } + var key = paths[paths.length-1]; + o[key] = val; + } +} + if (parsedArgs.help) { console.log("Node-RED v"+RED.version()); console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]"); @@ -69,6 +93,7 @@ if (parsedArgs.help) { console.log(" -u, --userDir DIR use specified user directory"); console.log(" -v, --verbose enable verbose output"); console.log(" --safe enable safe mode"); + console.log(" -D, --define X=Y overwrite value in settings file"); console.log(" -?, --help show this help"); console.log(""); console.log("Documentation can be found at http://nodered.org"); @@ -130,6 +155,33 @@ try { process.exit(); } +if (parsedArgs.define) { + var defs = parsedArgs.define; + defs.forEach(function (def) { + try { + var match = /^(([^=]+)=(.+)|@(.*))$/.exec(def); + if (match) { + if (!match[4]) { + var val = JSON.parse(match[3]); + setProperty(settings, match[2], val); + } + else { + var obj = fs.readJsonSync(match[4]); + Object.entries(obj).forEach(([key, val]) => { + settings[key] = val; + }); + } + } + else { + throw new Error("Unexpected option: "+def); + } + } + catch (e) { + console.log(e); + } + }); +} + if (parsedArgs.verbose) { settings.verbose = true; } From 2da1554caa09c3ba6937ed5078403a59c830d503 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 18 Feb 2020 21:38:32 +0900 Subject: [PATCH 031/366] update message catalogue for subflow UI --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 7977a5752..6c9196577 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -351,7 +351,8 @@ "bool": "bool", "json": "JSON", "bin": "buffer", - "env": "env variable" + "env": "env variable", + "cred": "credential" }, "menu": { "input": "input", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index cf04ced1d..656ec764e 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -351,7 +351,8 @@ "bool": "真偽", "json": "JSON", "bin": "バッファ", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" }, "menu": { "input": "入力", From 8405826fab39cf55596ff064221e0f37170f506e Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 24 Feb 2020 21:17:54 +0000 Subject: [PATCH 032/366] Ensure trigger sends complete 2nd msg if set to send latest msg and add test to close #2474 --- .../@node-red/nodes/core/function/89-trigger.js | 13 ++++++++----- test/nodes/core/function/89-trigger_spec.js | 8 +++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 48a595ccc..d84bf57a8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -106,7 +106,9 @@ module.exports = function(RED) { }); } + var npay; this.on('input', function(msg) { + if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); } processMessageQueue(msg); }); @@ -124,7 +126,7 @@ module.exports = function(RED) { else { if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); - if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2type !== "nul") { promise = new Promise((resolve,reject) => { @@ -188,7 +190,8 @@ module.exports = function(RED) { promise.then(() => { msg2.payload = node.topics[topic].m2; delete node.topics[topic]; - node.send(msg2); + if (node.op2type === "payl") { node.send(npay); } + else { node.send(msg2); } node.status({}); }).catch(err => { node.error(err); @@ -244,9 +247,9 @@ module.exports = function(RED) { }); }, node.duration); } - else { - if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } - } + // else { + // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + // } } return Promise.resolve(); } diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 5f7bfc2d1..be0094ccf 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -736,10 +736,12 @@ describe('trigger node', function() { try { if (c === 0) { msg.should.have.a.property("payload", "Goodbye"); + msg.should.have.a.property("topic", "test2"); c += 1; } else { msg.should.have.a.property("payload", "World"); + msg.should.have.a.property("topic", "test3"); (Date.now() - ss).should.be.greaterThan(70); done(); } @@ -747,12 +749,12 @@ describe('trigger node', function() { catch(err) { done(err); } }); var ss = Date.now(); - n1.emit("input", {payload:"Hello"}); + n1.emit("input", {payload:"Hello", topic:"test1"}); setTimeout( function() { - n1.emit("input", {payload:"Goodbye"}); + n1.emit("input", {payload:"Goodbye", topic:"test2"}); },20); setTimeout( function() { - n1.emit("input", {payload:"World"}); + n1.emit("input", {payload:"World", topic:"test3"}); },80); }); }); From 37bcd5c6032109f73df3427baea8fa82b360b984 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 25 Feb 2020 21:28:15 +0000 Subject: [PATCH 033/366] First pass at adding support for GET requests with a body --- .../nodes/core/network/21-httprequest.html | 28 +++++++++++++++++-- .../nodes/core/network/21-httprequest.js | 9 ++++++ .../nodes/locales/en-US/messages.json | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index aecb8eeb9..d2cc61b56 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -33,8 +33,12 @@
        - - + +
        @@ -106,6 +110,7 @@ method:{value:"GET"}, ret: {value:"txt"}, paytoqs: {value: false}, + paytobody: {value: false}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, tls: {type:"tls-config",required: false}, persist: {value:false}, @@ -168,6 +173,13 @@ $(".node-input-paytoqs-row").hide(); } }); + if (this.paytoqs) { + $("#node-input-paytox").val("query"); + } else if (this.paytobody) { + $("#node-input-paytox").val("body"); + } else { + $("#node-input-paytox").val("ignore"); + } if (this.authType) { $('#node-input-useAuth').prop('checked', true); $("#node-input-authType-select").val(this.authType); @@ -226,6 +238,18 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } + + var payto = $("#node-input-paytox").val(); + if(payto == "query") { + this.paytoqs = true; + this.paytobody = false; + } else if (payto == "body") { + this.paytoqs = false; + this.paytobody = true; + } else { + this.paytoqs = false; + this.paytobody = false; + } } }); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 4c39f9f8c..5d911bffe 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -29,6 +29,7 @@ module.exports = function(RED) { var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; var nodeMethod = n.method || "GET"; var paytoqs = n.paytoqs; + var paytobody = n.paytobody; var nodeHTTPPersistent = n["persist"]; if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); @@ -277,6 +278,14 @@ module.exports = function(RED) { node.error(RED._("httpin.errors.invalid-payload"),msg); nodeDone(); return; + } + } else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) { + if (typeof msg.payload === "object") { + opts.body = JSON.stringify(msg.payload); + } else if (typeof msg.payload == "number") { + opts.body = msg.payload+""; + } else if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) { + opts.body = msg.payload; } } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index e1d7c6368..94d541444 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -398,7 +398,7 @@ "status": "Status code", "headers": "Headers", "other": "other", - "paytoqs" : "Append msg.payload as query string parameters", + "paytoqs" : "Append msg.payload as", "utf8String": "UTF8 string", "binaryBuffer": "binary buffer", "jsonObject": "parsed JSON object", From bba6855872b915eab70ffc3226418bc83b8b5d0d Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Wed, 26 Feb 2020 12:59:40 +0900 Subject: [PATCH 034/366] Add admin api authentication function --- .../@node-red/editor-api/lib/auth/index.js | 3 +- .../editor-api/lib/auth/strategies.js | 24 ++++++++++- .../@node-red/editor-api/lib/auth/users.js | 14 +++++- .../editor-api/lib/auth/strategies_spec.js | 32 ++++++++++++++ .../editor-api/lib/auth/users_spec.js | 43 +++++++++++++++++++ 5 files changed, 112 insertions(+), 4 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 a7ee2e7f3..c3948cd27 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 @@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); +passport.use(strategies.tokensStrategy); var server = oauth2orize.createServer(); @@ -60,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { if (!req.user) { return next(); } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index b17bf1473..9705ddd28 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,9 +123,31 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } +function TokensStrategy() { + passport.Strategy.call(this); + this.name = 'tokens'; +} +util.inherits(TokensStrategy, passport.Strategy); +TokensStrategy.prototype.authenticate = function(req) { + var self = this; + var token = req.headers[Users.tokenHeader()]; + if (token) { + Users.tokens(token).then(function(admin) { + if (admin) { + self.success(admin,{scope:admin.permissions}); + } else { + self.fail(401); + } + }); + } else { + self.fail(401); + } +} + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, - anonymousStrategy: new AnonymousStrategy() + anonymousStrategy: new AnonymousStrategy(), + tokensStrategy: new TokensStrategy() } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index 24a762958..f032332db 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -59,7 +59,9 @@ function getDefaultUser() { var api = { get: get, authenticate: authenticate, - default: getDefaultUser + default: getDefaultUser, + tokens: getDefaultUser, + tokenHeader: "authorization" } function init(config) { @@ -105,6 +107,12 @@ function init(config) { } else { api.default = getDefaultUser; } + if (config.tokens && typeof config.tokens === "function") { + api.tokens = config.tokens; + if (config.tokenHeader && typeof config.tokenHeader === "string") { + api.tokenHeader = config.tokenHeader.toLowerCase(); + } + } } function cleanUser(user) { if (user && user.hasOwnProperty('password')) { @@ -118,5 +126,7 @@ module.exports = { init: init, get: function(username) { return api.get(username).then(cleanUser)}, authenticate: function() { return api.authenticate.apply(null, arguments) }, - default: function() { return api.default(); } + default: function() { return api.default(); }, + tokens: function(token) { return api.tokens(token); }, + tokenHeader: function() { return api.tokenHeader } }; diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index d30f6198a..13573f22a 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -129,6 +129,38 @@ describe("api/auth/strategies", function() { }) }); + describe("Tokens Strategy", function() { + it('Succeeds if tokens user enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + it('Fails if tokens user not enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function() { + return when.resolve(null); + }); + strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; + strategies.tokensStrategy.fail = function(err) { + err.should.equal(401); + strategies.tokensStrategy.fail = strategies.tokensStrategy._fail; + delete strategies.tokensStrategy._fail; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + afterEach(function() { + Users.tokens.restore(); + }) + }); + describe("Bearer Strategy", function() { it('Rejects invalid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 515d23034..228163684 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -227,4 +227,47 @@ describe("api/auth/users", function() { }); }); }); + + describe('Initialised with tokens set as function',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); } + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + (Users.tokenHeader()).should.equal("authorization"); + done(); + }); + }); + }); + + describe('Initialised with tokens set as function and tokenHeader set as token header name',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); }, + tokenHeader: "X-TEST-TOKEN" + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + Users.should.have.property('tokenHeader').which.is.a.Function(); + (Users.tokenHeader()).should.equal("x-test-token"); + done(); + }); + }); + }); + }); From 0ca36a89e30e6200cec1f369dd1124b02a4c6616 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 26 Feb 2020 19:45:01 +0000 Subject: [PATCH 035/366] Updates to match Nick's suggestions --- .../nodes/core/network/21-httprequest.html | 38 ++++++++----------- .../nodes/core/network/21-httprequest.js | 8 +++- .../nodes/locales/en-US/messages.json | 6 ++- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index d2cc61b56..6fea31766 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -33,11 +33,10 @@
        - - + + +
        @@ -110,7 +109,6 @@ method:{value:"GET"}, ret: {value:"txt"}, paytoqs: {value: false}, - paytobody: {value: false}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, tls: {type:"tls-config",required: false}, persist: {value:false}, @@ -173,12 +171,16 @@ $(".node-input-paytoqs-row").hide(); } }); - if (this.paytoqs) { - $("#node-input-paytox").val("query"); - } else if (this.paytobody) { - $("#node-input-paytox").val("body"); + console.log("paytoqs: " + this.paytoqs); + if (this.paytoqs === true || this.paytoqs == "query") { + $("#node-input-paytoqs").val("query"); + console.log("q"); + } else if (this.paytoqs === "body") { + $("#node-input-paytoqs").val("body"); + console.log("b"); } else { - $("#node-input-paytox").val("ignore"); + $("#node-input-paytoqs").val("ignore"); + console.log("i"); } if (this.authType) { $('#node-input-useAuth').prop('checked', true); @@ -238,18 +240,8 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } - - var payto = $("#node-input-paytox").val(); - if(payto == "query") { - this.paytoqs = true; - this.paytobody = false; - } else if (payto == "body") { - this.paytoqs = false; - this.paytobody = true; - } else { - this.paytoqs = false; - this.paytobody = false; - } + console.log("save - paytoqs " + this.paytoqs); + console.log("save - paytoqs " + $("#node-input-paytoqs").val()); } }); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 5d911bffe..fcb6a29df 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -28,8 +28,8 @@ module.exports = function(RED) { var nodeUrl = n.url; var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; var nodeMethod = n.method || "GET"; - var paytoqs = n.paytoqs; - var paytobody = n.paytobody; + var paytoqs = false; + var paytobody = false; var nodeHTTPPersistent = n["persist"]; if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); @@ -39,6 +39,10 @@ module.exports = function(RED) { if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; } else { this.reqTimeout = 120000; } + if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; } + else if (n.paytoqs === "body") { paytobody = true; } + + var prox, noprox; if (process.env.http_proxy) { prox = process.env.http_proxy; } if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 94d541444..f4cde30b4 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -398,7 +398,11 @@ "status": "Status code", "headers": "Headers", "other": "other", - "paytoqs" : "Append msg.payload as", + "paytoqs" : { + "ignore": "Ignore msg.payload", + "query": "Append msg.payload to query-string parameters", + "body": "Send msg.payload as request Body" + }, "utf8String": "UTF8 string", "binaryBuffer": "binary buffer", "jsonObject": "parsed JSON object", From 7723ff461bc9261c6b750788ff931488130ccb09 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 26 Feb 2020 19:46:54 +0000 Subject: [PATCH 036/366] Remove console.logs --- .../@node-red/nodes/core/network/21-httprequest.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index 6fea31766..35fb6ed1c 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -174,13 +174,10 @@ console.log("paytoqs: " + this.paytoqs); if (this.paytoqs === true || this.paytoqs == "query") { $("#node-input-paytoqs").val("query"); - console.log("q"); } else if (this.paytoqs === "body") { $("#node-input-paytoqs").val("body"); - console.log("b"); } else { $("#node-input-paytoqs").val("ignore"); - console.log("i"); } if (this.authType) { $('#node-input-useAuth').prop('checked', true); @@ -240,8 +237,6 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } - console.log("save - paytoqs " + this.paytoqs); - console.log("save - paytoqs " + $("#node-input-paytoqs").val()); } }); From 95982ad46493650a725627142872303cc5e0ea3f Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 16:01:57 +0900 Subject: [PATCH 037/366] Update adminAuth tokensStrategy test spec --- .../editor-api/lib/auth/strategies_spec.js | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index 13573f22a..848aaf99d 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -130,10 +130,13 @@ describe("api/auth/strategies", function() { }); describe("Tokens Strategy", function() { - it('Succeeds if tokens user enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function(token) { + it('Succeeds if tokens user enabled custom header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { return when.resolve("tokens-"+token); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "x-test-token"; + }); strategies.tokensStrategy._success = strategies.tokensStrategy.success; strategies.tokensStrategy.success = function(user) { user.should.equal("tokens-1234"); @@ -141,12 +144,31 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._success; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}}); + }); + it('Succeeds if tokens user enabled default header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); it('Fails if tokens user not enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function() { + var userTokens = sinon.stub(Users,"tokens",function() { return when.resolve(null); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; strategies.tokensStrategy.fail = function(err) { err.should.equal(401); @@ -154,10 +176,11 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._fail; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); afterEach(function() { Users.tokens.restore(); + Users.tokenHeader.restore(); }) }); From 458d794f52492a311316add9486a3986c2b64904 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:41:59 +0900 Subject: [PATCH 038/366] Fix tokensStrategy order --- packages/node_modules/@node-red/editor-api/lib/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c3948cd27..88e924e53 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 @@ -61,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() { if (!req.user) { return next(); } From 83942c255151aee0e92b2045b93a927576483f54 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:55:21 +0900 Subject: [PATCH 039/366] Fix plugin only receives the actual token --- .../@node-red/editor-api/lib/auth/strategies.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index 9705ddd28..87023a487 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -130,7 +130,14 @@ function TokensStrategy() { util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { var self = this; - var token = req.headers[Users.tokenHeader()]; + var token = null; + if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } if (token) { Users.tokens(token).then(function(admin) { if (admin) { From bd4fc2e5cc4337eded1913df390c1caed9721f35 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 29 Feb 2020 09:15:42 -0500 Subject: [PATCH 040/366] Fix workspace CSS properties syntax --- .../@node-red/editor-client/src/sass/workspace.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index f6255eacf..2162cad8a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -112,7 +112,7 @@ position: absolute; bottom: 0; right:0; - zIndex: 101; + z-index: 101; border-left: 1px solid $primary-border-color; border-top: 1px solid $primary-border-color; background: $view-navigator-background; @@ -122,7 +122,7 @@ stroke-dasharray: 5,5; pointer-events: none; stroke: $secondary-border-color; - strokeWidth: 1; + stroke-width: 1; fill: $view-background; } From 8a82552bdcdac22329763cf9a099523a3747b49c Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 29 Feb 2020 15:14:57 -0500 Subject: [PATCH 041/366] Consolidate duplicates --- .../@node-red/editor-client/src/sass/ace.scss | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index fb6eaf8eb..adcaa3458 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -9,19 +9,15 @@ color: transparent !important; } } - - .ace_gutter { + background: $text-editor-gutter-background; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { + background: $text-editor-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - } - - .ace_scroller { - background: $text-editor-background; color: $text-editor-color; } .ace_marker-layer .ace_active-line { @@ -37,9 +33,6 @@ .ace_gutter-active-line { background: $text-editor-gutter-active-line-background; } - .ace_gutter { - background: $text-editor-gutter-background; - } .ace_tooltip { font-family: $primary-font; line-height: 1.4em; From 6675fdf3c2ca8f3f96dd5af38d64655934f3b3c9 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 2 Mar 2020 05:50:32 +0000 Subject: [PATCH 042/366] Saving the node description property to the library --- .../editor-client/src/js/ui/editor.js | 1 + .../editor-client/src/js/ui/library.js | 21 ++++++++++++------- .../nodes/core/function/10-function.html | 13 ++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) 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 2f5c4fe11..2c1cbec85 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 @@ -1638,6 +1638,7 @@ RED.editor = (function() { }; editorTabs.addTab(descriptionTab); nodeInfoEditor = buildDescriptionForm(descriptionTab.content,node); + node.nodeInfoEditor = nodeInfoEditor; } var appearanceTab = { 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 e25223f94..a653c9d7e 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 @@ -64,12 +64,14 @@ RED.library = (function() { var queryArgs = []; var data = {}; - for (var i=0; i Date: Tue, 3 Mar 2020 19:04:32 +0000 Subject: [PATCH 043/366] [groups] add basic group functionality to editor --- Gruntfile.js | 1 + .../editor-client/src/js/keymap.json | 3 +- .../@node-red/editor-client/src/js/nodes.js | 15 + .../@node-red/editor-client/src/js/red.js | 1 + .../editor-client/src/js/ui/editor.js | 248 ++++++- .../editor-client/src/js/ui/state.js | 4 +- .../@node-red/editor-client/src/js/ui/view.js | 626 ++++++++++++++++-- 7 files changed, 857 insertions(+), 41 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index cbf70f4dc..425784341 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -177,6 +177,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/group.js", "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", 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 b7d25e160..c4d666845 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 @@ -61,6 +61,7 @@ "shift-down": "core:step-selection-down", "shift-left": "core:step-selection-left", "ctrl-shift-j": "core:show-previous-tab", - "ctrl-shift-k": "core:show-next-tab" + "ctrl-shift-k": "core:show-next-tab", + "ctrl-shift-g": "core:group-selection" } } 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 cdf2c8796..2bf8d4a21 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 @@ -27,6 +27,9 @@ RED.nodes = (function() { var subflows = {}; var loadedFlowVersion = null; + var groups = {}; + var groupsByZ = {}; + var initialLoad; var dirty = false; @@ -1444,6 +1447,14 @@ RED.nodes = (function() { // var loadedFlowVersion = null; } + function addGroup(group) { + groupsByZ[group.z] = groupsByZ[group.z] || []; + groupsByZ[group.z].push(group); + groups[group.id] = group; + } + + + return { init: function() { RED.events.on("registry:node-type-added",function(type) { @@ -1539,6 +1550,10 @@ RED.nodes = (function() { subflow: getSubflow, subflowContains: subflowContains, + addGroup: addGroup, + group: function(id) { return groups[id] }, + groups: function(z) { return groupsByZ[z] }, + eachNode: function(cb) { for (var n=0;n", { + class: "red-ui-tray-footer-left" + }).appendTo(trayFooter) + var trayBody = tray.find('.red-ui-tray-body'); + trayBody.parent().css('overflow','hidden'); + + 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 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" + }; + buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group); + + editorTabs.addTab(nodePropertiesTab); + + var descriptionTab = { + id: "editor-tab-description", + label: RED._("editor-tab.description"), + name: RED._("editor-tab.description"), + content: $('
          ', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + iconClass: "fa fa-file-text-o", + onchange: function() { + nodeInfoEditor.focus(); + } + }; + editorTabs.addTab(descriptionTab); + nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node); + prepareEditDialog(group,group._def,"node-input", function() { + trayBody.i18n(); + finishedBuilding = true; + done(); + }); + }, + close: function() { + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { + RED.view.state(RED.state.DEFAULT); + } + nodeInfoEditor.destroy(); + nodeInfoEditor = null; + editStack.pop(); + editing_node = null; + }, + show: function() { + } + } + + if (editTrayWidthCache.hasOwnProperty('group')) { + trayOptions.width = editTrayWidthCache['group']; + } + RED.tray.show(trayOptions); + } + function showTypeEditor(type, options) { if (customEditTypes.hasOwnProperty(type)) { if (editStack.length > 0) { @@ -2576,6 +2821,7 @@ RED.editor = (function() { edit: showEditDialog, editConfig: showEditConfigNodeDialog, editSubflow: showEditSubflowDialog, + editGroup: showEditGroupDialog, editJavaScript: function(options) { showTypeEditor("_js",options) }, editExpression: function(options) { showTypeEditor("_expression", options) }, editJSON: function(options) { showTypeEditor("_json", options) }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/state.js b/packages/node_modules/@node-red/editor-client/src/js/ui/state.js index 1631ba87b..a2c9b762d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/state.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/state.js @@ -25,5 +25,7 @@ RED.state = { IMPORT_DRAGGING: 8, QUICK_JOINING: 9, PANNING: 10, - SELECTING_NODE: 11 + SELECTING_NODE: 11, + GROUP_DRAGGING: 12, + GROUP_RESIZE: 13 } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 44f75b481..47e618611 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -21,12 +21,15 @@ * \- .red-ui-workspace-chart-event-layer "eventLayer" * |- .red-ui-workspace-chart-background * |- .red-ui-workspace-chart-grid "gridLayer" + * |- "groupLayer" * |- "linkLayer" * |- "dragGroupLayer" * \- "nodeLayer" */ RED.view = (function() { + var DEBUG_EVENTS = false; + 2 var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, @@ -48,22 +51,29 @@ RED.view = (function() { var activeSpliceLink; var spliceActive = false; var spliceTimer; + var groupHoverTimer; var activeSubflow = null; var activeNodes = []; var activeLinks = []; var activeFlowLinks = []; var activeLinkNodes = {}; + var activeGroup = null; + var activeHoverGroup = null; + var activeGroups = []; + var dirtyGroups = {}; var selected_link = null, mousedown_link = null, mousedown_node = null, + mousedown_group = null, mousedown_port_type = null, mousedown_port_index = 0, mouseup_node = null, mouse_offset = [0,0], mouse_position = null, mouse_mode = 0, + mousedown_group_handle = null; moving_set = [], lasso = null, ghostNode = null, @@ -75,7 +85,8 @@ RED.view = (function() { scroll_position = [], quickAddActive = false, quickAddLink = null, - showAllLinkPorts = -1; + showAllLinkPorts = -1, + groupNodeSelectPrimed = false; var selectNodesOptions; @@ -101,6 +112,7 @@ RED.view = (function() { var linkLayer; var dragGroupLayer; var nodeLayer; + var groupLayer; var drag_lines; function init() { @@ -253,6 +265,7 @@ RED.view = (function() { gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); updateGrid(); + groupLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); @@ -517,6 +530,8 @@ RED.view = (function() { source:{z:activeWorkspace}, target:{z:activeWorkspace} }); + + activeGroups = RED.nodes.groups(activeWorkspace)||[]; } function generateLinkPath(origX,origY, destX, destY, sc) { @@ -671,6 +686,7 @@ RED.view = (function() { } function canvasMouseDown() { +if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } var point; if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); @@ -684,7 +700,7 @@ RED.view = (function() { scroll_position = [chart.scrollLeft(),chart.scrollTop()]; return; } - if (!mousedown_node && !mousedown_link) { + if (!mousedown_node && !mousedown_link && !mousedown_group) { selected_link = null; updateSelection(); } @@ -697,7 +713,13 @@ RED.view = (function() { if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if (d3.event.metaKey || d3.event.ctrlKey) { d3.event.stopPropagation(); - showQuickAddDialog(d3.mouse(this)); + clearSelection(); + point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + if (drag_lines.length > 0) { + clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) + } + showQuickAddDialog(point, null, clickedGroup); } } if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { @@ -718,7 +740,15 @@ RED.view = (function() { } } - function showQuickAddDialog(point,spliceLink) { + function showQuickAddDialog(point, spliceLink, targetGroup) { + if (targetGroup && !targetGroup.active) { + targetGroup.active = true; + targetGroup.dirty = true; + selectGroup(targetGroup,false); + activeGroup = targetGroup; + RED.view.redraw(); + } + var ox = point[0]; var oy = point[1]; @@ -946,6 +976,11 @@ RED.view = (function() { } } } + if (targetGroup) { + RED.group.addToGroup(targetGroup, nn); + + } + if (spliceLink) { resetMouseVars(); // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog @@ -972,6 +1007,12 @@ RED.view = (function() { // auto select dropped node - so info shows (if visible) clearSelection(); nn.selected = true; + if (targetGroup) { + selectGroup(targetGroup,false); + targetGroup.active = true + targetGroup.dirty = true; + activeGroup = targetGroup; + } moving_set.push({n:nn}); updateActiveNodes(); updateSelection(); @@ -1076,12 +1117,23 @@ RED.view = (function() { return; } - if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) { return; } var mousePos; - if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (mouse_mode === RED.state.GROUP_RESIZE) { + mousePos = mouse_position; + var nx = mousePos[0] + mousedown_group.dx; + var ny = mousePos[1] + mousedown_group.dy; + switch(mousedown_group.activeHandle) { + case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; + case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; + case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; + case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; + } + mousedown_group.dirty = true; + } else if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { // update drag line if (drag_lines.length === 0 && mousedown_port_type !== null) { if (d3.event.shiftKey) { @@ -1201,6 +1253,13 @@ RED.view = (function() { node.n.y -= (maxY - space_height); } } + if (mousedown_group) { + mousedown_group.pos.x0 = mousePos[0] + mousedown_group.dx0; + mousedown_group.pos.y0 = mousePos[1] + mousedown_group.dy0; + mousedown_group.pos.x1 = mousePos[0] + mousedown_group.dx1; + mousedown_group.pos.y1 = mousePos[1] + mousedown_group.dy1; + mousedown_group.dirty = true; + } if (snapGrid != d3.event.shiftKey && moving_set.length > 0) { var gridOffset = [0,0]; node = moving_set[0]; @@ -1265,6 +1324,29 @@ RED.view = (function() { },100); } } + if (!node.n.g && activeGroups) { + if (!groupHoverTimer) { + groupHoverTimer = setTimeout(function() { + activeHoverGroup = null; + for (var i=0;i= g.pos.x0 && node.n.x <= g.pos.x1 && + node.n.y >= g.pos.y0 && node.n.y <= g.pos.y1 + ) { + g.dirty = !g.hovered; + g.hovered = true; + activeHoverGroup = g; + } else { + // Mark dirty if it is selected + g.dirty = g.hovered; + g.hovered = false; + } + } + groupHoverTimer = null; + },50); + } + } } @@ -1275,6 +1357,7 @@ RED.view = (function() { } function canvasMouseUp() { +if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } var i; var historyEvent; if (mouse_mode === RED.state.PANNING) { @@ -1311,15 +1394,22 @@ RED.view = (function() { var y = parseInt(lasso.attr("y")); var x2 = x+parseInt(lasso.attr("width")); var y2 = y+parseInt(lasso.attr("height")); - if (!d3.event.ctrlKey) { + if (!d3.event.shiftKey) { clearSelection(); } - RED.nodes.eachNode(function(n) { + activeNodes.forEach(function(n) { if (n.z == RED.workspaces.active() && !n.selected) { - n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - moving_set.push({n:n}); + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + if (n.g) { + var group = RED.nodes.group(n.g); + if (!group.selected) { + selectGroup(RED.nodes.group(n.g),true); + } + } else { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } } } }); @@ -1355,6 +1445,20 @@ RED.view = (function() { } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (moving_set.length > 0) { + if (activeHoverGroup) { + for (var j=0;j 0 ? 1: 0) : (d.direction == "in" ? 0: 1) var wasJoining = false; if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { @@ -2316,7 +2485,23 @@ RED.view = (function() { } } + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when moving_set should be prepared to be dragged + for (i=0;i 0 && clickElapsed < 750) { + mouse_mode = RED.state.DEFAULT; + RED.editor.editGroup(g); + d3.event.stopPropagation(); + return; + } + + } + function groupMouseDown(g) { + var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); + if (! (mouse[0] < g.pos.x0+10 || mouse[0] > g.pos.x1-10 || mouse[1] < g.pos.y0+10 || mouse[1] > g.pos.y1-10) ) { + return + } + + focusView(); + if (d3.event.button === 1) { + return; + } + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove("escape"); + console.log("Dragged a node into the group") + } else if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; + } else if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + mousedown_group = g; + + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = ( + lastClickNode == g && + d3.event.button === 0 && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey + ); + lastClickNode = g; + + if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (g === activeGroup) { + exitActiveGroup(); + } + deselectGroup(g); + d3.event.stopPropagation(); + } else { + if (!g.selected) { + if (!d3.event.ctrlKey && !d3.event.metaKey) { + clearSelection(); + } + selectGroup(g,true);//!wasSelected); + } else { + exitActiveGroup(); + } + + + if (d3.event.button != 2) { + var d = g.nodes[0]; + prepareDrag(mouse); + mousedown_group.dx0 = mousedown_group.pos.x0 - mouse[0]; + mousedown_group.dy0 = mousedown_group.pos.y0 - mouse[1]; + mousedown_group.dx1 = mousedown_group.pos.x1 - mouse[0]; + mousedown_group.dy1 = mousedown_group.pos.y1 - mouse[1]; + } + } + + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function selectGroup(g, includeNodes) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + if (includeNodes) { + var currentSet = new Set(moving_set.map(function(n) { return n.n })); + g.nodes.forEach(function(n) { + if (!currentSet.has(n)) { + moving_set.push({n:n}) + // n.selected = true; + } + n.dirty = true; + }) + } + } + function exitActiveGroup() { + if (activeGroup) { + activeGroup.active = false; + activeGroup.dirty = true; + deselectGroup(activeGroup); + selectGroup(activeGroup,true); + activeGroup = null; + } + } + function deselectGroup(g) { + if (g.selected) { + g.selected = false; + g.dirty = true; + } + var nodeSet = new Set(g.nodes); + for (var i = moving_set.length-1; i >= 0; i -= 1) { + if (nodeSet.has(moving_set[i].n)) { + moving_set[i].n.selected = false; + moving_set[i].n.dirty = true; + moving_set.splice(i,1); + } + } + } + function getGroupAt(x,y) { + for (var i=0;i= g.pos.x0 && x <= g.pos.x1 && y >= g.pos.y0 && y <= g.pos.y1) { + return g; + } + } + return null; + } + + function groupHandleMouseDown(group, groupEl, handle,handleIndex) { + d3.event.stopPropagation(); + console.log("GHANDLE MD"); + if (d3.event.button != 2) { + mousedown_group = group; + group.activeHandle = handleIndex; + var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); + switch(handleIndex) { + case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; + case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; + case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; + case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; + } + group.dx = group.ox - mouse[0]; + group.dy = group.oy - mouse[1]; + console.log("START",group.ox, group.oy); + mouse_offset = d3.mouse(document.body); + if (isNaN(mouse_offset[0])) { + mouse_offset = d3.touches(document.body)[0]; + } + mouse_mode = RED.state.GROUP_RESIZE; + } + } + function groupHandleMouseUp(group,groupEl,handle,handleIndex) { + console.log("GHANDLE MU"); + d3.event.stopPropagation(); + delete group.ox; + delete group.oy; + delete group.dx; + delete group.dy; + resetMouseVars(); + mouse_mode = RED.state.DEFAULT; + } + function isButtonEnabled(d) { var buttonEnabled = true; var ws = RED.nodes.workspace(RED.workspaces.active()); @@ -3294,6 +3712,19 @@ RED.view = (function() { } d.dirty = false; + + if (d.g) { + if (!dirtyGroups[d.g]) { + dirtyGroups[d.g] = RED.nodes.group(d.g); + } + var group = dirtyGroups[d.g]; + group.pos = { + x0: Math.min(group.pos.x0,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)), + y0: Math.min(group.pos.y0,d.y-d.h/2-25), + x1: Math.max(group.pos.x1,d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0)), + y1: Math.max(group.pos.y1,d.y+d.h/2+25) + } + } } }); @@ -3324,7 +3755,9 @@ RED.view = (function() { d3.event.stopPropagation(); if (d3.event.metaKey || d3.event.ctrlKey) { l.classed("red-ui-flow-link-splice",true); - showQuickAddDialog(d3.mouse(this), selected_link); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog(point, selected_link, clickedGroup); } }) .on("touchstart",function(d) { @@ -3509,6 +3942,112 @@ RED.view = (function() { }) + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.exit().remove(); + var groupEnter = group.enter().insert("svg:g") + .attr("class", "red-ui-flow-group") + + groupEnter.each(function(d,i) { + var g = d3.select(this); + + // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-0",true).style({ + // "fill":"white", + // "stroke": "#ff7f0e", + // "stroke-width": 2 + // }).attr("d","m -5 6 v -2 q 0 -9 9 -9 h 2 v 11 z") + // + // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-1",true).style({ + // "fill": "white", + // "stroke": "#ff7f0e", + // "stroke-width": 2 + // }).attr("d","m -6 -5 h 2 q 9 0 9 9 v 2 h -11 z") + // + // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-2",true).style({ + // "fill": "white", + // "stroke": "#ff7f0e", + // "stroke-width": 2 + // }).attr("d","m 5 -6 v 2 q 0 9 -9 9 h -2 v -11 z") + // + // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-3",true).style({ + // "fill": "white", + // "stroke": "#ff7f0e", + // "stroke-width": 2 + // }).attr("d","m 6 5 h -2 q -9 0 -9 -9 v -2 h 11 z") + + + + g.append('rect').classed("red-ui-flow-group-outline",true) + .attr('rx',1).attr('ry',1).style({ + "fill":"none", + "stroke": "#ff7f0e", + "stroke-opacity": 0, + "stroke-width": 15 + }) + g.append('rect').classed("red-ui-flow-group-body",true) + .attr('rx',1).attr('ry',1).style({ + "fill":d.fill||"none", + "stroke": d.stroke||"none", + "stroke-width": 2 + }) + + + d.dirty = true; + g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + + }); + group.each(function(d,i) { + if (d.dirty || dirtyGroups[d.id]) { + var g = d3.select(this); + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + d.nodes.forEach(function(n) { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + }); + + d.pos = { + x0: minX, y0: minY, + x1: maxX, y1: maxY + } + + g.attr("transform","translate("+d.pos.x0+","+d.pos.y0+")"); + g.selectAll(".red-ui-flow-group-outline") + .attr("width",d.pos.x1-d.pos.x0) + .attr("height",d.pos.y1-d.pos.y0) + .style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0}); + + g.selectAll(".red-ui-flow-group-body") + .attr("width",d.pos.x1-d.pos.x0) + .attr("height",d.pos.y1-d.pos.y0) + .style("stroke",function(d) { /*if (d.selected) { return "#ff7f0e" } */return d.style.stroke || "none"}) + .style("stroke-dasharray", function(d) { return (d.active||d.hovered)?"10 4":"none"}) + .style("fill", function(d) { return d.style.fill || "none"}) + .style("fill-opacity", 0.1) + + // g.selectAll(".red-ui-flow-group-handle-0") + // .style("opacity",function(d) { return d.selected?1:0}) + // g.selectAll(".red-ui-flow-group-handle-1") + // .attr("transform","translate("+(maxX-minX)+",0)") + // .style("opacity",function(d) { return d.selected?1:0}) + // g.selectAll(".red-ui-flow-group-handle-2") + // .attr("transform","translate("+(maxX-minX)+","+(maxY-minY)+")") + // .style("opacity",function(d) { return d.selected?1:0}) + // g.selectAll(".red-ui-flow-group-handle-3") + // .attr("transform","translate(0,"+(maxY-minY)+")") + // .style("opacity",function(d) { return d.selected?1:0}) + + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) + + } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( @@ -3785,10 +4324,17 @@ RED.view = (function() { selectedNode.dirty = true; moving_set = [{n:selectedNode}]; } + } else if (selection) { + if (selection.groups) { + updateActiveNodes(); + selection.groups.forEach(function(g) { + selectGroup(g,true); + }) + } } } updateSelection(); - redraw(); + redraw(true); }, selection: function() { var selection = {}; @@ -3798,6 +4344,10 @@ RED.view = (function() { if (selected_link != null) { selection.link = selected_link; } + selection.groups = activeGroups.filter(function(g) { return g.selected }) + if (selection.groups.length === 0) { + delete selection.groups; + } return selection; }, scale: function() { From 97d58e34f2818fb1f7c958c7a5e31ada07c7dc15 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Mar 2020 21:48:38 +0000 Subject: [PATCH 044/366] [groups] Support nested groups in editor --- .../editor-client/src/js/ui/group.js | 217 ++++++++++++++++ .../@node-red/editor-client/src/js/ui/view.js | 243 ++++++++++-------- 2 files changed, 356 insertions(+), 104 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/group.js 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 new file mode 100644 index 000000000..7c9bd67f9 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -0,0 +1,217 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +RED.group = (function() { + + var _groupEditTemplate = ''; + + var groupDef = { + defaults:{ + name:{value:""}, + style:{value:{}} + }, + category: "config", + oneditprepare: function() { + var style = this.style || {}; + $("#node-input-style-stroke").val(style.stroke || "#eeeeee") + $("#node-input-style-fill").val(style.fill || "none") + }, + oneditresize: function(size) { + }, + oneditsave: function() { + this.style.stroke = $("#node-input-style-stroke").val(); + this.style.fill = $("#node-input-style-fill").val(); + }, + set:{ + module: "node-red" + } + } + + function init() { + + + RED.actions.add("core:group-selection", function() { groupSelection() }) + + $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); + + } + + function groupSelection() { + var selection = RED.view.selection(); + // var groupNodes = new Set(); + // + // if (selection.groups) { + // selection.groups.forEach(function(g) { + // g.nodes.forEach() + // }) + // } + // + // if (selection.nodes) { + // + // } + + if (selection.nodes) { + var group = createGroup(selection.nodes); + if (group) { + RED.view.select({groups:[group]}) + } + } + } + function createGroup(nodes) { + if (nodes.length === 0) { + return; + } + // nodes is an array + // each node must be on the same tab (z) + var group = { + id: RED.nodes.id(), + type: 'group', + nodes: [], + style: { + stroke: "#999", + fill: "none" + }, + x: Number.POSITIVE_INFINITY, + y: Number.POSITIVE_INFINITY, + w: 0, + h: 0, + _def: RED.group.def + } + try { + addToGroup(group,nodes); + } catch(err) { + RED.notify(err,"error"); + return; + } + group.z = nodes[0].z; + + RED.nodes.addGroup(group); + return group; + } + + function addToGroup(group,nodes) { + if (!Array.isArray(nodes)) { + nodes = [nodes]; + } + var i,n,z; + var g; + // First pass - validate we can safely add these nodes to the group + for (i=0;i -1) { + g.nodes.splice(ni,1) + } + } + n.g = group.id; + group.nodes.push(n); + group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + group.y = Math.min(group.y,n.y-n.h/2-25); + group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x), + group.h = Math.max(group.h,n.y+n.h/2+25-group.y); + } + } + + function getNodes(group,recursive) { + var nodes = []; + group.nodes.forEach(function(n) { + if (!recursive || n.type !== 'group') { + nodes.push(n); + } else { + nodes = nodes.concat(getNodes(n,recursive)) + } + }) + return nodes; + } + + function groupContains(group,item) { + if (item.g === group.id) { + return true; + } + for (var i=0;i 0) { var gridOffset = [0,0]; node = moving_set[0]; @@ -1327,20 +1326,15 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } if (!node.n.g && activeGroups) { if (!groupHoverTimer) { groupHoverTimer = setTimeout(function() { - activeHoverGroup = null; + activeHoverGroup = getGroupAt(node.n.x,node.n.y); for (var i=0;i= g.pos.x0 && node.n.x <= g.pos.x1 && - node.n.y >= g.pos.y0 && node.n.y <= g.pos.y1 - ) { - g.dirty = !g.hovered; + if (g === activeHoverGroup) { g.hovered = true; - activeHoverGroup = g; - } else { - // Mark dirty if it is selected - g.dirty = g.hovered; + g.dirty = true; + } else if (g.hovered) { g.hovered = false; + g.dirty = true; } } groupHoverTimer = null; @@ -1972,7 +1966,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } - + function calculateTextWidth(str, className, offset) { var result=convertLineBreakCharacter(str); var width = 0; @@ -2714,11 +2708,12 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } + function groupMouseDown(g) { var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); - if (! (mouse[0] < g.pos.x0+10 || mouse[0] > g.pos.x1-10 || mouse[1] < g.pos.y0+10 || mouse[1] > g.pos.y1-10) ) { - return - } + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } focusView(); if (d3.event.button === 1) { @@ -2768,10 +2763,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (d3.event.button != 2) { var d = g.nodes[0]; prepareDrag(mouse); - mousedown_group.dx0 = mousedown_group.pos.x0 - mouse[0]; - mousedown_group.dy0 = mousedown_group.pos.y0 - mouse[1]; - mousedown_group.dx1 = mousedown_group.pos.x1 - mouse[0]; - mousedown_group.dy1 = mousedown_group.pos.y1 - mouse[1]; + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; } } @@ -2787,7 +2780,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } if (includeNodes) { var currentSet = new Set(moving_set.map(function(n) { return n.n })); - g.nodes.forEach(function(n) { + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { if (!currentSet.has(n)) { moving_set.push({n:n}) // n.selected = true; @@ -2820,48 +2814,62 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } function getGroupAt(x,y) { + var candidateGroups = {}; for (var i=0;i= g.pos.x0 && x <= g.pos.x1 && y >= g.pos.y0 && y <= g.pos.y1) { - return g; + if (x >= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; } } - return null; + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[0]] + } } - function groupHandleMouseDown(group, groupEl, handle,handleIndex) { - d3.event.stopPropagation(); - console.log("GHANDLE MD"); - if (d3.event.button != 2) { - mousedown_group = group; - group.activeHandle = handleIndex; - var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); - switch(handleIndex) { - case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; - case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; - case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; - case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; - } - group.dx = group.ox - mouse[0]; - group.dy = group.oy - mouse[1]; - console.log("START",group.ox, group.oy); - mouse_offset = d3.mouse(document.body); - if (isNaN(mouse_offset[0])) { - mouse_offset = d3.touches(document.body)[0]; - } - mouse_mode = RED.state.GROUP_RESIZE; - } - } - function groupHandleMouseUp(group,groupEl,handle,handleIndex) { - console.log("GHANDLE MU"); - d3.event.stopPropagation(); - delete group.ox; - delete group.oy; - delete group.dx; - delete group.dy; - resetMouseVars(); - mouse_mode = RED.state.DEFAULT; - } + // function groupHandleMouseDown(group, groupEl, handle,handleIndex) { + // d3.event.stopPropagation(); + // console.log("GHANDLE MD"); + // if (d3.event.button != 2) { + // mousedown_group = group; + // group.activeHandle = handleIndex; + // var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); + // switch(handleIndex) { + // case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; + // case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; + // case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; + // case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; + // } + // group.dx = group.ox - mouse[0]; + // group.dy = group.oy - mouse[1]; + // console.log("START",group.ox, group.oy); + // mouse_offset = d3.mouse(document.body); + // if (isNaN(mouse_offset[0])) { + // mouse_offset = d3.touches(document.body)[0]; + // } + // mouse_mode = RED.state.GROUP_RESIZE; + // } + // } + // function groupHandleMouseUp(group,groupEl,handle,handleIndex) { + // console.log("GHANDLE MU"); + // d3.event.stopPropagation(); + // delete group.ox; + // delete group.oy; + // delete group.dx; + // delete group.dy; + // resetMouseVars(); + // mouse_mode = RED.state.DEFAULT; + // } function isButtonEnabled(d) { var buttonEnabled = true; @@ -3712,18 +3720,19 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } d.dirty = false; - if (d.g) { if (!dirtyGroups[d.g]) { - dirtyGroups[d.g] = RED.nodes.group(d.g); - } - var group = dirtyGroups[d.g]; - group.pos = { - x0: Math.min(group.pos.x0,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)), - y0: Math.min(group.pos.y0,d.y-d.h/2-25), - x1: Math.max(group.pos.x1,d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0)), - y1: Math.max(group.pos.y1,d.y+d.h/2+25) + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } } + // var group = dirtyGroups[d.g]; + // group.x = Math.min(group.x,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)); + // group.y = Math.min(group.y,d.y-d.h/2-25), + // group.w = Math.max(group.w, d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0) - group.x), + // group.h = Math.max(group.h, d.y+d.h/2+25 - group.y) } } }); @@ -3981,20 +3990,21 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } .attr('rx',1).attr('ry',1).style({ "fill":"none", "stroke": "#ff7f0e", + "pointer-events": "stroke", "stroke-opacity": 0, "stroke-width": 15 }) + g.append('rect').classed("red-ui-flow-group-body",true) .attr('rx',1).attr('ry',1).style({ + "pointer-events": "none", "fill":d.fill||"none", "stroke": d.stroke||"none", "stroke-width": 2 }) - - - d.dirty = true; g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + d.dirty = true; }); group.each(function(d,i) { if (d.dirty || dirtyGroups[d.id]) { @@ -4004,26 +4014,33 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } var maxX = 0; var maxY = 0; d.nodes.forEach(function(n) { - minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-25); - maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+25); + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } }); - d.pos = { - x0: minX, y0: minY, - x1: maxX, y1: maxY - } + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; - g.attr("transform","translate("+d.pos.x0+","+d.pos.y0+")"); + g.attr("transform","translate("+d.x+","+d.y+")"); g.selectAll(".red-ui-flow-group-outline") - .attr("width",d.pos.x1-d.pos.x0) - .attr("height",d.pos.y1-d.pos.y0) + .attr("width",d.w) + .attr("height",d.h) .style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0}); g.selectAll(".red-ui-flow-group-body") - .attr("width",d.pos.x1-d.pos.x0) - .attr("height",d.pos.y1-d.pos.y0) + .attr("width",d.w) + .attr("height",d.h) .style("stroke",function(d) { /*if (d.selected) { return "#ff7f0e" } */return d.style.stroke || "none"}) .style("stroke-dasharray", function(d) { return (d.active||d.hovered)?"10 4":"none"}) .style("fill", function(d) { return d.style.fill || "none"}) @@ -4338,16 +4355,34 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }, selection: function() { var selection = {}; + + var allNodes = new Set(); + if (moving_set.length > 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); + moving_set.forEach(function(n) { + allNodes.add(n.n); + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); } if (selected_link != null) { selection.link = selected_link; } - selection.groups = activeGroups.filter(function(g) { return g.selected }) - if (selection.groups.length === 0) { - delete selection.groups; - } return selection; }, scale: function() { From 51ea5dc34276cff2f8ae4f6eac091b2336e77aa1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 10:43:28 +0000 Subject: [PATCH 045/366] [groups] Add ungroup-selection action --- .../editor-client/src/js/keymap.json | 3 +- .../@node-red/editor-client/src/js/nodes.js | 7 +- .../editor-client/src/js/ui/group.js | 43 ++++++++---- .../@node-red/editor-client/src/js/ui/view.js | 66 +++++++++++-------- 4 files changed, 75 insertions(+), 44 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 c4d666845..5bd938e98 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 @@ -62,6 +62,7 @@ "shift-left": "core:step-selection-left", "ctrl-shift-j": "core:show-previous-tab", "ctrl-shift-k": "core:show-next-tab", - "ctrl-shift-g": "core:group-selection" + "ctrl-shift-g": "core:group-selection", + "ctrl-shift-u": "core:ungroup-selection" } } 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 2bf8d4a21..fdd6886d1 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 @@ -1452,7 +1452,11 @@ RED.nodes = (function() { groupsByZ[group.z].push(group); groups[group.id] = group; } - + function removeGroup(group) { + var i = groupsByZ[group.z].indexOf(group); + groupsByZ[group.z].splice(i,1); + delete groups[group.id]; + } return { @@ -1551,6 +1555,7 @@ RED.nodes = (function() { subflowContains: subflowContains, addGroup: addGroup, + removeGroup: removeGroup, group: function(id) { return groups[id] }, groups: function(z) { return groupsByZ[z] }, 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 7c9bd67f9..bfc73f098 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 @@ -60,6 +60,7 @@ RED.group = (function() { RED.actions.add("core:group-selection", function() { groupSelection() }) + RED.actions.add("core:ungroup-selection", function() { ungroupSelection() }) $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); @@ -67,25 +68,39 @@ RED.group = (function() { function groupSelection() { var selection = RED.view.selection(); - // var groupNodes = new Set(); - // - // if (selection.groups) { - // selection.groups.forEach(function(g) { - // g.nodes.forEach() - // }) - // } - // - // if (selection.nodes) { - // - // } - if (selection.nodes) { var group = createGroup(selection.nodes); if (group) { - RED.view.select({groups:[group]}) + RED.view.select({nodes:[group]}) } } } + function ungroupSelection() { + var selection = RED.view.selection(); + if (selection.nodes) { + var newSelection = []; + groups = selection.nodes.filter(function(n) { return n.type === "group" }); + groups.forEach(function(g) { + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var index = parentGroup.nodes.indexOf(g); + parentGroup.nodes.splice(index,1); + } + g.nodes.forEach(function(n) { + newSelection.push(n); + if (parentGroup) { + // Move nodes to parent group + n.g = parentGroup.id; + parentGroup.nodes.push(n); + } else { + delete n.g; + } + }) + RED.nodes.removeGroup(g); + }) + RED.view.select({nodes:newSelection}) + } + } function createGroup(nodes) { if (nodes.length === 0) { return; @@ -168,7 +183,7 @@ RED.group = (function() { group.nodes.push(n); group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); group.y = Math.min(group.y,n.y-n.h/2-25); - group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x), + group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x); group.h = Math.max(group.h,n.y+n.h/2+25-group.y); } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 81ba16b81..94937aa65 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -29,7 +29,6 @@ RED.view = (function() { var DEBUG_EVENTS = false; - 2 var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, @@ -63,30 +62,30 @@ RED.view = (function() { var activeGroups = []; var dirtyGroups = {}; - var selected_link = null, - mousedown_link = null, - mousedown_node = null, - mousedown_group = null, - mousedown_port_type = null, - mousedown_port_index = 0, - mouseup_node = null, - mouse_offset = [0,0], - mouse_position = null, - mouse_mode = 0, - mousedown_group_handle = null; - moving_set = [], - lasso = null, - ghostNode = null, - showStatus = false, - lastClickNode = null, - dblClickPrimed = null, - clickTime = 0, - clickElapsed = 0, - scroll_position = [], - quickAddActive = false, - quickAddLink = null, - showAllLinkPorts = -1, - groupNodeSelectPrimed = false; + var selected_link = null; + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var moving_set = []; + var lasso = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; var selectNodesOptions; @@ -4342,10 +4341,21 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } moving_set = [{n:selectedNode}]; } } else if (selection) { - if (selection.groups) { + if (selection.nodes) { updateActiveNodes(); - selection.groups.forEach(function(g) { - selectGroup(g,true); + moving_set = []; + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } else { + selectGroup(n,true); + } }) } } From 4d96d953705757632329081f0a6e7ff840e6ebd3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 15:52:26 +0000 Subject: [PATCH 046/366] [groups] Add merge-selection-to-group and remove-selection-from-group --- .../editor-client/src/js/ui/group.js | 118 +++++++++++++++--- .../@node-red/editor-client/src/js/ui/view.js | 2 +- 2 files changed, 105 insertions(+), 15 deletions(-) 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 bfc73f098..22deb3f32 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 @@ -61,6 +61,8 @@ RED.group = (function() { RED.actions.add("core:group-selection", function() { groupSelection() }) RED.actions.add("core:ungroup-selection", function() { ungroupSelection() }) + RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() }) + RED.actions.add("core:remove-selection-from-group", function() { removeSelection() }) $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); @@ -81,24 +83,110 @@ RED.group = (function() { var newSelection = []; groups = selection.nodes.filter(function(n) { return n.type === "group" }); groups.forEach(function(g) { - var parentGroup = RED.nodes.group(g.g); - if (parentGroup) { - var index = parentGroup.nodes.indexOf(g); - parentGroup.nodes.splice(index,1); + newSelection = newSelection.concat(ungroup(g)) + }) + RED.view.select({nodes:newSelection}) + } + } + + function ungroup(g) { + var nodes = []; + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var index = parentGroup.nodes.indexOf(g); + parentGroup.nodes.splice(index,1); + } + g.nodes.forEach(function(n) { + nodes.push(n); + if (parentGroup) { + // Move nodes to parent group + n.g = parentGroup.id; + parentGroup.nodes.push(n); + parentGroup.dirty = true; + n.dirty = true; + } else { + delete n.g; + } + }) + RED.nodes.removeGroup(g); + return nodes; + } + + function mergeSelection() { + // TODO: this currently creates an entirely new group. Need to merge properties + // of any existing group + var selection = RED.view.selection(); + if (selection.nodes) { + var nodes = []; + var n; + var parentGroup; + // First pass, check they are all in the same parent + // TODO: DRY mergeSelection,removeSelection,... + for (var i=0; i 0) { if (selectedGroups.length === 1 && selectedGroups[0].active) { // Let nodes be nodes From 9a0c843f29393e7bd871bd88aecbdc71f9114712 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 22:49:31 +0000 Subject: [PATCH 047/366] [groups] Support deleting groups as part of selection --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 9 +++++++++ .../@node-red/editor-client/src/js/ui/group.js | 4 ---- .../@node-red/editor-client/src/js/ui/view.js | 4 ++++ 3 files changed, 13 insertions(+), 4 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 fdd6886d1..5c0004d8c 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 @@ -1455,6 +1455,15 @@ RED.nodes = (function() { function removeGroup(group) { var i = groupsByZ[group.z].indexOf(group); groupsByZ[group.z].splice(i,1); + + if (group.g) { + if (groups[group.g]) { + var index = groups[group.g].nodes.indexOf(group); + groups[group.g].nodes.splice(index,1); + groups[group.g].dirty = true; + } + } + delete groups[group.id]; } 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 22deb3f32..e4aaf699e 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 @@ -92,10 +92,6 @@ RED.group = (function() { function ungroup(g) { var nodes = []; var parentGroup = RED.nodes.group(g.g); - if (parentGroup) { - var index = parentGroup.nodes.indexOf(g); - parentGroup.nodes.splice(index,1); - } g.nodes.forEach(function(n) { nodes.push(n); if (parentGroup) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index aaa92fa70..4d99f7b87 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1823,6 +1823,10 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } node.dirty = true; } } + var selectedGroups = activeGroups.filter(function(g) { return !g.active && g.selected }); + selectedGroups.forEach(function(g) { + RED.nodes.removeGroup(g); + }); if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { From 84d2b8ad6db06c34b678c65b12b856f10548c02f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sat, 7 Mar 2020 01:55:45 +0900 Subject: [PATCH 048/366] add support of initialization & finalization to function node --- .../editor-client/src/js/ui/library.js | 28 ++- .../nodes/core/function/10-function.html | 224 ++++++++++++++++-- .../nodes/core/function/10-function.js | 6 + .../locales/en-US/function/10-function.html | 3 +- .../nodes/locales/en-US/messages.json | 2 + .../locales/ja/function/10-function.html | 3 +- .../@node-red/nodes/locales/ja/messages.json | 2 + test/nodes/core/function/10-function_spec.js | 27 ++- 8 files changed, 269 insertions(+), 26 deletions(-) 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 e25223f94..2520c16a9 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 @@ -50,6 +50,18 @@ RED.library = (function() { ''+ '
          ' + function toSingleLine(text) { + var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n"); + return result; + } + + function fromSingleLine(text) { + var result = text.replace(/\\[\\n]/g, function(s) { + return ((s === "\\\\") ? "\\" : "\n"); + }); + return result; + } + function saveToLibrary() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; var name = $("#"+elementPrefix+"name").val().trim(); @@ -68,6 +80,10 @@ RED.library = (function() { var field = activeLibrary.fields[i]; if (field == "name") { data.name = name; + } else if(field == "initialize") { + data.initialize = toSingleLine(activeLibrary.initEditor.getValue()); + } else if(field == "finalize") { + data.finalize = toSingleLine(activeLibrary.finalizeEditor.getValue()); } else { data[field] = $("#"+elementPrefix+field).val(); } @@ -523,7 +539,17 @@ RED.library = (function() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; for (var i=0; i
          -
          - - - + +
          +
            -
            -
            -
            -
            -
            - - + +
            + + + + + + +
            + diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index 65a1b4a61..7bdeada65 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -62,6 +62,8 @@ module.exports = function(RED) { var node = this; this.name = n.name; this.func = n.func; + this.ini = n.initialize; + this.fin = n.finalize; var handleNodeDoneCall = true; // Check to see if the Function appears to call `node.done()`. If so, @@ -89,6 +91,8 @@ module.exports = function(RED) { "};\n"+ this.func+"\n"+ "})(msg,send,done);"; + var iniText = "(function () {\n"+this.ini +"\n})();"; + var finText = "(function () {\n"+this.fin +"\n})();"; this.topic = n.topic; this.outstandingTimers = []; this.outstandingIntervals = []; @@ -229,6 +233,7 @@ module.exports = function(RED) { } var context = vm.createContext(sandbox); try { + vm.runInContext(iniText, context); this.script = vm.createScript(functionText, { filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces displayErrors: true @@ -297,6 +302,7 @@ module.exports = function(RED) { } }); this.on("close", function() { + vm.runInContext(finText, context); while (node.outstandingTimers.length > 0) { clearTimeout(node.outstandingTimers.pop()); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html index 28bc76f3a..c1c768f84 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html @@ -15,12 +15,13 @@ --> '; + var colorPalette = [ + "#ff0000", + "#ffC000", + "#ffff00", + "#92d04f", + "#0070c0", + "#001f60", + "#6f2fa0", + "#000000", + "#777777" + ] + var colorSteps = 3; + var colorCount = colorPalette.length; + for (var i=0,len=colorPalette.length*colorSteps;i Date: Fri, 20 Mar 2020 20:00:03 +0000 Subject: [PATCH 067/366] [groups] Add style options for group label --- .../editor-client/locales/en-US/editor.json | 2 +- .../src/js/ui/common/colorPicker.js | 223 ++++++++++++++++++ .../editor-client/src/js/ui/group.js | 139 ++++++++++- .../@node-red/editor-client/src/js/ui/view.js | 62 ++++- .../editor-client/src/sass/editor.scss | 52 +++- 5 files changed, 461 insertions(+), 17 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index a5254661b..2b3cb691b 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -15,7 +15,7 @@ "next": "Next", "clone": "Clone project", "cont": "Continue", - "stroke": "Stroke", + "line": "Outline", "fill": "Fill" }, "type": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js new file mode 100644 index 000000000..47f4cfed2 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -0,0 +1,223 @@ +RED.colorPicker = (function() { + + function getDarkerColor(c) { + var r,g,b; + if (/^#[a-f0-9]{6}$/i.test(c)) { + r = parseInt(c.substring(1, 3), 16); + g = parseInt(c.substring(3, 5), 16); + b = parseInt(c.substring(5, 7), 16); + } else if (/^#[a-f0-9]{3}$/i.test(c)) { + r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16); + g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16); + b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16); + } else { + return c; + } + var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ; + r = Math.max(0,r-50); + g = Math.max(0,g-50); + b = Math.max(0,b-50); + return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0') + } + + function create(options) { + var color = options.value; + var id = options.id; + var colorPalette = options.palette || []; + var width = options.cellWidth || 30; + var height = options.cellHeight || 30; + var margin = options.cellMargin || 2; + var perRow = options.cellPerRow || 6; + + var container = $("
            ",{style:"display:inline-block"}); + var colorHiddenInput = $("", { id: id, type: "hidden", value: color }).appendTo(container); + var opacityHiddenInput = $("", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container); + + var colorButton = $('
            '+ '
            '+ ''+ @@ -535,9 +536,8 @@ RED.group = (function() { function getNodes(group,recursive) { var nodes = []; group.nodes.forEach(function(n) { - if (!recursive || n.type !== 'group') { - nodes.push(n); - } else { + nodes.push(n); + if (recursive && n.type === 'group') { nodes = nodes.concat(getNodes(n,recursive)) } }) 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 539fb763d..61ab71c72 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 @@ -67,9 +67,16 @@ RED.view.tools = (function() { function moveSelection(dx,dy) { if (moving_set === null) { + moving_set = []; var selection = RED.view.selection(); if (selection.nodes) { - moving_set = selection.nodes.map(function(n) { return {n:n}}); + while (selection.nodes.length > 0) { + var n = selection.nodes.shift(); + moving_set.push({n:n}); + if (n.type === "group") { + selection.nodes = selection.nodes.concat(n.nodes); + } + } } } if (moving_set && moving_set.length > 0) { @@ -93,6 +100,9 @@ RED.view.tools = (function() { node.n.x += dx; node.n.y += dy; node.n.dirty = true; + if (node.n.type === "group") { + RED.group.markDirty(node.n); + } minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 22db1a99b..094e2e0db 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -415,7 +415,7 @@ RED.view = (function() { moving_set.push({n:nn}); if (group) { selectGroup(group,false); - group.active = true; + enterActiveGroup(group); activeGroup = group; } updateActiveNodes(); @@ -790,10 +790,8 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } function showQuickAddDialog(point, spliceLink, targetGroup) { if (targetGroup && !targetGroup.active) { - targetGroup.active = true; - targetGroup.dirty = true; selectGroup(targetGroup,false); - activeGroup = targetGroup; + enterActiveGroup(targetGroup); RED.view.redraw(); } @@ -1068,9 +1066,7 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } nn.selected = true; if (targetGroup) { selectGroup(targetGroup,false); - targetGroup.active = true - targetGroup.dirty = true; - activeGroup = targetGroup; + enterActiveGroup(targetGroup); } moving_set.push({n:nn}); updateActiveNodes(); @@ -1294,10 +1290,18 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } node.n.x = mousePos[0]+node.dx; node.n.y = mousePos[1]+node.dy; node.n.dirty = true; - minX = Math.min(node.n.x-node.n.w/2-5,minX); - minY = Math.min(node.n.y-node.n.h/2-5,minY); - maxX = Math.max(node.n.x+node.n.w/2+5,maxX); - maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + if (node.n.type === "group") { + RED.group.markDirty(node.n); + minX = Math.min(node.n.x-5,minX); + minY = Math.min(node.n.y-5,minY); + maxX = Math.max(node.n.x+node.n.w+5,maxX); + maxY = Math.max(node.n.y+node.n.h+5,maxY); + } else { + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + maxX = Math.max(node.n.x+node.n.w/2+5,maxX); + maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + } } if (minX !== 0 || minY !== 0) { for (i = 0; i 0) { - var gridOffset = [0,0]; - node = moving_set[0]; - gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); - gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = moving_set[i++]; + } while(i x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + while (g.g) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + }) + activeNodes.forEach(function(n) { if (!n.selected) { if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { @@ -1469,6 +1500,9 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } } } }); + + + // var selectionChanged = false; // do { // selectionChanged = false; @@ -1521,11 +1555,9 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } addedToGroup = activeHoverGroup; activeHoverGroup.hovered = false; - activeHoverGroup.dirty = true; - activeHoverGroup.active = true; - activeGroup = activeHoverGroup; + enterActiveGroup(activeHoverGroup) + // TODO: check back whether this should add to moving_set activeGroup.selected = true; - activeHoverGroup = null; } @@ -1538,6 +1570,7 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } n.n.moved = true; } } + if (ns.length > 0) { historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; if (activeSpliceLink) { @@ -1817,6 +1850,8 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } var node = moving_set[0].n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); } else { RED.editor.edit(node); } @@ -1882,11 +1917,14 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } var startDirty = RED.nodes.dirty(); var startChanged = false; + var selectedGroups = []; if (moving_set.length > 0) { for (var i=0;i 0) { var g = selectedGroups.shift(); if (removedGroups.indexOf(g) === -1) { @@ -2040,24 +2077,31 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } } }); } else { - if (moving_set.length > 0) { - nodes = moving_set.map(function(n) { return n.n }); - } - var groups = activeGroups.filter(function(g) { return g.selected && !g.active }); - while (groups.length > 0) { - var group = groups.shift(); - nodes.push(group); - groups = groups.concat(group.nodes.filter(function(n) { return n.type === "group" })) + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) } } if (nodes.length > 0) { var nns = []; + var nodeCount = 0; + var groupCount = 0; for (var n=0;n= 0; i -= 1) { + if (moving_set[i].n === group) { + moving_set.splice(i,1); + break; + } + } + } function exitActiveGroup() { if (activeGroup) { activeGroup.active = false; @@ -2927,8 +2986,9 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.dirty = true; } var nodeSet = new Set(g.nodes); + nodeSet.add(g); for (var i = moving_set.length-1; i >= 0; i -= 1) { - if (nodeSet.has(moving_set[i].n)) { + if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { moving_set[i].n.selected = false; moving_set[i].n.dirty = true; moving_set.splice(i,1); @@ -4072,30 +4132,48 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } group[0].reverse(); group.each(function(d,i) { + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } if (d.dirty || dirtyGroups[d.id]) { var g = d3.select(this); - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - d.nodes.forEach(function(n) { - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-25); - maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+25); - } else { - minX = Math.min(minX,n.x-25) - minY = Math.min(minY,n.y-25) - maxX = Math.max(maxX,n.x+n.w+25) - maxY = Math.max(maxY,n.y+n.h+25) - } - }); + if (d.nodes.length > 0) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + d.nodes.forEach(function(n) { + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + } else { + d.w = 40; + d.h = 40; + } + if (!d.minWidth) { + if (d.style.label && d.name) { + d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8); + } else { + d.minWidth = 40; + } + } + d.w = Math.max(d.minWidth,d.w); - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; g.attr("transform","translate("+d.x+","+d.y+")") .classed("red-ui-flow-group-hovered",d.hovered); @@ -4113,10 +4191,10 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.selectAll(".red-ui-flow-group-body") .attr("width",d.w) .attr("height",d.h) - .style("stroke", d.style.stroke || "none") - .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : 1) - .style("fill", d.style.fill || "none") - .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : 1) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") var label = g.selectAll(".red-ui-flow-group-label"); label.classed("hide",!!!d.style.label) @@ -4210,22 +4288,29 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } RED.workspaces.show(new_default_workspace.id); } var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); + new_ms = new_ms.concat(new_groups.map(function(g) { return {n:g}})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - + // var new_gs = new_groups.filter(function(g) { console.log(g.id,g.x,g.y); return g.nodes.length === 0}).map(function(g) { return {n:g}}) // TODO: pick a more sensible root node - if (new_ms.length > 0) { - var root_node = new_ms[0].n; - var dx = root_node.x; - var dy = root_node.y; + if (new_ms.length > 0 /* || new_gs.length > 0*/) { + if (mouse_position == null) { mouse_position = [0,0]; } + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + dx = root_node.x; + dy = root_node.y; + } + var minX = 0; var minY = 0; var i; - var node; + var node,group; for (i=0;i 0) { @@ -4402,8 +4506,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } RED.view.redraw(); } - - function getSelection() { var selection = {}; @@ -4411,10 +4513,12 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (moving_set.length > 0) { moving_set.forEach(function(n) { - allNodes.add(n.n); + if (n.n.type !== 'group') { + allNodes.add(n.n); + } }); } - var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); if (selectedGroups.length > 0) { if (selectedGroups.length === 1 && selectedGroups[0].active) { // Let nodes be nodes From 03e9522d98a72cd6e41a8c9208ffd3ad0913e8d1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Mar 2020 18:01:57 +0000 Subject: [PATCH 079/366] [groups] Include groups when exporting --- .../@node-red/editor-client/src/js/nodes.js | 19 ++++++++++++++++--- .../editor-client/src/js/ui/clipboard.js | 4 +++- 2 files changed, 19 insertions(+), 4 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 8d3aac89f..14f673d37 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 @@ -697,8 +697,18 @@ RED.nodes = (function() { /** * Converts the current node selection to an exportable JSON Object **/ - function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) { + function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) { var nns = []; + + exportedIds = exportedIds || {}; + set = set.filter(function(n) { + if (exportedIds[n.id]) { + return false; + } + exportedIds[n.id] = true; + return true; + }) + exportedConfigNodes = exportedConfigNodes || {}; exportedSubflows = exportedSubflows || {}; for (var n=0;n Date: Thu, 26 Mar 2020 20:26:58 +0000 Subject: [PATCH 080/366] [groups] Include groups when copying whole tabs --- .../@node-red/editor-client/src/js/nodes.js | 3 +++ .../@node-red/editor-client/src/js/ui/view.js | 19 +++---------------- 2 files changed, 6 insertions(+), 16 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 14f673d37..3403fee6c 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 @@ -372,6 +372,9 @@ RED.nodes = (function() { } } removedGroups = groupsByZ[id] || []; + removedGroups.forEach(function(g) { + delete groups[g.id] + }) delete groupsByZ[id]; for (n=0;n 0 /* || new_gs.length > 0*/) { + if (new_ms.length > 0) { if (mouse_position == null) { @@ -4344,18 +4343,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } - // for (i=0;i Date: Thu, 26 Mar 2020 21:00:22 +0000 Subject: [PATCH 081/366] [groups] Tidy up Info sidebar summary of group selection --- .../editor-client/locales/en-US/editor.json | 1 + .../editor-client/src/js/ui/group.js | 11 +----- .../editor-client/src/js/ui/tab-info.js | 38 ++++++++++++++++++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index e69d0b48d..296b4c6f8 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -551,6 +551,7 @@ "label": "info", "node": "Node", "type": "Type", + "group": "Group", "module": "Module", "id": "ID", "status": "Status", 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 0f2723897..256bf62c8 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 @@ -22,16 +22,7 @@ RED.group = (function() { ''+ '
            '+ - // '
            '+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // '
            '+ - '
            '+ + // '
            '+ '
            '+ ''+ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index c6e6d0023..3eada5e3f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -163,7 +163,8 @@ RED.sidebar.info = (function() { var types = { nodes:0, flows:0, - subflows:0 + subflows:0, + groups: 0 } node.forEach(function(n) { if (n.type === 'tab') { @@ -171,6 +172,8 @@ RED.sidebar.info = (function() { types.nodes += RED.nodes.filterNodes({z:n.id}).length; } else if (n.type === 'subflow') { types.subflows++; + } else if (n.type === 'group') { + types.groups++; } else { types.nodes++; } @@ -190,6 +193,9 @@ RED.sidebar.info = (function() { if (types.nodes > 0) { $('
            ').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts); } + if (types.groups > 0) { + $('
            ').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts); + } } else { // A single 'thing' selected. @@ -220,6 +226,36 @@ RED.sidebar.info = (function() { propRow = $(''+RED._("sidebar.info.status")+'').appendTo(tableBody); $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) } + } else if (node.type === "group") { + // An actual node is selected in the editor - build up its properties table + propRow = $(''+RED._("sidebar.info.group")+"").appendTo(tableBody); + RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); + if (node.name) { + propRow = $(''+RED._("common.label.name")+'').appendTo(tableBody); + $('').text(node.name).appendTo(propRow.children()[1]); + } + propRow = $(' ').appendTo(tableBody); + var typeCounts = { + nodes:0, + groups: 0 + } + var allNodes = RED.group.getNodes(node,true); + allNodes.forEach(function(n) { + if (n.type === "group") { + typeCounts.groups++; + } else { + typeCounts.nodes++ + } + }); + var counts = $('
            ').appendTo($(propRow.children()[1])); + if (typeCounts.nodes > 0) { + $('
            ').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts); + } + if (typeCounts.groups > 0) { + $('
            ').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts); + } + + } else { // An actual node is selected in the editor - build up its properties table propRow = $(''+RED._("sidebar.info.node")+"").appendTo(tableBody); From 94ef25bbb9604727a20baa0e37c4e4b84a972643 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Mar 2020 22:50:46 +0000 Subject: [PATCH 082/366] [groups] i18n group messages --- .../editor-client/locales/en-US/editor.json | 11 ++++++++++- .../@node-red/editor-client/src/js/ui/editor.js | 2 +- .../@node-red/editor-client/src/js/ui/group.js | 16 +++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 296b4c6f8..9899baf83 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -16,7 +16,9 @@ "clone": "Clone project", "cont": "Continue", "line": "Outline", - "fill": "Fill" + "fill": "Fill", + "color": "Color", + "position": "Position" }, "type": { "string": "string", @@ -320,6 +322,13 @@ "multipleInputsToSelection": "Cannot create subflow: multiple inputs to selection" } }, + "group": { + "editGroup": "Edit group: __name__", + "errors": { + "cannotCreateDiffGroups": "Cannot create group using nodes from different groups", + "cannotAddSubflowPorts": "Cannot add subflow ports to a group" + } + }, "editor": { "configEdit": "Edit", "configAdd": "Add", 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 a98fb9cd5..d3a54cbe7 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 @@ -515,7 +515,7 @@ RED.editor = (function() { var node = editStack[i]; label = node.type; if (node.type === 'group') { - label = RED._("subflow.editGroup",{name:RED.utils.sanitize(node.name||node.id)}); + label = RED._("group.editGroup",{name:RED.utils.sanitize(node.name||node.id)}); } else if (node.type === '_expression') { label = RED._("expressionEditor.title"); } else if (node.type === '_js') { 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 256bf62c8..2eb13453b 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 @@ -39,12 +39,12 @@ RED.group = (function() { '
            '+ '
            '+ ''+ - ''+ + ''+ ''+ '
            '+ '
            '+ ''+ - ''+ + ''+ ''+ '
            '+ '
            '+ @@ -340,7 +340,7 @@ RED.group = (function() { if (i === 0) { parentGroup = n.g; } else if (n.g !== parentGroup) { - RED.notify("Cannot merge nodes from different groups","error"); + RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error"); return; } } @@ -404,7 +404,7 @@ RED.group = (function() { return; } if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) { - RED.notify("Cannot add subflow ports to a group","error"); + RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error"); return; } // nodes is an array @@ -446,7 +446,7 @@ RED.group = (function() { if (!z) { z = n.z; } else if (z !== n.z) { - throw new Error("Cannot create group using nodes with different z properties") + throw new Error("Cannot add nooes with different z properties") } if (n.g) { // This is already in a group. @@ -454,13 +454,13 @@ RED.group = (function() { if (!g) { if (i!==0) { // TODO: this might be ok when merging groups - throw new Error("Cannot create group using nodes from different groups") + throw new Error(RED._("group.errors.cannotCreateDiffGroups")) } g = n.g } } if (g !== n.g) { - throw new Error("Cannot create group using nodes from different groups") + throw new Error(RED._("group.errors.cannotCreateDiffGroups")) } } // The nodes are already in a group. The assumption is they should be @@ -495,7 +495,6 @@ RED.group = (function() { markDirty(group); } function removeFromGroup(group, nodes, reparent) { - console.log('rfg',group,nodes); if (!Array.isArray(nodes)) { nodes = [nodes]; } @@ -504,7 +503,6 @@ RED.group = (function() { // TODO: DRY mergeSelection,removeSelection,... for (var i=0; i Date: Thu, 26 Mar 2020 22:51:06 +0000 Subject: [PATCH 083/366] [groups] Better ordering of group elements on the DOM --- .../@node-red/editor-client/src/js/ui/view.js | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 44948caaa..55055d036 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -554,8 +554,10 @@ RED.view = (function() { activeGroups = RED.nodes.groups(activeWorkspace)||[]; activeGroups.forEach(function(g) { if (g.g) { + g._root = g.g; g._depth = 1; } else { + g._root = g.id; g._depth = 0; } }); @@ -571,15 +573,30 @@ RED.view = (function() { g._depth = parentDepth + 1; changed = true; } - } else { - console.log("Missing group",g.g); + if (g._root !== parentGroup._root) { + g._root = parentGroup._root; + changed = true; + } } } }); } while (changed) activeGroups.sort(function(a,b) { - return a._depth - b._depth; + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } }); + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) } function generateLinkPath(origX,origY, destX, destY, sc) { @@ -4126,7 +4143,11 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }); if (addedGroups) { group.sort(function(a,b) { - return a._depth - b._depth; + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } }) } group[0].reverse(); From e5150ea012d442a73dafb6bc9b28669b86774876 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 31 Mar 2020 16:48:20 +0900 Subject: [PATCH 084/366] force redraw after node installation --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 2 +- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 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 cdf2c8796..ac5347961 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 @@ -1499,7 +1499,7 @@ RED.nodes = (function() { l.target = newNodeMap[l.target.id]; } }); - RED.view.redraw(true); + RED.view.redraw(true, true); } }); }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 44f75b481..5a7e11132 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3765,7 +3765,12 @@ RED.view = (function() { } }, - redraw: function(updateActive) { + redraw: function(updateActive, force) { + if (force) { + activeNodes = []; + activeLinks = []; + redraw(); + } if (updateActive) { updateActiveNodes(); updateSelection(); From fa8236ee2cc34167945399dbe6bfbe6a9d5fb64f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 31 Mar 2020 20:32:07 +0900 Subject: [PATCH 085/366] update for recent change of dev branch --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 7229680a5..da3f9b286 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4565,7 +4565,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (force) { activeNodes = []; activeLinks = []; - redraw(); + activeGroups = []; + _redraw(); } if (updateActive) { updateActiveNodes(); From 7fa4df082edd3411fbd27f1a2d17bd96a2e147ea Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 31 Mar 2020 15:58:51 +0100 Subject: [PATCH 086/366] Force sync redraw of view when replacing unknown nodes --- .../@node-red/editor-client/src/js/nodes.js | 7 ++++--- .../@node-red/editor-client/src/js/ui/view.js | 14 ++++++-------- 2 files changed, 10 insertions(+), 11 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 7150823c6..af60f6a30 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 @@ -1596,8 +1596,9 @@ RED.nodes = (function() { }); removeLinks.forEach(removeLink); - - RED.view.redraw(true); + // Force the redraw to be synchronous so the view updates + // *now* and removes the unknown node + RED.view.redraw(true, true); var result = importNodes(reimportList,false); var newNodeMap = {}; result[0].forEach(function(n) { @@ -1611,7 +1612,7 @@ RED.nodes = (function() { l.target = newNodeMap[l.target.id]; } }); - RED.view.redraw(true, true); + RED.view.redraw(true); } }); }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index da3f9b286..aa2d14e44 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4561,18 +4561,16 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }, updateActive: updateActiveNodes, - redraw: function(updateActive, force) { - if (force) { - activeNodes = []; - activeLinks = []; - activeGroups = []; - _redraw(); - } + redraw: function(updateActive, syncRedraw) { if (updateActive) { updateActiveNodes(); updateSelection(); } - redraw(); + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } }, focus: focusView, importNodes: importNodes, From 5da89892b487c1029232468f311937a68fbcd543 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 1 Apr 2020 14:10:35 +0100 Subject: [PATCH 087/366] [groups] Draw group selection above all other groups --- .../editor-client/src/js/ui/palette.js | 12 ++-- .../@node-red/editor-client/src/js/ui/view.js | 61 +++++++++++++------ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 706b10570..19e257e79 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -301,17 +301,17 @@ RED.palette = (function() { hoverGroup = null; activeGroup = RED.view.getActiveGroup(); if (activeGroup) { - document.getElementById(activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); + document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); } RED.view.focus(); }, stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (hoverGroup) { - document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); } if (activeGroup) { - document.getElementById(activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); + document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); } if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } @@ -323,13 +323,15 @@ RED.palette = (function() { mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!groupTimer) { groupTimer = setTimeout(function() { + mouseX /= RED.view.scale(); + mouseY /= RED.view.scale(); var group = RED.view.getGroupAtPoint(mouseX,mouseY); if (group !== hoverGroup) { if (hoverGroup) { - document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); } if (group) { - document.getElementById(group.id).classList.add("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); } hoverGroup = group; if (hoverGroup) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index aa2d14e44..0f012e5c3 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -22,9 +22,10 @@ * |- .red-ui-workspace-chart-background * |- .red-ui-workspace-chart-grid "gridLayer" * |- "groupLayer" + * |- "groupSelectLayer" * |- "linkLayer" * |- "dragGroupLayer" - * \- "nodeLayer" + * |- "nodeLayer" */ RED.view = (function() { @@ -110,6 +111,7 @@ RED.view = (function() { var gridLayer; var linkLayer; var dragGroupLayer; + var groupSelectLayer; var nodeLayer; var groupLayer; var drag_lines; @@ -265,9 +267,11 @@ RED.view = (function() { updateGrid(); groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); + drag_lines = []; RED.events.on("workspace:change",function(event) { @@ -3891,11 +3895,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } gg = dirtyGroups[gg].g; } } - // var group = dirtyGroups[d.g]; - // group.x = Math.min(group.x,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)); - // group.y = Math.min(group.y,d.y-d.h/2-25), - // group.w = Math.max(group.w, d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0) - group.x), - // group.h = Math.max(group.h, d.y+d.h/2+25 - group.y) } } }); @@ -4116,21 +4115,34 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); - group.exit().remove(); - var groupEnter = group.enter().insert("svg:g") - .attr("class", "red-ui-flow-group") + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + var addedGroups = false; groupEnter.each(function(d,i) { addedGroups = true; var g = d3.select(this); g.attr("id",d.id); - g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); - g.append('rect').classed("red-ui-flow-group-outline-select",true) + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) .attr('rx',1).attr('ry',1) .attr("x",-4) - .attr("y",-4); + .attr("y",-4) + .style("stroke","rgba(255,255,255,0.8)") + .style("stroke-width",6) + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',1).attr('ry',1) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {console.log("omd");groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {console.log("omu");groupMouseUp.call(g[0][0],d)}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); g.append('rect').classed("red-ui-flow-group-body",true) .attr('rx',1).attr('ry',1).style({ @@ -4197,16 +4209,28 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.attr("transform","translate("+d.x+","+d.y+")") - .classed("red-ui-flow-group-hovered",d.hovered); g.selectAll(".red-ui-flow-group-outline") .attr("width",d.w) .attr("height",d.h) - g.selectAll(".red-ui-flow-group-outline-select") - .attr("width",d.w+8) - .attr("height",d.h+8) - .style("stroke-opacity",(d.selected)?0.8:0) - .style("stroke-dasharray", (d.active)?"10 4":"none") + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; g.selectAll(".red-ui-flow-group-body") @@ -4250,7 +4274,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } }) - } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( From fbfc74e5ca0acea4e5be010b3353708707c5c8af Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 11:57:46 +0100 Subject: [PATCH 088/366] [groups] Ensure newly imported nodes have width/height --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 0f012e5c3..db4fed72e 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4362,6 +4362,9 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } node.n.moved = true; node.n.x -= dx - mouse_position[0]; node.n.y -= dy - mouse_position[1]; + node.n.w = node_width; + node.n.h = node_height; + node.n.resize = true; node.dx = node.n.x - mouse_position[0]; node.dy = node.n.y - mouse_position[1]; if (node.n.type === "group") { From 24f70009188bdc9e531ba6f050791dcc56f218ce Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 23:23:41 +0100 Subject: [PATCH 089/366] [groups] Remove padStart because IE11 --- .../@node-red/editor-client/src/js/ui/common/colorPicker.js | 3 ++- .../node_modules/@node-red/editor-client/src/js/ui/group.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js index 47f4cfed2..0c7944c84 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -17,7 +17,8 @@ RED.colorPicker = (function() { r = Math.max(0,r-50); g = Math.max(0,g-50); b = Math.max(0,b-50); - return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0') + var s = ((r<<16) + (g<<8) + b).toString(16); + return '#'+'000000'.slice(0, 6-s.length)+s; } function create(options) { 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 2eb13453b..22c168e22 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 @@ -78,7 +78,8 @@ RED.group = (function() { r = Math.min(255,Math.floor(r+j*dr)); g = Math.min(255,Math.floor(g+j*dg)); b = Math.min(255,Math.floor(b+j*db)); - colorPalette.push('#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0')); + var s = ((r<<16) + (g<<8) + b).toString(16); + colorPalette.push('#'+'000000'.slice(0, 6-s.length)+s); } var defaultGroupStyle = {}; @@ -207,7 +208,8 @@ RED.group = (function() { function convertColorToHex(c) { var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c); if (m) { - return "#"+(((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).padStart(6,'0')) + var s = ((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16) + return '#'+'000000'.slice(0, 6-s.length)+s; } return c; } From 4f3163286318010fb3fd0915ab8f990c92f79cbe Mon Sep 17 00:00:00 2001 From: tmdoit Date: Fri, 3 Apr 2020 16:10:33 +0200 Subject: [PATCH 090/366] Fix: Allow CR and LF control chars to be a part of the value (#2526) To properly parse CSV data. --- packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 0424e1e9b..fe39f4c59 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -187,7 +187,7 @@ module.exports = function(RED) { // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",' k[j] = line.length - 1 === i ? null : ""; } - else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines + else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines //console.log(j,k,o,k[j]); if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if ( node.template[j] && (node.template[j] !== "") ) { From e969a1c97cf02a0740aca4294d96278b74ed22d1 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 3 Apr 2020 15:54:19 +0100 Subject: [PATCH 091/366] Let CSV node only send headers once (and then reset that on msg.reset) and also accept msg.columns csv string to set column headers if not specified in node. And Add tests --- .../@node-red/nodes/core/parsers/70-CSV.html | 15 +++- .../@node-red/nodes/core/parsers/70-CSV.js | 32 +++++-- .../nodes/locales/en-US/messages.json | 5 ++ .../nodes/locales/en-US/parsers/70-CSV.html | 4 +- test/nodes/core/parsers/70-CSV_spec.js | 86 +++++++++++++------ 5 files changed, 106 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html index c7e8fdc58..80778ac1b 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html @@ -28,7 +28,7 @@
            -   
            +   

            @@ -49,8 +49,13 @@
            - -
            @@ -73,7 +78,7 @@ sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)}, //quo: {value:'"',required:true}, hdrin: {value:""}, - hdrout: {value:""}, + hdrout: {value:"none"}, multi: {value:"one",required:true}, ret: {value:'\\n'}, temp: {value:""}, @@ -92,6 +97,8 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); } + if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");} if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); } if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");} $("#node-input-skip").spinner({ min:0 }); diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 0424e1e9b..55db3d964 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -26,7 +26,7 @@ module.exports = function(RED) { this.lineend = "\n"; this.multi = n.multi || "one"; this.hdrin = n.hdrin || false; - this.hdrout = n.hdrout || false; + this.hdrout = n.hdrout || "none"; this.goodtmpl = true; this.skip = parseInt(n.skip || 0); this.store = []; @@ -34,6 +34,8 @@ module.exports = function(RED) { this.include_empty_strings = n.include_empty_strings || false; this.include_null_values = n.include_null_values || false; if (this.parsestrings === undefined) { this.parsestrings = true; } + if (this.hdrout === false) { this.hdrout = "none"; } + if (this.hdrout === true) { this.hdrout = "all"; } var tmpwarn = true; var node = this; @@ -51,14 +53,22 @@ module.exports = function(RED) { return col; } node.template = clean(node.template); + node.hdrSent = false; this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + node.hdrSent = false; + } if (msg.hasOwnProperty("payload")) { if (typeof msg.payload == "object") { // convert object to CSV string try { var ou = ""; - if (node.hdrout) { + if (node.hdrout !== "none" && node.hdrSent === false) { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } ou += node.template.join(node.sep) + node.ret; + if (node.hdrout === "once") { node.hdrSent = true; } } if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } for (var s = 0; s < msg.payload.length; s++) { @@ -77,13 +87,15 @@ module.exports = function(RED) { ou += msg.payload[s].join(node.sep) + node.ret; } else { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } if ((node.template.length === 1) && (node.template[0] === '')) { /* istanbul ignore else */ if (tmpwarn === true) { // just warn about missing template once node.warn(RED._("csv.errors.obj_csv")); tmpwarn = false; } - ou = ""; for (var p in msg.payload[0]) { /* istanbul ignore else */ if (msg.payload[0].hasOwnProperty(p)) { @@ -127,6 +139,7 @@ module.exports = function(RED) { } } msg.payload = ou; + msg.columns = node.template.join(','); if (msg.payload !== '') { node.send(msg); } } catch(e) { node.error(e,msg); } @@ -187,7 +200,7 @@ module.exports = function(RED) { // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",' k[j] = line.length - 1 === i ? null : ""; } - else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines + else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines //console.log(j,k,o,k[j]); if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if ( node.template[j] && (node.template[j] !== "") ) { @@ -227,6 +240,7 @@ module.exports = function(RED) { a.push(o); // add to the array } var has_parts = msg.hasOwnProperty("parts"); + if (node.multi !== "one") { msg.payload = a; if (has_parts) { @@ -235,12 +249,15 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store; + console.log(node.template) + msg.columns = node.template.filter(val => val).join(','); delete msg.parts; node.send(msg); node.store = []; } } else { + msg.columns = node.template.filter(val => val).join(','); node.send(msg); // finally send the array } } @@ -248,6 +265,7 @@ module.exports = function(RED) { var len = a.length; for (var i = 0; i < len; i++) { var newMessage = RED.util.cloneMessage(msg); + newMessage.columns = node.template.filter(val => val).join(','); newMessage.payload = a[i]; if (!has_parts) { newMessage.parts = { @@ -273,7 +291,11 @@ module.exports = function(RED) { } else { node.warn(RED._("csv.errors.csv_js")); } } - else { node.send(msg); } // If no payload - just pass it on. + else { + if (!msg.hasOwnProperty("reset")) { + node.send(msg); // If no payload and not reset - just pass it on. + } + } }); } RED.nodes.registerType("csv",CSVNode); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index b5f4474d3..6806f5f21 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -723,6 +723,11 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "never send column headers", + "all": "always send column headers", + "once": "send headers once, until msg.reset" + }, "errors": { "csv_js": "This node only handles CSV strings or js objects.", "obj_csv": "No columns template specified for object -> CSV." diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html index 19ece5ac0..6f16f5b72 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html @@ -38,11 +38,13 @@

            The column template can contain an ordered list of column names. When converting CSV to an object, the column names will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.

            When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.

            +

            If the template is blank then the node can use a simple comma separated list of properties supplied in msg.columns to + determine what to extract. If that is not present then all the object properties are ouput in the order in which they are found.

            If the input is an array then the columns template is only used to optionally generate a row of column titles.

            If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.

            If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.

            If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.

            -

            The node can accept a multi-part input as long as the parts property is set correctly.

            +

            The node can accept a multi-part input as long as the parts property is set correctly, for example from a file-in node or split node.

            If outputting multiple messages they will have their parts property set and form a complete message sequence.

            Note: the column template must be comma separated - even if a different separator is chosen for the data.

            diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 3917d2d7c..9e7b45d8f 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** * Copyright JS Foundation and other contributors, http://js.foundation * @@ -70,12 +71,13 @@ describe('CSV node', function() { it('should convert a simple csv string to a javascript object', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); + msg.should.have.property('columns', "a,b,c,d"); check_parts(msg, 0, 1); done(); }); @@ -86,7 +88,7 @@ describe('CSV node', function() { it('should remove quotes and whitespace from template', function(done) { var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -102,12 +104,13 @@ describe('CSV node', function() { it('should create column names if no template provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 }); + msg.should.have.property('columns', "col1,col2,col3,col4"); check_parts(msg, 0, 1); done(); }); @@ -118,12 +121,13 @@ describe('CSV node', function() { it('should allow dropping of fields from the template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, d: 4 }); + msg.should.have.property('columns', 'a,d'); check_parts(msg, 0, 1); done(); }); @@ -134,7 +138,7 @@ describe('CSV node', function() { it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -150,7 +154,7 @@ describe('CSV node', function() { it('should not parse numbers when told not to do so', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -166,7 +170,7 @@ describe('CSV node', function() { it('should leave handle strings with scientific notation as numbers', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -183,7 +187,7 @@ describe('CSV node', function() { it('should allow quotes in the input (but drop blank strings)', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -234,7 +238,7 @@ describe('CSV node', function() { it('should recover from an odd number of quotes in the input', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -277,12 +281,13 @@ describe('CSV node', function() { it('should be able to output multiple lines as one array', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]); + msg.should.have.property('columns','a,b,c,d'); msg.should.not.have.property('parts'); done(); }); @@ -293,7 +298,7 @@ describe('CSV node', function() { it('should handle numbers in strings but not IP addresses', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -309,7 +314,7 @@ describe('CSV node', function() { it('should preserve parts property', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -353,7 +358,7 @@ describe('CSV node', function() { it('should skip several lines from start if requested', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -367,9 +372,9 @@ describe('CSV node', function() { }); }); - it('should skip several lines from start then use next line as a tempate', function(done) { + it('should skip several lines from start then use next line as a template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -385,7 +390,7 @@ describe('CSV node', function() { it('should skip several lines from start and correct parts', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -417,11 +422,13 @@ describe('CSV node', function() { n2.on("input", function(msg) { if (c === 0) { msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 0, 2); c += 1; } else { msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 1, 2); done(); } @@ -445,7 +452,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -463,7 +470,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv with no template', function(done) { var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -481,7 +488,7 @@ describe('CSV node', function() { it('should handle a template with spaces in the property names', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -499,7 +506,7 @@ describe('CSV node', function() { it('should convert an array of objects to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -517,7 +524,7 @@ describe('CSV node', function() { it('should convert a simple array back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -535,7 +542,7 @@ describe('CSV node', function() { it('should convert an array of arrays back to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -553,7 +560,7 @@ describe('CSV node', function() { it('should be able to include column names as first row', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -569,9 +576,36 @@ describe('CSV node', function() { }); }); + it('should be able to pass in column names', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var count = 0; + n2.on("input", function(msg) { + count += 1; + try { + if (count === 1) { + msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n'); + } + if (count === 3) { + msg.should.have.property('payload', '4,,3,4\r\n'); + done() + } + } + catch(e) { done(e); } + }); + var testJson = [{ d: 1, b: 3, c: 2, a: 4 }]; + n1.emit("input", {payload:testJson, columns:"a,,b,a"}); + n1.emit("input", {payload:testJson}); + n1.emit("input", {payload:testJson}); + }); + }); + it('should handle quotes and sub-properties', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -591,7 +625,7 @@ describe('CSV node', function() { it('should just pass through if no payload provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -611,7 +645,7 @@ describe('CSV node', function() { it('should warn if provided a number or boolean', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); From 24eb78d1371bf1ada68ebaa3556376fb92250943 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 3 Apr 2020 16:55:43 +0100 Subject: [PATCH 092/366] add ja translations --- .../node_modules/@node-red/nodes/locales/ja/messages.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index e15947fa6..92f0b1677 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -719,6 +719,11 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "カラムヘッダを送信しない", + "all": "カラムヘッダを常に送信する", + "once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)" + }, "errors": { "csv_js": "本ノードが処理できる形式は、CSV文字列またはJSONのみです", "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません" From efad7270b7eebf97522200eab38457495ea7245a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 3 Apr 2020 16:56:46 +0100 Subject: [PATCH 093/366] Add polyfills for IE11 --- Gruntfile.js | 1 + .../editor-client/src/js/polyfills.js | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/polyfills.js diff --git a/Gruntfile.js b/Gruntfile.js index b2da4a514..b6474182a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -125,6 +125,7 @@ module.exports = function(grunt) { src: [ // Ensure editor source files are concatenated in // the right order + "packages/node_modules/@node-red/editor-client/src/js/polyfills.js", "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", "packages/node_modules/@node-red/editor-client/src/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/events.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js new file mode 100644 index 000000000..1a7caf121 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js @@ -0,0 +1,34 @@ +(function() { + var isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + + if (isIE11) { + // IE11 does not provide classList on SVGElements + if (! ("classList" in SVGElement.prototype)) { + Object.defineProperty(SVGElement.prototype, 'classList', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'classList')); + } + + // IE11 does not provide children on SVGElements + if (! ("children" in SVGElement.prototype)) { + Object.defineProperty(SVGElement.prototype, 'children', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'children')); + } + + if (!Array.from) { + // JSONata provides an Array.from polyfill that doesn't handle iterables. + // So in IE11 we expect Array.from to exist already, it just needs some + // changes to support iterables. + throw new Error("Missing Array.from base polyfill"); + } + Array._from = Array.from; + Array.from = function() { + var arrayLike = arguments[0] + if (arrayLike.forEach) { + var result = []; + arrayLike.forEach(function(i) { + result.push(i); + }) + return result; + } + return Array._from.apply(null,arguments); + } + } +})(); From 161f6090c1c5cd0f19cb1e2f3c5073212a5c8d85 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 6 Apr 2020 16:34:41 +0900 Subject: [PATCH 094/366] update initialize & finalize processing of function node --- .../editor-client/src/js/ui/library.js | 28 +- .../nodes/core/function/10-function.html | 90 +++--- .../nodes/core/function/10-function.js | 272 ++++++++++++------ .../lib/storage/localfilesystem/library.js | 15 +- 4 files changed, 248 insertions(+), 157 deletions(-) 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 2520c16a9..06e320f32 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 @@ -50,18 +50,6 @@ RED.library = (function() { ''+ '
            ' - function toSingleLine(text) { - var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n"); - return result; - } - - function fromSingleLine(text) { - var result = text.replace(/\\[\\n]/g, function(s) { - return ((s === "\\\\") ? "\\" : "\n"); - }); - return result; - } - function saveToLibrary() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; var name = $("#"+elementPrefix+"name").val().trim(); @@ -80,10 +68,8 @@ RED.library = (function() { var field = activeLibrary.fields[i]; if (field == "name") { data.name = name; - } else if(field == "initialize") { - data.initialize = toSingleLine(activeLibrary.initEditor.getValue()); - } else if(field == "finalize") { - data.finalize = toSingleLine(activeLibrary.finalizeEditor.getValue()); + } else if (typeof(field) === 'object') { + data[field.name] = field.get(); } else { data[field] = $("#"+elementPrefix+field).val(); } @@ -539,13 +525,9 @@ RED.library = (function() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; for (var i=0; i
            -