From b761904424fa99300a0259cc6205ff4f2a91c7ba Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 22 May 2018 15:48:24 +0100 Subject: [PATCH 01/17] let Pi nodes be visible/editable on all platforms even where they are not physically available. --- nodes/core/hardware/36-rpi-gpio.js | 343 ++++++++++++++----------- nodes/core/locales/en-US/messages.json | 2 +- 2 files changed, 189 insertions(+), 156 deletions(-) diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js index cc1a8ab2a..40c3d0f9b 100644 --- a/nodes/core/hardware/36-rpi-gpio.js +++ b/nodes/core/hardware/36-rpi-gpio.js @@ -6,35 +6,36 @@ module.exports = function(RED) { var fs = require('fs'); var gpioCommand = __dirname+'/nrgpio'; + var allOK = true; try { var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); - if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); } - } catch(err) { - throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); - } - - try { - fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian - // /usr/lib/python2.7/dist-packages/RPi/GPIO - } catch(err) { + if (cpuinfo.indexOf(": BCM") === -1) { + allOK = false; + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode")); + } try { - fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch - } - catch(err) { + fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian + // /usr/lib/python2.7/dist-packages/RPi/GPIO + } catch(err) { try { - fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot - } - catch(err) { - RED.log.warn(RED._("rpi-gpio.errors.libnotfound")); - throw "Warning : "+RED._("rpi-gpio.errors.libnotfound"); + fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch + } catch(err) { + try { + fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot + } catch(err) { + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound")); + allOK = false; + } } } - } - - if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) { - RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand})); - throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable"); + if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) { + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand})); + allOK = false; + } + } catch(err) { + allOK = false; + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode")); } // the magic to make python print stuff immediately @@ -61,48 +62,62 @@ module.exports = function(RED) { } } - if (node.pin !== undefined) { - node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]); - node.running = true; - node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + if (allOK === true) { + if (node.pin !== undefined) { + node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]); + node.running = true; + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); - node.child.stdout.on('data', function (data) { - var d = data.toString().trim().split("\n"); - for (var i = 0; i < d.length; i++) { - if (d[i] === '') { return; } - if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) { - node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) }); + node.child.stdout.on('data', function (data) { + var d = data.toString().trim().split("\n"); + for (var i = 0; i < d.length; i++) { + if (d[i] === '') { return; } + if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) { + node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) }); + } + node.buttonState = d[i]; + node.status({fill:"green",shape:"dot",text:d[i]}); + if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); } } - node.buttonState = d[i]; - node.status({fill:"green",shape:"dot",text:d[i]}); - if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); } - } - }); + }); - node.child.stderr.on('data', function (data) { - if (RED.settings.verbose) { node.log("err: "+data+" :"); } - }); + node.child.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); - node.child.on('close', function (code) { - node.running = false; - node.child = null; - if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } - if (node.done) { - node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); - node.done(); - } - else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } - }); + node.child.on('close', function (code) { + node.running = false; + node.child = null; + if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); - node.child.on('error', function (err) { - if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } - else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } - else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) } - }); + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) } + }); + } + else { + node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + } } else { - node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + if (node.read === true) { + var val; + if (node.intype == "up") { val = 1; } + if (node.intype == "down") { val = 0; } + setTimeout(function(){ + node.send({ topic:"pi/"+node.pin, payload:val }); + node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})}); + },250); + } } node.on("close", function(done) { @@ -155,20 +170,83 @@ module.exports = function(RED) { else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); } } - if (node.pin !== undefined) { - if (node.set && (node.out === "out")) { - node.child = spawn(gpioCommand, [node.out,node.pin,node.level]); - node.status({fill:"green",shape:"dot",text:node.level}); - } else { - node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]); - node.status({fill:"green",shape:"dot",text:"common.status.ok"}); - } - node.running = true; + if (allOK === true) { + if (node.pin !== undefined) { + if (node.set && (node.out === "out")) { + node.child = spawn(gpioCommand, [node.out,node.pin,node.level]); + node.status({fill:"green",shape:"dot",text:node.level}); + } else { + node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + } + node.running = true; - node.on("input", inputlistener); + node.on("input", inputlistener); + + node.child.stdout.on('data', function (data) { + if (RED.settings.verbose) { node.log("out: "+data+" :"); } + }); + + node.child.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); + + node.child.on('close', function (code) { + node.child = null; + node.running = false; + if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); + + } + else { + node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + } + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + node.on("input", function(msg){ + node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})}); + }); + } + + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + delete pinsInUse[node.pin]; + if (node.child != null) { + node.done = done; + node.child.stdin.write("close "+node.pin); + node.child.kill('SIGKILL'); + } + else { done(); } + }); + + } + RED.nodes.registerType("rpi-gpio out",GPIOOutNode); + + function PiMouseNode(n) { + RED.nodes.createNode(this,n); + this.butt = n.butt || 7; + var node = this; + + if (allOK === true) { + node.child = spawn(gpioCommand+".py", ["mouse",node.butt]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); node.child.stdout.on('data', function (data) { - if (RED.settings.verbose) { node.log("out: "+data+" :"); } + data = Number(data); + if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); } + else { node.send({ topic:"pi/mouse", button:data, payload:0 }); } }); node.child.stderr.on('data', function (data) { @@ -192,69 +270,19 @@ module.exports = function(RED) { else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } }); + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + if (node.child != null) { + node.done = done; + node.child.kill('SIGINT'); + node.child = null; + } + else { done(); } + }); } else { - node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); } - - node.on("close", function(done) { - node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); - delete pinsInUse[node.pin]; - if (node.child != null) { - node.done = done; - node.child.stdin.write("close "+node.pin); - node.child.kill('SIGKILL'); - } - else { done(); } - }); - - } - RED.nodes.registerType("rpi-gpio out",GPIOOutNode); - - function PiMouseNode(n) { - RED.nodes.createNode(this,n); - this.butt = n.butt || 7; - var node = this; - - node.child = spawn(gpioCommand+".py", ["mouse",node.butt]); - node.status({fill:"green",shape:"dot",text:"common.status.ok"}); - - node.child.stdout.on('data', function (data) { - data = Number(data); - if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); } - else { node.send({ topic:"pi/mouse", button:data, payload:0 }); } - }); - - node.child.stderr.on('data', function (data) { - if (RED.settings.verbose) { node.log("err: "+data+" :"); } - }); - - node.child.on('close', function (code) { - node.child = null; - node.running = false; - if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } - if (node.done) { - node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); - node.done(); - } - else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } - }); - - node.child.on('error', function (err) { - if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } - else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } - else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } - }); - - node.on("close", function(done) { - node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); - if (node.child != null) { - node.done = done; - node.child.kill('SIGINT'); - node.child = null; - } - else { done(); } - }); } RED.nodes.registerType("rpi-mouse",PiMouseNode); @@ -262,39 +290,40 @@ module.exports = function(RED) { RED.nodes.createNode(this,n); var node = this; - node.child = spawn(gpioCommand+".py", ["kbd","0"]); - node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + if (allOK === true) { + node.child = spawn(gpioCommand+".py", ["kbd","0"]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); - node.child.stdout.on('data', function (data) { - var b = data.toString().trim().split(","); - var act = "up"; - if (b[1] === "1") { act = "down"; } - if (b[1] === "2") { act = "repeat"; } - node.send({ topic:"pi/key", payload:Number(b[0]), action:act }); - }); + node.child.stdout.on('data', function (data) { + var b = data.toString().trim().split(","); + var act = "up"; + if (b[1] === "1") { act = "down"; } + if (b[1] === "2") { act = "repeat"; } + node.send({ topic:"pi/key", payload:Number(b[0]), action:act }); + }); - node.child.stderr.on('data', function (data) { - if (RED.settings.verbose) { node.log("err: "+data+" :"); } - }); + node.child.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); - node.child.on('close', function (code) { - node.running = false; - node.child = null; - if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } - if (node.done) { - node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); - node.done(); - } - else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } - }); + node.child.on('close', function (code) { + node.running = false; + node.child = null; + if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); - node.child.on('error', function (err) { - if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } - else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } - else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } - }); + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); - node.on("close", function(done) { + node.on("close", function(done) { node.status({}); if (node.child != null) { node.done = done; @@ -303,6 +332,10 @@ module.exports = function(RED) { } else { done(); } }); + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + } } RED.nodes.registerType("rpi-keyboard",PiKeyboardNode); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 74ec0bcd4..15d66c9dc 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -787,7 +787,7 @@ }, "errors": { "ignorenode": "Ignoring Raspberry Pi specific node", - "version": "Version command failed", + "version": "Failed to get version from Pi", "sawpitype": "Saw Pi Type", "libnotfound": "Cannot find Pi RPi.GPIO python library", "alreadyset": "GPIO pin __pin__ already set as type: __type__", From 7dd329b5ee589f9477e7a2e838e3cdbb066b24a9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 22 May 2018 17:26:52 +0100 Subject: [PATCH 02/17] Add basic loading tests for GPIO nodes --- nodes/core/hardware/36-rpi-gpio.js | 26 +++--- nodes/core/locales/en-US/messages.json | 2 +- test/nodes/core/hardware/36-rpi-gpio_spec.js | 90 ++++++++++++++++++++ 3 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 test/nodes/core/hardware/36-rpi-gpio_spec.js diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js index 40c3d0f9b..0a21f578a 100644 --- a/nodes/core/hardware/36-rpi-gpio.js +++ b/nodes/core/hardware/36-rpi-gpio.js @@ -340,20 +340,22 @@ module.exports = function(RED) { RED.nodes.registerType("rpi-keyboard",PiKeyboardNode); var pitype = { type:"" }; - exec(gpioCommand+" info", function(err,stdout,stderr) { - if (err) { - RED.log.info(RED._("rpi-gpio.errors.version")); - } - else { - try { - var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") ); - pitype.type = info["TYPE"]; + if (allOK === true) { + exec(gpioCommand+" info", function(err,stdout,stderr) { + if (err) { + RED.log.info(RED._("rpi-gpio.errors.version")); } - catch(e) { - RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); + else { + try { + var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") ); + pitype.type = info["TYPE"]; + } + catch(e) { + RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); + } } - } - }); + }); + } RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) { res.json(pitype); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 15d66c9dc..d59f3d1bf 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -786,7 +786,7 @@ "na": "N/A : __value__" }, "errors": { - "ignorenode": "Ignoring Raspberry Pi specific node", + "ignorenode": "Raspberry Pi specific node set inactive", "version": "Failed to get version from Pi", "sawpitype": "Saw Pi Type", "libnotfound": "Cannot find Pi RPi.GPIO python library", diff --git a/test/nodes/core/hardware/36-rpi-gpio_spec.js b/test/nodes/core/hardware/36-rpi-gpio_spec.js new file mode 100644 index 000000000..3933154c0 --- /dev/null +++ b/test/nodes/core/hardware/36-rpi-gpio_spec.js @@ -0,0 +1,90 @@ +/** + * 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. + **/ + +var should = require("should"); +var rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js"); +var helper = require("node-red-node-test-helper"); +var fs = require("fs"); + +describe('RPI GPIO Node', function() { + + before(function(done) { + helper.startServer(done); + }); + + after(function(done) { + helper.stopServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + var checkIgnore = function(done) { + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0)); + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode"); + done(); + } catch(err) { + done(err); + } + },25); + } + + it('should load Input node', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }]; + helper.load(rpi, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'rpi-gpio in'); + try { + var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); + if (cpuinfo.indexOf(": BCM") === 1) { + done(); // It's ON a PI ... should really do more tests ! + } else { + checkIgnore(done); + } + } + catch(e) { + checkIgnore(done); + } + }); + }); + + it('should load Output node', function(done) { + var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }]; + helper.load(rpi, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'rpi-gpio out'); + try { + var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); + if (cpuinfo.indexOf(": BCM") === 1) { + done(); // It's ON a PI ... should really do more tests ! + } else { + checkIgnore(done); + } + } + catch(e) { + checkIgnore(done); + } + }); + }); + +}); From 1d05b4c9814c656c2be9683d88eac4efae96eb41 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 23 May 2018 08:58:04 +0100 Subject: [PATCH 03/17] relax test spec slightly --- test/nodes/core/hardware/36-rpi-gpio_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/nodes/core/hardware/36-rpi-gpio_spec.js b/test/nodes/core/hardware/36-rpi-gpio_spec.js index 3933154c0..c11ea254c 100644 --- a/test/nodes/core/hardware/36-rpi-gpio_spec.js +++ b/test/nodes/core/hardware/36-rpi-gpio_spec.js @@ -39,7 +39,6 @@ describe('RPI GPIO Node', function() { var logEvents = helper.log().args.filter(function(evt) { return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0)); }); - logEvents.should.have.length(1); logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode"); done(); From ab788bc1e3b9decc6e55979b3b6d7e5e1d82468a Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 1 Jun 2018 12:58:09 +0900 Subject: [PATCH 04/17] Add i18n support for projectSettings.js --- editor/js/ui/projects/projectSettings.js | 112 +++++++++++++---------- red/api/editor/locales/en-US/editor.json | 39 ++++++++ red/api/editor/locales/ja/editor.json | 38 ++++++++ 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/editor/js/ui/projects/projectSettings.js b/editor/js/ui/projects/projectSettings.js index 91dbb57ff..b5ee024a1 100644 --- a/editor/js/ui/projects/projectSettings.js +++ b/editor/js/ui/projects/projectSettings.js @@ -49,7 +49,7 @@ RED.projects.settings = (function() { var tabContainer; var trayOptions = { - title: "Project Settings",// RED._("menu.label.userSettings"),, // TODO: nls + title: RED._("menu.label.userSettings"), buttons: [ { id: "node-dialog-ok", @@ -173,14 +173,14 @@ RED.projects.settings = (function() { container.empty(); var bg = $('').appendTo(container); var input = $('').val(summary||"").appendTo(container); - $('') + $('') .appendTo(bg) .click(function(evt) { evt.preventDefault(); updateProjectSummary(activeProject.summary, container); editButton.show(); }); - $('') + $('') .appendTo(bg) .click(function(evt) { evt.preventDefault(); @@ -223,7 +223,7 @@ RED.projects.settings = (function() { if (summary) { container.text(summary).removeClass('node-info-node'); } else { - container.text("No summary available").addClass('node-info-none');// TODO: nls + container.text(RED._("sidebar.project.projectSettings.noSummaryAvailable")).addClass('node-info-none'); } } @@ -235,7 +235,8 @@ RED.projects.settings = (function() { var summaryContent = $('
',{style:"color: #999"}).appendTo(summary); updateProjectSummary(activeProject.summary, summaryContent); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .prependTo(summary) .click(function(evt) { evt.preventDefault(); @@ -250,7 +251,8 @@ RED.projects.settings = (function() { updateProjectDescription(activeProject, descriptionContent); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .prependTo(description) .click(function(evt) { evt.preventDefault(); @@ -316,7 +318,7 @@ RED.projects.settings = (function() { // depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls // } if (totalCount === 0) { - depsList.editableList('addItem',{index:0, label:"None"}); // TODO: nls + depsList.editableList('addItem',{index:0, label:RED._("sidebar.project.projectSettings.none")}); } } @@ -381,7 +383,8 @@ RED.projects.settings = (function() { function createDependenciesPane(activeProject) { var pane = $('
'); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .appendTo(pane) .click(function(evt) { evt.preventDefault(); @@ -451,7 +454,8 @@ RED.projects.settings = (function() { var buttons = $('
').appendTo(metaRow); if (RED.user.hasPermission("projects.write")) { if (!entry.installed && RED.settings.theme('palette.editable') !== false) { - $('install').appendTo(buttons) + $('' + + RED._("sidebar.project.projectSettings.install") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); RED.palette.editor.install(entry,row,function(err) { @@ -468,7 +472,8 @@ RED.projects.settings = (function() { }); }) } else if (entry.known && entry.count === 0) { - $('remove from project').appendTo(buttons) + $('' + + RED._("sidebar.project.projectSettings.removeFromProject") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); @@ -484,7 +489,8 @@ RED.projects.settings = (function() { }); }); } else if (!entry.known) { - $('add to project').appendTo(buttons) + $('' + + RED._("sidebar.project.projectSettings.addToProject") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); @@ -723,10 +729,11 @@ RED.projects.settings = (function() { // } function createFilesSection(activeProject,pane) { - var title = $('

').text("Files").appendTo(pane); + var title = $('

').text(RED._("sidebar.project.projectSettings.files")).appendTo(pane); var filesContainer = $('').appendTo(pane); if (RED.user.hasPermission("projects.write")) { - var editFilesButton = $('') + var editFilesButton = $('') .appendTo(title) .click(function(evt) { evt.preventDefault(); @@ -750,7 +757,7 @@ RED.projects.settings = (function() { // Flow files row = $('').appendTo(filesContainer); - $('').text('Flow').appendTo(row); + $('').text(RED._("sidebar.project.projectSettings.flow")).appendTo(row); var flowFileLabel = $('
').appendTo(row); var flowFileLabelText = $('').text(activeProject.files.flow).appendTo(flowFileLabel); @@ -787,7 +794,7 @@ RED.projects.settings = (function() { }) row = $('').appendTo(filesContainer); - $('').text('Credentials').appendTo(row); + $('').text(RED._("sidebar.project.projectSettings.credentials")).appendTo(row); var credFileLabel = $('
').text(activeProject.files.credentials).appendTo(row); var credFileInput = $('
').text(activeProject.files.credentials).hide().insertAfter(credFileLabel); @@ -899,12 +906,15 @@ RED.projects.settings = (function() { var credentialFormRows = $('
',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel); - var credentialSetLabel = $('
Set the encryption key:
').hide().appendTo(credentialFormRows); - var credentialChangeLabel = $('
Change the encryption key:
').hide().appendTo(credentialFormRows); - var credentialResetLabel = $('
Reset the encryption key:
').hide().appendTo(credentialFormRows); + var credentialSetLabel = $('
' + + RED._("sidebar.project.projectSettings.setTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); + var credentialChangeLabel = $('
' + + RED._("sidebar.project.projectSettings.changeTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); + var credentialResetLabel = $('
' + + RED._("sidebar.project.projectSettings.resetTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); var credentialSecretExistingRow = $('').appendTo(credentialFormRows); - $('').text('Current key').appendTo(credentialSecretExistingRow); + $('').text(RED._("sidebar.project.projectSettings.currentKey")).appendTo(credentialSecretExistingRow); var credentialSecretExistingInput = $('').appendTo(credentialSecretExistingRow) .on("change keyup paste",function() { if (popover) { @@ -917,10 +927,11 @@ RED.projects.settings = (function() { var credentialSecretNewRow = $('').appendTo(credentialFormRows); - $('').text('New key').appendTo(credentialSecretNewRow); + $('').text(RED._("sidebar.project.projectSettings.newKey")).appendTo(credentialSecretNewRow); var credentialSecretNewInput = $('').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles); - var credentialResetWarning = $('
This will delete all existing credentials
').hide().appendTo(credentialFormRows); + var credentialResetWarning = $('
' + + RED._("sidebar.project.projectSettings.credentialsAlert") + '
').hide().appendTo(credentialFormRows); var hideEditForm = function() { @@ -950,13 +961,13 @@ RED.projects.settings = (function() { } var formButtons = $('').hide().appendTo(filesContainer); - $('') + $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); hideEditForm(); }); - var saveButton = $('') + var saveButton = $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); @@ -1032,13 +1043,13 @@ RED.projects.settings = (function() { var updateForm = function() { if (activeProject.settings.credentialSecretInvalid) { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning"); - credentialStateLabel.find(".user-settings-credentials-state").text("Invalid encryption key"); + credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.invalidEncryptionKey")); } else if (activeProject.settings.credentialsEncrypted) { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock"); - credentialStateLabel.find(".user-settings-credentials-state").text("Encryption enabled"); + credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionEnabled")); } else { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock"); - credentialStateLabel.find(".user-settings-credentials-state").text("Encryption disabled"); + credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionDisabled")); } credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); @@ -1050,7 +1061,7 @@ RED.projects.settings = (function() { function createLocalBranchListSection(activeProject,pane) { var localBranchContainer = $('').appendTo(pane); - $('

').text("Branches").appendTo(localBranchContainer); + $('

').text(RED._("sidebar.project.projectSettings.branches")).appendTo(localBranchContainer); var row = $('').appendTo(localBranchContainer); @@ -1063,7 +1074,7 @@ RED.projects.settings = (function() { var container = $('
').appendTo(row); if (entry.empty) { container.addClass('red-ui-search-empty'); - container.text("No branches"); + container.text(RED._("sidebar.project.projectSettings.noBranches")); return; } if (entry.current) { @@ -1095,7 +1106,7 @@ RED.projects.settings = (function() { .click(function(e) { e.preventDefault(); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); - var notification = RED.notify("Are you sure you want to delete the local branch '"+entry.name+"'? This cannot be undone.", { + var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteConfirm", { name: entry.name }), { type: "warning", modal: true, fixed: true, @@ -1123,7 +1134,7 @@ RED.projects.settings = (function() { }, 400: { 'git_delete_branch_unmerged': function(error) { - notification = RED.notify("The local branch '"+entry.name+"' has unmerged changes that will be lost. Are you sure you want to delete it?", { + notification = RED.notify(RED._("sidebar.project.projectSettings.unmergedConfirm", { name: entry.name }), { type: "warning", modal: true, fixed: true, @@ -1135,7 +1146,7 @@ RED.projects.settings = (function() { notification.close(); } },{ - text: 'Delete unmerged branch', + text: RED._("sidebar.project.projectSettings.deleteUnmergedBranch"), click: function() { options.url += "?force=true"; notification.close(); @@ -1183,14 +1194,15 @@ RED.projects.settings = (function() { } function createRemoteRepositorySection(activeProject,pane) { - $('

').text("Version Control").appendTo(pane); + $('

').text(RED._("sidebar.project.projectSettings.versionControl")).appendTo(pane); createLocalBranchListSection(activeProject,pane); var repoContainer = $('').appendTo(pane); - var title = $('

').text("Git remotes").appendTo(repoContainer); + var title = $('

').text(RED._("sidebar.project.projectSettings.gitRemotes")).appendTo(repoContainer); - var editRepoButton = $('') + var editRepoButton = $('') .appendTo(title) .click(function(evt) { editRepoButton.attr('disabled',true); @@ -1221,7 +1233,7 @@ RED.projects.settings = (function() { var container = $('
').appendTo(row); if (entry.empty) { container.addClass('red-ui-search-empty'); - container.text("No remotes"); + container.text(RED._("sidebar.project.projectSettings.noRemotes")); return; } else { $('').appendTo(container); @@ -1240,7 +1252,7 @@ RED.projects.settings = (function() { .click(function(e) { e.preventDefault(); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); - var notification = RED.notify("Are you sure you want to delete the remote '"+entry.name+"'?", { + var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteRemoteConfrim", { name: entry.name }), { type: "warning", modal: true, fixed: true, @@ -1252,7 +1264,7 @@ RED.projects.settings = (function() { notification.close(); } },{ - text: 'Delete remote', + text: RED._("sidebar.project.projectSettings.deleteRemote"), click: function() { notification.close(); @@ -1315,10 +1327,10 @@ RED.projects.settings = (function() { // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val()); var validRepo = repo.length > 0 && !/\s/.test(repo); if (/^https?:\/\/[^/]+@/i.test(repo)) { - remoteURLLabel.text("Do not include the username/password in the url"); + remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule2")); validRepo = false; } else { - remoteURLLabel.text("https://, ssh:// or file://"); + remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule")); } saveButton.attr('disabled',(!validName || !validRepo)) remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName); @@ -1332,22 +1344,24 @@ RED.projects.settings = (function() { var remoteNameInputChanged = false; var remoteURLInputChanged = false; - $('
').text('Add remote').appendTo(addRemoteDialog); + $('
').text(RED._('sidebar.project.projectSettings.addRemote2')).appendTo(addRemoteDialog); row = $('').appendTo(addRemoteDialog); - $('').text('Remote name').appendTo(row); + $('').text(RED._("sidebar.project.projectSettings.remoteName")).appendTo(row); var remoteNameInput = $('').appendTo(row).on("change keyup paste",function() { remoteNameInputChanged = true; validateForm(); }); - $('').appendTo(row).find("small"); + $('').appendTo(row).find("small"); row = $('').appendTo(addRemoteDialog); - $('').text('URL').appendTo(row); + $('').text(RED._("sidebar.project.projectSettings.url")).appendTo(row); var remoteURLInput = $('').appendTo(row).on("change keyup paste",function() { remoteURLInputChanged = true; validateForm() }); - var remoteURLLabel = $('').appendTo(row).find("small"); + var remoteURLLabel = $('').appendTo(row).find("small"); var hideEditForm = function() { editRepoButton.attr('disabled',false); @@ -1361,13 +1375,13 @@ RED.projects.settings = (function() { } var formButtons = $('') .appendTo(addRemoteDialog); - $('') + $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); hideEditForm(); }); - var saveButton = $('') + var saveButton = $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); @@ -1484,19 +1498,19 @@ RED.projects.settings = (function() { utils = _utils; addPane({ id:'main', - title: "Project", // TODO: nls + title: RED._("sidebar.project.name"), get: createMainPane, close: function() { } }); addPane({ id:'deps', - title: "Dependencies", // TODO: nls + title: RED._("sidebar.project.dependencies"), get: createDependenciesPane, close: function() { } }); addPane({ id:'settings', - title: "Settings", // TODO: nls + title: RED._("sidebar.project.settings"), get: createSettingsPane, close: function() { if (popover) { diff --git a/red/api/editor/locales/en-US/editor.json b/red/api/editor/locales/en-US/editor.json index b2285f566..af62248da 100644 --- a/red/api/editor/locales/en-US/editor.json +++ b/red/api/editor/locales/en-US/editor.json @@ -467,8 +467,47 @@ "description": "Description", "dependencies": "Dependencies", "settings": "Settings", + "noSummaryAvailable": "No summary available", "editDescription": "Edit project description", "editDependencies": "Edit project dependencies", + "editReadme": "Edit README.md", + "projectSettings": { + "edit": "edit", + "none": "None", + "install": "install", + "removeFromProject": "remove from project", + "addToProject": "add to project", + "none": "None", + "files": "Files", + "flow": "Flow", + "credentials": "Credentials", + "invalidEncryptionKey": "Invalid encryption key", + "encryptionEnabled": "Encryption enabled", + "encryptionDisabled": "Encryption disabled", + "resetTheEncryptionKey": "Reset the encryption key:", + "setTheEncryptionKey": "Set the encryption key:", + "changeTheEncryptionKey": "Change the encryption key:", + "currentKey": "Current key", + "newKey": "New key", + "credentialsAlert": "This will delete all existing credentials", + "versionControl": "Version Control", + "branches": "Branches", + "noBranches": "No branches", + "deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.", + "unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?", + "deleteUnmergedBranch": "Delete unmerged branch", + "gitRemotes": "Git remotes", + "addRemote": "add remote", + "addRemote2": "Add remote", + "remoteName": "Remote name", + "nameRule": "Must contain only A-Z 0-9 _ -", + "url": "URL", + "urlRule": "https://, ssh:// or file://", + "urlRule2": "Do not include the username/password in the URL", + "noRemotes": "No remotes", + "deleteRemoteConfrim": "Are you sure you want to delete the remote '__name__'?", + "deleteRemote": "Delete remote" + }, "userSettings": { "committerDetail": "Committer Details", "committerTip": "Leave blank to use system default", diff --git a/red/api/editor/locales/ja/editor.json b/red/api/editor/locales/ja/editor.json index d24dcc711..d18938123 100644 --- a/red/api/editor/locales/ja/editor.json +++ b/red/api/editor/locales/ja/editor.json @@ -459,8 +459,46 @@ "description": "詳細", "dependencies": "依存関係", "settings": "設定", + "noSummaryAvailable": "サマリが存在しません", "editDescription": "プロジェクトの詳細を編集", "editDependencies": "プロジェクトの依存関係を編集", + "editReadme": "README.mdを編集", + "projectSettings": { + "edit": "編集", + "none": "なし", + "install": "インストール", + "removeFromProject": "プロジェクトから削除", + "addToProject": "プロジェクトへ追加", + "files": "ファイル", + "flow": "フロー", + "credentials": "認証情報", + "invalidEncryptionKey": "不正な暗号化キー", + "encryptionEnabled": "暗号化が有効になっています", + "encryptionDisabled": "暗号化が無効になっています", + "setTheEncryptionKey": "暗号化キーを設定:", + "resetTheEncryptionKey": "暗号化キーを初期化:", + "changeTheEncryptionKey": "暗号化キーを変更:", + "currentKey": "現在のキー", + "newKey": "新規のキー", + "credentialsAlert": "既存の認証情報は全て削除されます", + "versionControl": "バージョン管理", + "branches": "ブランチ", + "noBranches": "ブランチなし", + "deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。", + "unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?", + "deleteUnmergedBranch": "マージされていないブランチを削除", + "gitRemotes": "Gitリモート", + "addRemote": "リモートを追加", + "addRemote2": "リモートを追加", + "remoteName": "リモート名", + "nameRule": "A-Z 0-9 _ - のみを含む", + "url": "URL", + "urlRule": "https://、ssh:// または file://", + "urlRule2": "URLにユーザ名、パスワードを含んではいけません", + "noRemotes": "リモートなし", + "deleteRemoteConfrim": "本当にリモート'__name__'を削除しますか?", + "deleteRemote": "リモートを削除" + }, "userSettings": { "committerDetail": "コミッター詳細", "committerTip": "システムのデフォルトを使用する場合、空白のままにしてください", From dc139bcc305bb0cfd687b5a07d70706aa86d6c43 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 4 Jun 2018 07:15:26 +0900 Subject: [PATCH 05/17] Remove new line --- editor/js/ui/projects/projectSettings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/js/ui/projects/projectSettings.js b/editor/js/ui/projects/projectSettings.js index b5ee024a1..b7156fcb6 100644 --- a/editor/js/ui/projects/projectSettings.js +++ b/editor/js/ui/projects/projectSettings.js @@ -1360,8 +1360,7 @@ RED.projects.settings = (function() { remoteURLInputChanged = true; validateForm() }); - var remoteURLLabel = $('').appendTo(row).find("small"); + var remoteURLLabel = $('').appendTo(row).find("small"); var hideEditForm = function() { editRepoButton.attr('disabled',false); From 0e4cedbc5e2bcd3568d7f4c8a4edd2153a30450a Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 4 Jun 2018 07:21:48 +0900 Subject: [PATCH 06/17] Remove new lines --- editor/js/ui/projects/projectSettings.js | 39 ++++++++---------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/editor/js/ui/projects/projectSettings.js b/editor/js/ui/projects/projectSettings.js index b7156fcb6..011c9fbe7 100644 --- a/editor/js/ui/projects/projectSettings.js +++ b/editor/js/ui/projects/projectSettings.js @@ -235,8 +235,7 @@ RED.projects.settings = (function() { var summaryContent = $('
',{style:"color: #999"}).appendTo(summary); updateProjectSummary(activeProject.summary, summaryContent); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .prependTo(summary) .click(function(evt) { evt.preventDefault(); @@ -251,8 +250,7 @@ RED.projects.settings = (function() { updateProjectDescription(activeProject, descriptionContent); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .prependTo(description) .click(function(evt) { evt.preventDefault(); @@ -383,8 +381,7 @@ RED.projects.settings = (function() { function createDependenciesPane(activeProject) { var pane = $('
'); if (RED.user.hasPermission("projects.write")) { - $('') + $('') .appendTo(pane) .click(function(evt) { evt.preventDefault(); @@ -454,8 +451,7 @@ RED.projects.settings = (function() { var buttons = $('
').appendTo(metaRow); if (RED.user.hasPermission("projects.write")) { if (!entry.installed && RED.settings.theme('palette.editable') !== false) { - $('' - + RED._("sidebar.project.projectSettings.install") + '').appendTo(buttons) + $('' + RED._("sidebar.project.projectSettings.install") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); RED.palette.editor.install(entry,row,function(err) { @@ -472,8 +468,7 @@ RED.projects.settings = (function() { }); }) } else if (entry.known && entry.count === 0) { - $('' - + RED._("sidebar.project.projectSettings.removeFromProject") + '').appendTo(buttons) + $('' + RED._("sidebar.project.projectSettings.removeFromProject") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); @@ -489,8 +484,7 @@ RED.projects.settings = (function() { }); }); } else if (!entry.known) { - $('' - + RED._("sidebar.project.projectSettings.addToProject") + '').appendTo(buttons) + $('' + RED._("sidebar.project.projectSettings.addToProject") + '').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); @@ -732,8 +726,7 @@ RED.projects.settings = (function() { var title = $('

').text(RED._("sidebar.project.projectSettings.files")).appendTo(pane); var filesContainer = $('').appendTo(pane); if (RED.user.hasPermission("projects.write")) { - var editFilesButton = $('') + var editFilesButton = $('') .appendTo(title) .click(function(evt) { evt.preventDefault(); @@ -906,12 +899,9 @@ RED.projects.settings = (function() { var credentialFormRows = $('
',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel); - var credentialSetLabel = $('
' - + RED._("sidebar.project.projectSettings.setTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); - var credentialChangeLabel = $('
' - + RED._("sidebar.project.projectSettings.changeTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); - var credentialResetLabel = $('
' - + RED._("sidebar.project.projectSettings.resetTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); + var credentialSetLabel = $('
' + RED._("sidebar.project.projectSettings.setTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); + var credentialChangeLabel = $('
' + RED._("sidebar.project.projectSettings.changeTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); + var credentialResetLabel = $('
' + RED._("sidebar.project.projectSettings.resetTheEncryptionKey") + '
').hide().appendTo(credentialFormRows); var credentialSecretExistingRow = $('').appendTo(credentialFormRows); $('').text(RED._("sidebar.project.projectSettings.currentKey")).appendTo(credentialSecretExistingRow); @@ -930,8 +920,7 @@ RED.projects.settings = (function() { $('').text(RED._("sidebar.project.projectSettings.newKey")).appendTo(credentialSecretNewRow); var credentialSecretNewInput = $('').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles); - var credentialResetWarning = $('
' - + RED._("sidebar.project.projectSettings.credentialsAlert") + '
').hide().appendTo(credentialFormRows); + var credentialResetWarning = $('
' + RED._("sidebar.project.projectSettings.credentialsAlert") + '
').hide().appendTo(credentialFormRows); var hideEditForm = function() { @@ -1201,8 +1190,7 @@ RED.projects.settings = (function() { var repoContainer = $('').appendTo(pane); var title = $('

').text(RED._("sidebar.project.projectSettings.gitRemotes")).appendTo(repoContainer); - var editRepoButton = $('') + var editRepoButton = $('') .appendTo(title) .click(function(evt) { editRepoButton.attr('disabled',true); @@ -1352,8 +1340,7 @@ RED.projects.settings = (function() { remoteNameInputChanged = true; validateForm(); }); - $('').appendTo(row).find("small"); + $('').appendTo(row).find("small"); row = $('').appendTo(addRemoteDialog); $('').text(RED._("sidebar.project.projectSettings.url")).appendTo(row); var remoteURLInput = $('').appendTo(row).on("change keyup paste",function() { From 4fbf1fe780fc6d63b8de8490642f1fe3fffd5398 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jun 2018 20:51:30 +0100 Subject: [PATCH 07/17] Add middle-button-drag to pan the workspace --- editor/js/ui/state.js | 3 ++- editor/js/ui/view.js | 46 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/editor/js/ui/state.js b/editor/js/ui/state.js index 1b0990c58..b14bc915b 100644 --- a/editor/js/ui/state.js +++ b/editor/js/ui/state.js @@ -23,5 +23,6 @@ RED.state = { EXPORT: 6, IMPORT: 7, IMPORT_DRAGGING: 8, - QUICK_JOINING: 9 + QUICK_JOINING: 9, + PANNING: 10 } diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index ba568459c..d6c1bfa8c 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -58,7 +58,8 @@ RED.view = (function() { lastClickNode = null, dblClickPrimed = null, clickTime = 0, - clickElapsed = 0; + clickElapsed = 0, + scroll_position; var clipboard = ""; @@ -73,6 +74,8 @@ RED.view = (function() { var PORT_TYPE_INPUT = 1; var PORT_TYPE_OUTPUT = 0; + var chart = $("#chart"); + var outer = d3.select("#chart") .append("svg:svg") .attr("width", space_width) @@ -94,6 +97,16 @@ RED.view = (function() { .on("mousemove", canvasMouseMove) .on("mousedown", canvasMouseDown) .on("mouseup", canvasMouseUp) + .on("mouseenter", function() { + if (lasso) { + if (d3.event.buttons !== 1) { + lasso.remove(); + lasso = null; + } + } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) { + resetMouseVars(); + } + }) .on("touchend", function() { clearTimeout(touchStartTime); touchStartTime = null; @@ -283,7 +296,6 @@ RED.view = (function() { function init() { RED.events.on("workspace:change",function(event) { - var chart = $("#chart"); if (event.old !== 0) { workspaceScrollPositions[event.old] = { left:chart.scrollLeft(), @@ -526,6 +538,15 @@ RED.view = (function() { function canvasMouseDown() { var point; + if (d3.event.button === 1) { + // Middle Click pan + mouse_mode = RED.state.PANNING; + mouse_position = [d3.event.pageX,d3.event.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + + return; + } + if (!mousedown_node && !mousedown_link) { selected_link = null; updateSelection(); @@ -644,7 +665,6 @@ RED.view = (function() { function canvasMouseMove() { var i; var node; - mouse_position = d3.touches(this)[0]||d3.mouse(this); // Prevent touch scrolling... //if (d3.touches(this)[0]) { // d3.event.preventDefault(); @@ -655,6 +675,22 @@ RED.view = (function() { //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); + if (mouse_mode === RED.state.PANNING) { + + var pos = [d3.event.pageX,d3.event.pageY]; + var deltaPos = [ + mouse_position[0]-pos[0], + mouse_position[1]-pos[1] + ]; + + chart.scrollLeft(scroll_position[0]+deltaPos[0]) + chart.scrollTop(scroll_position[1]+deltaPos[1]) + return + } + + mouse_position = d3.touches(this)[0]||d3.mouse(this); + + if (lasso) { var ox = parseInt(lasso.attr("ox")); var oy = parseInt(lasso.attr("oy")); @@ -906,6 +942,10 @@ RED.view = (function() { function canvasMouseUp() { var i; var historyEvent; + if (mouse_mode === RED.state.PANNING) { + resetMouseVars(); + return + } if (mouse_mode === RED.state.QUICK_JOINING) { return; } From 3b0300b8349ea26506dbec71e65ebbcb42d02123 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jun 2018 21:38:44 +0100 Subject: [PATCH 08/17] Cache flow library result to improve response time Fixes #1753 --- red/runtime/storage/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/red/runtime/storage/index.js b/red/runtime/storage/index.js index e0ef6d6b6..a2593eff1 100644 --- a/red/runtime/storage/index.js +++ b/red/runtime/storage/index.js @@ -25,6 +25,8 @@ var storageModule; var settingsAvailable; var sessionsAvailable; +var libraryFlowsCachedResult = null; + function moduleSelector(aSettings) { var toReturn; if (aSettings.storageModule) { @@ -156,7 +158,14 @@ var storageModuleInterface = { if (storageModule.hasOwnProperty("getAllFlows")) { return storageModule.getAllFlows(); } else { - return listFlows("/"); + if (libraryFlowsCachedResult) { + return Promise.resolve(libraryFlowsCachedResult); + } else { + return listFlows("/").then(function(result) { + libraryFlowsCachedResult = result; + return result; + }); + } } }, getFlow: function(fn) { @@ -178,6 +187,7 @@ var storageModuleInterface = { err.code = "forbidden"; return when.reject(err); } + libraryFlowsCachedResult = null; if (storageModule.hasOwnProperty("saveFlow")) { return storageModule.saveFlow(fn, data); } else { From 17c5fdf0d502a76497e4f907320108c50c1fae4d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Jun 2018 23:32:17 +0100 Subject: [PATCH 09/17] Add flow navigator widget --- Gruntfile.js | 1 + editor/js/ui/view-navigator.js | 164 +++++++++++++++++++++++++++++++++ editor/js/ui/view.js | 11 ++- editor/sass/mixins.scss | 3 +- editor/sass/workspace.scss | 4 +- editor/templates/index.mst | 1 + 6 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 editor/js/ui/view-navigator.js diff --git a/Gruntfile.js b/Gruntfile.js index 3738e60b4..6d53e4d29 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -144,6 +144,7 @@ module.exports = function(grunt) { "editor/js/ui/keyboard.js", "editor/js/ui/workspaces.js", "editor/js/ui/view.js", + "editor/js/ui/view-navigator.js", "editor/js/ui/sidebar.js", "editor/js/ui/palette.js", "editor/js/ui/tab-info.js", diff --git a/editor/js/ui/view-navigator.js b/editor/js/ui/view-navigator.js new file mode 100644 index 000000000..da1908982 --- /dev/null +++ b/editor/js/ui/view-navigator.js @@ -0,0 +1,164 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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.view.navigator = (function() { + + var nav_scale = 25; + var nav_width = 5000/nav_scale; + var nav_height = 5000/nav_scale; + + var navContainer; + var navBox; + var navBorder; + var navVis; + var scrollPos; + var scaleFactor; + var chartSize; + var dimensions; + var isDragging; + var isShowing = false; + + function refreshNodes() { + if (!isShowing) { + return; + } + var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id}); + navNode.exit().remove(); + navNode.enter().insert("rect") + .attr('class','navnode') + .attr("pointer-events", "none"); + navNode.each(function(d) { + d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale }) + .attr("y",function(d) { return (d.y-d.h/2)/nav_scale }) + .attr("width",function(d) { return Math.max(9,d.w/nav_scale) }) + .attr("height",function(d) { return Math.max(3,d.h/nav_scale) }) + .attr("fill",function(d) { return d._def.color;}) + }); + } + function onScroll() { + if (!isDragging) { + resizeNavBorder(); + } + } + function resizeNavBorder() { + if (navBorder) { + scaleFactor = RED.view.scale(); + chartSize = [ $("#chart").width(), $("#chart").height()]; + scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + navBorder.attr('x',scrollPos[0]/nav_scale) + .attr('y',scrollPos[1]/nav_scale) + .attr('width',chartSize[0]/nav_scale/scaleFactor) + .attr('height',chartSize[1]/nav_scale/scaleFactor) + } + } + + return { + init: function() { + + $(window).resize(resizeNavBorder); + RED.events.on("sidebar:resize",resizeNavBorder); + + var hideTimeout; + + navContainer = $('
').css({ + "position":"absolute", + "bottom":$("#workspace-footer").height(), + "right":0, + zIndex: 1 + }).appendTo("#workspace").hide(); + + navBox = d3.select(navContainer[0]) + .append("svg:svg") + .attr("width", nav_width) + .attr("height", nav_height) + .attr("pointer-events", "all") + .style({ + position: "absolute", + bottom: 0, + right:0, + zIndex: 101, + "border-left": "1px solid #ccc", + "border-top": "1px solid #ccc", + background: "rgba(245,245,245,0.5)", + "box-shadow": "-1px 0 3px rgba(0,0,0,0.1)" + }); + + navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({ + fill:"none", + stroke:"none", + pointerEvents:"all" + }).on("mousedown", function() { + // Update these in case they have changed + scaleFactor = RED.view.scale(); + chartSize = [ $("#chart").width(), $("#chart").height()]; + dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor]; + var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]); + var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]); + navBorder.attr('x',newX).attr('y',newY); + isDragging = true; + $("#chart").scrollLeft(newX*nav_scale*scaleFactor); + $("#chart").scrollTop(newY*nav_scale*scaleFactor); + }).on("mousemove", function() { + if (!isDragging) { return } + if (d3.event.buttons === 0) { + isDragging = false; + return; + } + var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]); + var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]); + navBorder.attr('x',newX).attr('y',newY); + $("#chart").scrollLeft(newX*nav_scale*scaleFactor); + $("#chart").scrollTop(newY*nav_scale*scaleFactor); + }).on("mouseup", function() { + isDragging = false; + }) + + navBorder = navBox.append("rect") + .attr("stroke-dasharray","5,5") + .attr("pointer-events", "none") + .style({ + stroke: "#999", + strokeWidth: 1, + fill: "white", + }); + + navVis = navBox.append("svg:g") + + + $("#btn-navigate").click(function(evt) { + evt.preventDefault(); + if (!isShowing) { + isShowing = true; + $("#btn-navigate").addClass("selected"); + resizeNavBorder(); + refreshNodes(); + $("#chart").on("scroll",onScroll); + navContainer.fadeIn(200); + } else { + isShowing = false; + navContainer.fadeOut(100); + $("#chart").off("scroll",onScroll); + $("#btn-navigate").removeClass("selected"); + } + }) + }, + refresh: refreshNodes, + resize: resizeNavBorder + } + + +})(); diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index d6c1bfa8c..6a8dd77c4 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -332,6 +332,8 @@ RED.view = (function() { redraw(); }); + RED.view.navigator.init(); + $("#btn-zoom-out").click(function() {zoomOut();}); $("#btn-zoom-zero").click(function() {zoomZero();}); $("#btn-zoom-in").click(function() {zoomIn();}); @@ -1060,17 +1062,20 @@ RED.view = (function() { function zoomIn() { if (scaleFactor < 2) { scaleFactor += 0.1; + RED.view.navigator.resize(); redraw(); } } function zoomOut() { if (scaleFactor > 0.3) { scaleFactor -= 0.1; + RED.view.navigator.resize(); redraw(); } } function zoomZero() { scaleFactor = 1; + RED.view.navigator.resize(); redraw(); } @@ -2542,7 +2547,7 @@ RED.view = (function() { } ).classed("link_selected", false); } - + RED.view.navigator.refresh(); if (d3.event) { d3.event.preventDefault(); } @@ -2816,7 +2821,9 @@ RED.view = (function() { gridSize = Math.max(5,v); updateGrid(); } + }, + getActiveNodes: function() { + return activeNodes; } - }; })(); diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss index 7201ef544..36d08c948 100644 --- a/editor/sass/mixins.scss +++ b/editor/sass/mixins.scss @@ -203,7 +203,7 @@ height: 25px; line-height: 23px; padding: 0 10px; - + user-select: none; .button-group:not(:last-child) { margin-right: 5px; @@ -227,6 +227,7 @@ font-size: 11px; line-height: 17px; height: 18px; + width: 18px; &.text-button { width: auto; padding: 0 5px; diff --git a/editor/sass/workspace.scss b/editor/sass/workspace.scss index 59b8fcf75..14b6eebb0 100644 --- a/editor/sass/workspace.scss +++ b/editor/sass/workspace.scss @@ -47,7 +47,9 @@ .workspace-footer-button { @include component-footer-button; } - +.workspace-footer-button-toggle { + @include component-footer-button-toggle; +} #workspace-footer { @include component-footer; } diff --git a/editor/templates/index.mst b/editor/templates/index.mst index b8d0ac0c0..972258a55 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -51,6 +51,7 @@ +
From 2a122ed28358cc34130b8f9ab4338b364e007ac0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jun 2018 12:54:32 +0100 Subject: [PATCH 10/17] Allow subflows to be put in any palette category --- editor/js/nodes.js | 5 +- editor/js/ui/editor.js | 44 +++++++++++++- editor/js/ui/palette.js | 75 +++++++++++++++++------- editor/js/ui/tab-info.js | 7 +++ editor/templates/index.mst | 4 ++ red/api/editor/locales/en-US/editor.json | 2 + 6 files changed, 113 insertions(+), 24 deletions(-) diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 433e400a2..12ff3e682 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -133,7 +133,7 @@ RED.nodes = (function() { registerNodeType: function(nt,def) { nodeDefinitions[nt] = def; def.type = nt; - if (def.category != "subflows") { + if (nt.substring(0,8) != "subflow:") { def.set = nodeSets[typeToId[nt]]; nodeSets[typeToId[nt]].added = true; nodeSets[typeToId[nt]].enabled = true; @@ -356,7 +356,7 @@ RED.nodes = (function() { defaults:{name:{value:""}}, info: sf.info, icon: function() { return sf.icon||"subflow.png" }, - category: "subflows", + category: sf.category || "subflows", inputs: sf.in.length, outputs: sf.out.length, color: "#da9", @@ -519,6 +519,7 @@ RED.nodes = (function() { node.type = n.type; node.name = n.name; node.info = n.info; + node.category = n.category; node.in = []; node.out = []; diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index df50db725..380521333 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -1701,6 +1701,21 @@ RED.editor = (function() { editing_node.icon = icon; changed = true; } + var newCategory = $("#subflow-input-category").val().trim(); + if (newCategory === "_custom_") { + newCategory = $("#subflow-input-custom-category").val().trim(); + if (newCategory === "") { + newCategory = editing_node.category; + } + } + if (newCategory === 'subflows') { + newCategory = ''; + } + if (newCategory != editing_node.category) { + changes['category'] = editing_node.category; + editing_node.category = newCategory; + changed = true; + } RED.palette.refresh(); @@ -1773,8 +1788,6 @@ RED.editor = (function() { }); portLabels.content.addClass("editor-tray-content"); - - if (editing_node) { RED.sidebar.info.refresh(editing_node); } @@ -1787,6 +1800,33 @@ RED.editor = (function() { $("#subflow-input-name").val(subflow.name); RED.text.bidi.prepareInput($("#subflow-input-name")); + + $("#subflow-input-category").empty(); + var categories = RED.palette.getCategories(); + categories.sort(function(A,B) { + return A.label.localeCompare(B.label); + }) + categories.forEach(function(cat) { + $("#subflow-input-category").append($("").val(cat.id).text(cat.label)); + }) + $("#subflow-input-category").append($("").attr('disabled',true).text("---")); + $("#subflow-input-category").append($("").val("_custom_").text(RED._("palette.addCategory"))); + + + $("#subflow-input-category").change(function() { + var val = $(this).val(); + if (val === "_custom_") { + $("#subflow-input-category").width(120); + $("#subflow-input-custom-category").show(); + } else { + $("#subflow-input-category").width(250); + $("#subflow-input-custom-category").hide(); + } + }) + + + $("#subflow-input-category").val(subflow.category||"subflows"); + subflowEditor.getSession().setValue(subflow.info||"",-1); var userCount = 0; var subflowType = "subflow:"+editing_node.id; diff --git a/editor/js/ui/palette.js b/editor/js/ui/palette.js index 38ebc1091..c72ed4f7a 100644 --- a/editor/js/ui/palette.js +++ b/editor/js/ui/palette.js @@ -21,7 +21,18 @@ RED.palette = (function() { var categoryContainers = {}; - function createCategoryContainer(category, label) { + + function createCategory(originalCategory,rootCategory,category,ns) { + if ($("#palette-base-category-"+rootCategory).length === 0) { + createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory); + } + $("#palette-container-"+rootCategory).show(); + if ($("#palette-"+category).length === 0) { + $("#palette-base-category-"+rootCategory).append('
'); + } + } + function createCategoryContainer(originalCategory,category, labelId) { + var label = RED._(labelId, {defaultValue:category}); label = (label || category).replace(/_/g, " "); var catDiv = $('
'+ '
'+label+'
'+ @@ -31,7 +42,8 @@ RED.palette = (function() { '
'+ '
'+ '
').appendTo("#palette-container"); - + catDiv.data('category',originalCategory); + catDiv.data('label',label); categoryContainers[category] = { container: catDiv, close: function() { @@ -133,6 +145,7 @@ RED.palette = (function() { } if (exclusion.indexOf(def.category)===-1) { + var originalCategory = def.category; var category = def.category.replace(/ /g,"_"); var rootCategory = category.split("-")[0]; @@ -153,7 +166,6 @@ RED.palette = (function() { d.className="palette_node"; - if (def.icon) { var icon_url = RED.utils.getNodeIcon(def); var iconContainer = $('
',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d); @@ -174,21 +186,12 @@ RED.palette = (function() { d.appendChild(portIn); } - if ($("#palette-base-category-"+rootCategory).length === 0) { - if(coreCategories.indexOf(rootCategory) !== -1){ - createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory})); - } else { - var ns = def.set.id; - createCategoryContainer(rootCategory, RED._(ns+":palette.label."+rootCategory, {defaultValue:rootCategory})); - } - } - $("#palette-container-"+rootCategory).show(); - - if ($("#palette-"+category).length === 0) { - $("#palette-base-category-"+rootCategory).append('
'); - } + createCategory(def.category,rootCategory,category,(coreCategories.indexOf(rootCategory) !== -1)?"node-red":def.set.id); $("#palette-"+category).append(d); + + $(d).data('category',rootCategory); + d.onmousedown = function(e) { e.preventDefault(); }; var popover = RED.popover.create({ @@ -308,7 +311,7 @@ RED.palette = (function() { }); var nodeInfo = null; - if (def.category == "subflows") { + if (nt.indexOf("subflow:") === 0) { $(d).dblclick(function(e) { RED.workspaces.show(nt.substring(8)); e.preventDefault(); @@ -382,6 +385,31 @@ RED.palette = (function() { } setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); setIcon(paletteNode,sf); + + var currentCategory = paletteNode.data('category'); + var newCategory = (sf.category||"subflows"); + if (currentCategory !== newCategory) { + var category = newCategory.replace(/ /g,"_"); + createCategory(newCategory,category,category,"node-red"); + + var currentCategoryNode = paletteNode.closest(".palette-category"); + var newCategoryNode = $("#palette-"+category); + newCategoryNode.append(paletteNode); + if (newCategoryNode.find(".palette_node").length === 1) { + categoryContainers[category].open(); + } + + paletteNode.data('category',newCategory); + if (currentCategoryNode.find(".palette_node").length === 0) { + if (currentCategoryNode.find("i").hasClass("expanded")) { + currentCategoryNode.find(".palette-content").slideToggle(); + currentCategoryNode.find("i").toggleClass("expanded"); + } + } + + + + } }); } @@ -471,7 +499,7 @@ RED.palette = (function() { categoryList = coreCategories } categoryList.forEach(function(category){ - createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category})); + createCategoryContainer(category, category, "palette.label."+category); }); $("#palette-collapse-all").on("click", function(e) { @@ -491,13 +519,20 @@ RED.palette = (function() { } }); } - + function getCategories() { + var categories = []; + $("#palette-container .palette-category").each(function(i,d) { + categories.push({id:$(d).data('category'),label:$(d).data('label')}); + }) + return categories; + } return { init: init, add:addNodeType, remove:removeNodeType, hide:hideNodeType, show:showNodeType, - refresh:refreshNodeTypes + refresh:refreshNodeTypes, + getCategories: getCategories }; })(); diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index 340a6bd74..bb1bf37cb 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -170,6 +170,10 @@ RED.sidebar.info = (function() { if (node.type === "tab") { 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 === "subflow") { + propRow = $(''+RED._("subflow.category")+'').appendTo(tableBody); + var category = node.category||"subflows"; + $(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category})) } } else { propRow = $(''+RED._("sidebar.info.node")+"").appendTo(tableBody); @@ -235,6 +239,9 @@ RED.sidebar.info = (function() { } } if (m) { + propRow = $(''+RED._("subflow.category")+'').appendTo(tableBody); + var category = subflowNode.category||"subflows"; + $(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category})) $(''+RED._("sidebar.info.instances")+""+subflowUserCount+'').appendTo(tableBody); } diff --git a/editor/templates/index.mst b/editor/templates/index.mst index 972258a55..23a87fde7 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -132,6 +132,10 @@
+
+ + +
diff --git a/red/api/editor/locales/en-US/editor.json b/red/api/editor/locales/en-US/editor.json index af62248da..65681850f 100644 --- a/red/api/editor/locales/en-US/editor.json +++ b/red/api/editor/locales/en-US/editor.json @@ -240,6 +240,7 @@ "output": "outputs:", "deleteSubflow": "delete subflow", "info": "Description", + "category": "Category", "format":"markdown format", "errors": { "noNodesSelected": "Cannot create subflow: no nodes selected", @@ -318,6 +319,7 @@ "noInfo": "no information available", "filter": "filter nodes", "search": "search modules", + "addCategory": "Add new...", "label": { "subflows": "subflows", "input": "input", From 68779caa2e83013b83c74335b8d5b44afcb4519a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jun 2018 15:34:08 +0100 Subject: [PATCH 11/17] Only edit nodes on dbl click on primary button with no modifiers --- editor/js/ui/view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index 6a8dd77c4..71454153d 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -1697,7 +1697,9 @@ RED.view = (function() { clickElapsed = now-clickTime; clickTime = now; - dblClickPrimed = (lastClickNode == mousedown_node); + dblClickPrimed = (lastClickNode == mousedown_node && + d3.event.buttons === 1 && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey); lastClickNode = mousedown_node; var i; From 6cad80c4ad3fe913c6d6107470652214f61fa209 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jun 2018 23:46:06 +0100 Subject: [PATCH 12/17] Add node icon picker widget --- editor/js/ui/editor.js | 227 +++++++++++++++++++++------------------- editor/sass/editor.scss | 61 +++++++++++ 2 files changed, 179 insertions(+), 109 deletions(-) diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index 380521333..0f6050b8a 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -108,17 +108,6 @@ RED.editor = (function() { } } } - if (node.icon) { - var iconPath = RED.utils.separateIconPath(node.icon); - if (!iconPath.module) { - return isValid; - } - var iconSets = RED.nodes.getIconSets(); - var iconFileList = iconSets[iconPath.module]; - if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) { - isValid = false; - } - } return isValid; } @@ -170,27 +159,6 @@ RED.editor = (function() { } } } - validateIcon(node); - } - - function validateIcon(node) { - if (node._def.hasOwnProperty("defaults") && !node._def.defaults.hasOwnProperty("icon") && node.icon) { - var iconPath = RED.utils.separateIconPath(node.icon); - var iconSets = RED.nodes.getIconSets(); - var iconFileList = iconSets[iconPath.module]; - var iconModule = $("#node-settings-icon-module"); - var iconFile = $("#node-settings-icon-file"); - if (!iconFileList) { - iconModule.addClass("input-error"); - iconFile.removeClass("input-error"); - } else if (iconFileList.indexOf(iconPath.file) === -1) { - iconModule.removeClass("input-error"); - iconFile.addClass("input-error"); - } else { - iconModule.removeClass("input-error"); - iconFile.removeClass("input-error"); - } - } } function validateNodeEditorProperty(node,defaults,property,prefix) { @@ -711,6 +679,97 @@ RED.editor = (function() { } return result; } + function showIconPicker(container, node, iconPath, done) { + var containerPos = container.offset(); + var pickerBackground = $('
').css({ + position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20 + }).appendTo("body"); + + var top = containerPos.top - 30; + + if (top+280 > $( window ).height()) { + top = $( window ).height() - 280; + } + var picker = $('
').css({ + top: top+"px", + left: containerPos.left+"px", + }).appendTo("body"); + + var hide = function() { + pickerBackground.remove(); + picker.remove(); + RED.keyboard.remove("escape"); + } + RED.keyboard.add("*","escape",function(){hide()}); + pickerBackground.on("mousedown", hide); + + var searchDiv = $("
",{class:"red-ui-search-container"}).appendTo(picker); + searchInput = $('').attr("placeholder","Search icons").appendTo(searchDiv).searchBox({ + delay: 50, + change: function() { + var searchTerm = $(this).val().trim(); + if (searchTerm === "") { + iconList.find(".red-ui-icon-list-module").show(); + iconList.find(".red-ui-icon-list-icon").show(); + } else { + iconList.find(".red-ui-icon-list-module").hide(); + iconList.find(".red-ui-icon-list-icon").each(function(i,n) { + if ($(n).data('icon').indexOf(searchTerm) === -1) { + $(n).hide(); + } else { + $(n).show(); + } + }); + } + } + }); + + var row = $('
').appendTo(picker); + var iconList = $('
').appendTo(picker); + var metaRow = $('
').appendTo(picker); + var summary = $('').appendTo(metaRow); + var resetButton = $('').appendTo(metaRow).click(function(e) { + e.preventDefault(); + hide(); + done(null); + }); + var iconSets = RED.nodes.getIconSets(); + Object.keys(iconSets).forEach(function(moduleName) { + var icons = iconSets[moduleName]; + if (icons.length > 0) { + // selectIconModule.append($("").val(moduleName).text(moduleName)); + var header = $('
').text(moduleName).appendTo(iconList); + $('').prependTo(header); + icons.forEach(function(icon) { + var iconDiv = $('
',{class:"red-ui-icon-list-icon"}).appendTo(iconList); + var nodeDiv = $('
',{class:"red-ui-search-result-node"}).appendTo(iconDiv); + var colour = node._def.color; + var icon_url = "icons/"+moduleName+"/"+icon; + iconDiv.data('icon',icon_url) + nodeDiv.css('backgroundColor',colour); + var iconContainer = $('
',{class:"palette_icon_container"}).appendTo(nodeDiv); + $('
',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer); + + if (iconPath.module === moduleName && iconPath.file === icon) { + iconDiv.addClass("selected"); + } + iconDiv.on("mouseover", function() { + summary.text(icon); + }) + iconDiv.on("mouseout", function() { + summary.html(" "); + }) + iconDiv.click(function() { + hide(); + done(moduleName+"/"+icon); + }) + }) + } + }); + picker.slideDown(100); + searchInput.focus(); + } + function buildLabelForm(container,node) { var dialogForm = $('
').appendTo(container); @@ -748,81 +807,36 @@ RED.editor = (function() { } if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) { - $('
').appendTo(dialogForm); - var iconDiv = $("#node-settings-icon"); - $('