From 7ab5a2be47c64a61aa404cd9baec0519772a9564 Mon Sep 17 00:00:00 2001 From: kazntree Date: Sun, 20 Jan 2019 19:10:54 +0900 Subject: [PATCH 1/9] remove chromedriver package, and instruct how to install it when running grunt test-ui --- Gruntfile.js | 10 +++++++++- package.json | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 43ff0b370..dc78832d7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,6 +15,7 @@ **/ var path = require("path"); +var fs = require("fs-extra"); module.exports = function(grunt) { @@ -553,6 +554,13 @@ module.exports = function(grunt) { }); }); + grunt.registerTask('verifyUiTestDependencies', function() { + if (!fs.existsSync(path.join("node_modules", "chromedriver"))) { + grunt.fail.fatal('You need to run "npm install chromedriver@2" before running UI test.'); + return false; + } + }); + grunt.registerTask('setDevEnv', 'Sets NODE_ENV=development so non-minified assets are used', function () { @@ -573,7 +581,7 @@ module.exports = function(grunt) { grunt.registerTask('test-ui', 'Builds editor content then runs unit tests on editor ui', - ['build','jshint:editor','webdriver:all']); + ['verifyUiTestDependencies','build','jshint:editor','webdriver:all']); grunt.registerTask('test-nodes', 'Runs unit tests on core nodes', diff --git a/package.json b/package.json index 5799c22c5..549ecf9c6 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "bcrypt": "~2.0.0" }, "devDependencies": { - "chromedriver": "2.45.0", "grunt": "~1.0.3", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", From 79062e2034e9fdab61ec35db67208621a6e727d8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sat, 26 Jan 2019 20:49:22 +0000 Subject: [PATCH 2/9] Move nodes to top-left corner when converting to subflow --- .../@node-red/editor-client/src/js/history.js | 2 ++ .../editor-client/src/js/ui/subflow.js | 28 +++++++++++++++---- .../@node-red/editor-client/src/js/ui/view.js | 12 ++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index a85c31cf1..95cc78292 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -262,6 +262,8 @@ RED.history = (function() { } else if (ev.t == "createSubflow") { if (ev.nodes) { RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) { + n.x += ev.subflow.offsetX; + n.y += ev.subflow.offsetY; n.z = ev.activeWorkspace; n.dirty = true; }); 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 2acbd88b5..ba303152f 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 @@ -430,6 +430,12 @@ RED.subflow = (function() { RED.nodes.dirty(true); } + function snapToGrid(x) { + if (RED.settings.get("editor").view['view-snap-grid']) { + x = Math.round(x / RED.view.gridSize()) * RED.view.gridSize(); + } + return x; + } function convertToSubflow() { var selection = RED.view.selection(); if (!selection.nodes) { @@ -461,8 +467,14 @@ RED.subflow = (function() { Math.max(boundingBox[3],n.y) ] } + var offsetX = snapToGrid(boundingBox[0] - 180); + var offsetY = snapToGrid(boundingBox[1] - 60); - var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2]; + + var center = [ + snapToGrid((boundingBox[2]+boundingBox[0]) / 2), + snapToGrid((boundingBox[3]+boundingBox[1]) / 2) + ]; RED.nodes.eachLink(function(link) { if (nodes[link.source.id] && nodes[link.target.id]) { @@ -519,8 +531,8 @@ RED.subflow = (function() { in: Object.keys(candidateInputNodes).map(function(v,i) { var index = i; return { type:"subflow", direction:"in", - x:candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80, - y:candidateInputNodes[v].y, + x:snapToGrid(candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80 - offsetX), + y:snapToGrid(candidateInputNodes[v].y - offsetY), z:subflowId, i:index, id:RED.nodes.id(), @@ -529,8 +541,8 @@ RED.subflow = (function() { out: candidateOutputs.map(function(v,i) { var index = i; return { type:"subflow", direction:"in", - x:v.source.x+(v.source.w/2)+80, - y:v.source.y, + x:snapToGrid(v.source.x+(v.source.w/2)+80 - offsetX), + y:snapToGrid(v.source.y - offsetY), z:subflowId, i:index, id:RED.nodes.id(), @@ -605,6 +617,8 @@ RED.subflow = (function() { return isLocalLink; }); } + n.x -= offsetX; + n.y -= offsetY; n.z = subflow.id; } @@ -613,7 +627,9 @@ RED.subflow = (function() { nodes:[subflowInstance.id], links:new_links, subflow: { - subflow: subflow + subflow: subflow, + offsetX: offsetX, + offsetY: offsetY }, activeWorkspace: RED.workspaces.active(), 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 9b6415624..9405b60dd 100644 --- 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 @@ -2703,6 +2703,8 @@ RED.view = (function() { d.resize = false; } var thisNode = d3.select(this); + thisNode.classed("node_subflow",function(d) { return activeSubflow != null; }) + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); if (mouse_mode != RED.state.MOVING_ACTIVE) { @@ -3004,6 +3006,9 @@ RED.view = (function() { links.each(function(d) { var link = d3.select(this); if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { + if (/link_line/.test(link.attr('class'))) { + link.classed("link_subflow", function(d) { return !d.link && activeSubflow }); + } link.attr("d",function(d){ var numOutputs = d.source.outputs || 1; var sourcePort = d.sourcePort || 0; @@ -3017,8 +3022,11 @@ RED.view = (function() { // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ // d.x2+" "+d.y2; - - return generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + if (/NaN/.test(path)) { + return "" + } + return path; }); } }) From e0bb03a53fb24bbf514421470bddfc14e1ea69c7 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Jan 2019 14:40:24 +0000 Subject: [PATCH 3/9] More api documentation updates --- CHANGELOG.md | 26 +++++++++++- Gruntfile.js | 4 +- jsdoc.json | 1 - .../@node-red/editor-api/lib/auth/index.js | 14 ++++++- .../@node-red/editor-api/lib/index.js | 41 +++++++++++++++++++ .../@node-red/registry/lib/index.js | 10 +++++ packages/node_modules/node-red/lib/red.js | 17 ++++++++ 7 files changed, 109 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b5d834d..52fd4a18e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,31 @@ Runtime - - Bump JSONata to 1.6.5 + - Bump JSONata to 1.6.4 + - Add Flow.getSetting for resolving env-var properties + - Refactor Subflow logic into own class + - Restore RED.auth to node-red module api + - Tidy up when usage in Flow and Node + +Editor + + - German translation + - Change default dropdown appearance and sidebar tab menu handling + - Handle multiple-select box when nothing selected Fixes #2021 + - Handle i18n properly when key is a valid sub-identifier Fixes #2028 + - Avoid duplicate links when missing node type installed Fixes #2032 + - Add View Tools + - Don't collapse version control header when clicking refresh + - Add fast entry via keyboard for string of nodes + - Check for undeployed change before showing open project dialog + - Add placeholder node when in quick-add mode + - Move nodes to top-left corner when converting to subflow + +Nodes + + - Debug: Allow debug edit expression to be sent to status + - WebSocket: Fix missing translated help + #### 0.20.0-beta.3: Beta Release diff --git a/Gruntfile.js b/Gruntfile.js index dc78832d7..3e8740e7d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -443,7 +443,9 @@ module.exports = function(grunt) { 'packages/node_modules/@node-red/runtime/lib/api/*.js', 'packages/node_modules/@node-red/runtime/lib/events.js', 'packages/node_modules/@node-red/util/**/*.js', - ], + 'packages/node_modules/@node-red/editor-api/lib/index.js', + 'packages/node_modules/@node-red/editor-api/lib/auth/index.js' + ], options: { destination: 'docs', configure: './jsdoc.json' diff --git a/jsdoc.json b/jsdoc.json index dca8b7e26..d7f881032 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -15,7 +15,6 @@ }, "templates": { "systemName": "Node-RED Runtime API", - "theme":"yeti", "footer": "", "copyright": "Released under the Apache License v2.0", "default": { 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 97e250f4a..7781ebc7f 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 @@ -14,6 +14,11 @@ * limitations under the License. **/ + +/** + * @mixin @node-red/editor-api_auth + */ + var passport = require("passport"); var oauth2orize = require("oauth2orize"); @@ -44,7 +49,14 @@ function init(_settings,storage) { Tokens.init(mergedAdminAuth,storage); } } - +/** + * Returns an Express middleware function that ensures the user making a request + * has the necessary permission. + * + * @param {String} permission - the permission required for the request, such as `flows.write` + * @return {Function} - an Express middleware + * @memberof @node-red/editor-api_auth + */ function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 6a7b91944..10c9912ed 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -14,6 +14,16 @@ * limitations under the License. **/ + + /** + * This module provides an Express application to serve the Node-RED editor. + * + * It implements the Node-RED HTTP Admin API the Editor uses to interact + * with the Node-RED runtime. + * + * @namespace @node-red/editor-api + */ + var express = require("express"); var bodyParser = require("body-parser"); var util = require('util'); @@ -28,6 +38,15 @@ var adminApp; var server; var editor; + +/** + * Initialise the module. + * @param {Object} settings The runtime settings + * @param {HTTPServer} server An instance of HTTP Server + * @param {Storage} storage An instance of Node-RED Storage + * @param {Runtime} runtimeAPI An instance of Node-RED Runtime + * @memberof @node-red/editor-api + */ function init(settings,_server,storage,runtimeAPI) { server = _server; if (settings.httpAdminRoot !== false) { @@ -80,6 +99,12 @@ function init(settings,_server,storage,runtimeAPI) { adminApp = null; } } + +/** + * Start the module. + * @return {Promise} resolves when the application is ready to handle requests + * @memberof @node-red/editor-api + */ function start() { if (editor) { return editor.start(); @@ -87,6 +112,12 @@ function start() { return when.resolve(); } } + +/** + * Stop the module. + * @return {Promise} resolves when the application is stopped + * @memberof @node-red/editor-api + */ function stop() { if (editor) { editor.stop(); @@ -97,8 +128,18 @@ module.exports = { init: init, start: start, stop: stop, + + /** + * @memberof @node-red/editor-api + * @mixes @node-red/editor-api_auth + */ auth: { needsPermission: auth.needsPermission }, + /** + * The Express app used to serve the Node-RED Editor + * @type ExpressApplication + * @memberof @node-red/editor-api + */ get httpAdmin() { return adminApp; } }; diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index a020a7827..2805534a2 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -14,6 +14,16 @@ * limitations under the License. **/ + + /** + * This module provides the node registry for the Node-RED runtime. + * + * It is responsible for loading node modules and making them available + * to the runtime. + * + * @namespace @node-red/registry + */ + var registry = require("./registry"); var loader = require("./loader"); var installer = require("./installer"); diff --git a/packages/node_modules/node-red/lib/red.js b/packages/node_modules/node-red/lib/red.js index 5d08a3ca4..b679ad138 100644 --- a/packages/node_modules/node-red/lib/red.js +++ b/packages/node_modules/node-red/lib/red.js @@ -122,6 +122,13 @@ module.exports = { */ util: redUtil.util, + + /** + * This provides access to the internal nodes module of the + * runtime. + * + * @memberof node-red + */ get nodes() { return runtime._.nodes }, /** @@ -131,6 +138,12 @@ module.exports = { */ events: runtime.events, + /** + * This provides access to the internal settings module of the + * runtime. + * + * @memberof node-red + */ get settings() { return runtime._.settings }, @@ -145,18 +158,21 @@ module.exports = { /** * The express application for the Editor Admin API + * @type ExpressApplication * @memberof node-red */ get httpAdmin() { return api.httpAdmin }, /** * The express application for HTTP Nodes + * @type ExpressApplication * @memberof node-red */ get httpNode() { return runtime.httpNode }, /** * The HTTP Server used by the runtime + * @type HTTPServer * @memberof node-red */ get server() { return server }, @@ -170,6 +186,7 @@ module.exports = { /** * The editor authentication api. + * @see @node-red/editor-api_auth * @memberof node-red */ auth: api.auth From ceba08a801a333a3a176a70bae324f87f482e96d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Jan 2019 15:27:40 +0000 Subject: [PATCH 4/9] Update dependencies and tidy up sentiment --- package.json | 9 +++-- .../@node-red/editor-api/package.json | 2 +- .../locales/de/analysis/72-sentiment.html | 35 ------------------- .../node_modules/@node-red/nodes/package.json | 5 ++- packages/node_modules/node-red/package.json | 2 +- 5 files changed, 8 insertions(+), 45 deletions(-) delete mode 100755 packages/node_modules/@node-red/nodes/locales/de/analysis/72-sentiment.html diff --git a/package.json b/package.json index 549ecf9c6..d99d80ac4 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ } ], "dependencies": { - "ajv": "6.6.2", + "ajv": "6.7.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.18.3", @@ -56,7 +56,7 @@ "node-red-node-feedparser": "^0.1.14", "node-red-node-rbe": "0.2.*", "node-red-node-sentiment": "^0.1.0", - "node-red-node-tail": "^0.0.1", + "node-red-node-tail": "^0.0.2", "node-red-node-twitter": "^1.1.0", "nopt": "4.0.1", "oauth2orize": "1.11.0", @@ -67,10 +67,9 @@ "raw-body": "2.3.3", "request": "2.88.0", "semver": "5.6.0", - "sentiment": "2.1.0", "uglify-js": "3.4.9", "when": "3.7.8", - "ws": "6.1.2", + "ws": "6.1.3", "xml2js": "0.4.19" }, "optionalDependencies": { @@ -106,7 +105,7 @@ "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.1.0", - "supertest": "3.3.0", + "supertest": "3.4.2", "wdio-chromedriver-service": "^0.1.5", "wdio-mocha-framework": "^0.6.4", "wdio-spec-reporter": "^0.1.5", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index acb97d660..6d80da2e2 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -32,6 +32,6 @@ "passport-oauth2-client-password": "0.1.2", "passport": "0.4.0", "when": "3.7.8", - "ws": "6.1.2" + "ws": "6.1.3" } } diff --git a/packages/node_modules/@node-red/nodes/locales/de/analysis/72-sentiment.html b/packages/node_modules/@node-red/nodes/locales/de/analysis/72-sentiment.html deleted file mode 100755 index 6077ce5d5..000000000 --- a/packages/node_modules/@node-red/nodes/locales/de/analysis/72-sentiment.html +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index bb8968c19..f0876e0bb 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -15,7 +15,7 @@ } ], "dependencies": { - "ajv": "6.6.2", + "ajv": "6.7.0", "body-parser": "1.18.3", "cheerio": "0.22.0", "cookie-parser": "1.4.3", @@ -36,8 +36,7 @@ "on-headers": "1.0.1", "raw-body": "2.3.3", "request": "2.88.0", - "sentiment": "2.1.0", - "ws": "6.1.2", + "ws": "6.1.3", "xml2js": "0.4.19" } } diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 2d3d6399f..bcc161e6a 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -43,7 +43,7 @@ "node-red-node-feedparser": "^0.1.14", "node-red-node-rbe": "0.2.*", "node-red-node-sentiment": "^0.1.0", - "node-red-node-tail": "^0.0.1", + "node-red-node-tail": "^0.0.2", "node-red-node-twitter": "^1.1.0", "nopt": "4.0.1", "semver": "5.6.0" From d23b32a83016ba96cafaf626b92138905cdefbf7 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Jan 2019 15:29:01 +0000 Subject: [PATCH 5/9] Bump to 0.20.0-beta.4 --- package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 4 ++-- packages/node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index d99d80ac4..00e96ea14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "description": "A visual tool for wiring the Internet of Things", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 6d80da2e2..f1aac7db0 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "0.20.0-beta.3", - "@node-red/editor-client": "0.20.0-beta.3", + "@node-red/util": "0.20.0-beta.4", + "@node-red/editor-client": "0.20.0-beta.4", "bcryptjs": "2.4.3", "body-parser": "1.18.3", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index cb21abd26..3d49757ce 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index f0876e0bb..f79a04df3 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 5a97dd083..f3b7edc5c 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "0.20.0-beta.3", + "@node-red/util": "0.20.0-beta.4", "semver": "5.6.0", "uglify-js": "3.4.9", "when": "3.7.8" diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 96e53edf7..2b0e908f5 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "0.20.0-beta.3", - "@node-red/util": "0.20.0-beta.3", + "@node-red/registry": "0.20.0-beta.4", + "@node-red/util": "0.20.0-beta.4", "clone": "2.1.2", "express": "4.16.4", "fs-extra": "7.0.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 422ba3140..681f40d69 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index bcc161e6a..4441c534b 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "0.20.0-beta.3", + "version": "0.20.0-beta.4", "description": "A visual tool for wiring the Internet of Things", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "0.20.0-beta.3", - "@node-red/runtime": "0.20.0-beta.3", - "@node-red/util": "0.20.0-beta.3", - "@node-red/nodes": "0.20.0-beta.3", + "@node-red/editor-api": "0.20.0-beta.4", + "@node-red/runtime": "0.20.0-beta.4", + "@node-red/util": "0.20.0-beta.4", + "@node-red/nodes": "0.20.0-beta.4", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.16.4", From d534a8952d4a0e26f589774b0402421b07dc430e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 29 Jan 2019 21:49:20 +0000 Subject: [PATCH 6/9] Do not propagate Flow.getNode to parent when called from outside flow --- .../@node-red/runtime/lib/nodes/flows/Flow.js | 13 ++-- .../runtime/lib/nodes/flows/index.js | 4 +- .../runtime/lib/nodes/flows/Flow_spec.js | 63 +++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index 20399f19d..7ca6afb77 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -281,11 +281,13 @@ class Flow { /** * Get a node instance from this flow. If the node is not known to this * flow, pass the request up to the parent. - * @param {[type]} id [description] + * @param {String} id [description] + * @param {Boolean} cancelBubble if true, prevents the flow from passing the request to the parent + * This stops infinite loops when the parent asked this Flow for the + * node to begin with. * @return {[type]} [description] */ - getNode(id) { - // console.log('getNode',id,!!this.activeNodes[id]) + getNode(id, cancelBubble) { if (!id) { return undefined; } @@ -298,7 +300,10 @@ class Flow { // TEMP: this is a subflow internal node within this flow return this.activeNodes[id]; } - return this.parent.getNode(id); + if (!cancelBubble) { + return this.parent.getNode(id); + } + return undefined; } /** diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js index 20c3274d1..4cce74e83 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js @@ -207,11 +207,11 @@ function setFlows(_config,type,muteLog,forceStart) { function getNode(id) { var node; if (activeNodesToFlow[id] && activeFlows[activeNodesToFlow[id]]) { - return activeFlows[activeNodesToFlow[id]].getNode(id); + return activeFlows[activeNodesToFlow[id]].getNode(id,true); } for (var flowId in activeFlows) { if (activeFlows.hasOwnProperty(flowId)) { - node = activeFlows[flowId].getNode(id); + node = activeFlows[flowId].getNode(id,true); if (node) { return node; } diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js index 14b1e58ab..feac88e22 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js @@ -413,6 +413,69 @@ describe('Flow', function() { }); }); + describe('#getNode',function() { + it("gets a node known to the flow",function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, + {id:"4",z:"t1",type:"test",foo:"a"} + ]); + var flow = Flow.create({},config,config.flows["t1"]); + flow.start(); + + Object.keys(flow.getActiveNodes()).should.have.length(4); + + flow.getNode('1').should.have.a.property('id','1'); + + flow.stop().then(() => { done() }); + }); + + it("passes to parent if node not known locally",function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, + {id:"4",z:"t1",type:"test",foo:"a"} + ]); + var flow = Flow.create({ + getNode: id => { return {id:id}} + },config,config.flows["t1"]); + flow.start(); + + Object.keys(flow.getActiveNodes()).should.have.length(4); + + flow.getNode('1').should.have.a.property('id','1'); + + flow.getNode('parentNode').should.have.a.property('id','parentNode'); + + + flow.stop().then(() => { done() }); + }); + + it("does not pass to parent if cancelBubble set",function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, + {id:"4",z:"t1",type:"test",foo:"a"} + ]); + var flow = Flow.create({ + getNode: id => { return {id:id}} + },config,config.flows["t1"]); + flow.start(); + + Object.keys(flow.getActiveNodes()).should.have.length(4); + + flow.getNode('1').should.have.a.property('id','1'); + + should.not.exist(flow.getNode('parentNode',true)); + flow.stop().then(() => { done() }); + }); + }); describe("#handleStatus",function() { it("passes a status event to the adjacent status node",function(done) { From 85de2270033f8f7908f78663464cb8a7ec04c964 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 30 Jan 2019 10:50:29 +0000 Subject: [PATCH 7/9] Make Node._flow a writeable property This is needed so an existing node constructor that does: Object.assign(this,config); works when it tries to replace this._flow with config._flow. --- packages/node_modules/@node-red/runtime/lib/nodes/Node.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index a0825f7b1..b7d44c7b5 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -38,7 +38,11 @@ function Node(n) { // Make this a non-enumerable property as it may cause // circular references. Any existing code that tries to JSON serialise // the object (such as dashboard) will not like circular refs - Object.defineProperty(this,'_flow', {value: n._flow, }) + // The value must still be writable in the case that a node does: + // Object.assign(this,config) + // as part of its constructure - config._flow will overwrite this._flow + // which we can tolerate as they are the same object. + Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) } this.updateWires(n.wires); } From efe8fbbd1170e9ed459f7f898f0dde9b297982e1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 30 Jan 2019 15:12:01 +0000 Subject: [PATCH 8/9] Better handling of multiple flow merges Fixes #2039 Keeps better track of what was merged so a subsequent merge properly identifies new-vs-old and doesn't remove thinks by mistake --- .../@node-red/editor-client/src/js/ui/diff.js | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js index 7815efc7d..bef2ffc69 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js @@ -687,7 +687,7 @@ RED.diff = (function() { diff: remoteDiff } } - + var selectState = ""; if (conflicted) { @@ -1158,19 +1158,19 @@ RED.diff = (function() { } }); - return { + var diff = { currentConfig: currentConfig, newConfig: newConfig, added: added, deleted: deleted, changed: changed, moved: moved - } + }; + return diff; } function resolveDiffs(localDiff,remoteDiff) { var conflicted = {}; var resolutions = {}; - var diff = { localDiff: localDiff, remoteDiff: remoteDiff, @@ -1348,7 +1348,7 @@ RED.diff = (function() { if (node) { nodeChangedStates[id] = node.changed; } - localChangedStates[id] = true; + localChangedStates[id] = 1; newConfig.push(remoteDiff.newConfig.all[id]); } } else { @@ -1363,7 +1363,7 @@ RED.diff = (function() { nodeChangedStates[id] = node.changed; } if (!localDiff.added.hasOwnProperty(id)) { - localChangedStates[id] = true; + localChangedStates[id] = 2; newConfig.push(remoteDiff.newConfig.all[id]); } } @@ -1376,24 +1376,42 @@ RED.diff = (function() { } function mergeDiff(diff) { + //console.log(diff); var appliedDiff = applyDiff(diff); var newConfig = appliedDiff.config; var nodeChangedStates = appliedDiff.nodeChangedStates; var localChangedStates = appliedDiff.localChangedStates; + var isDirty = RED.nodes.dirty(); + var historyEvent = { t:"replace", config: RED.nodes.createCompleteNodeSet(), changed: nodeChangedStates, - dirty: RED.nodes.dirty(), + dirty: isDirty, rev: RED.nodes.version() } RED.history.push(historyEvent); + var originalFlow = RED.nodes.originalFlow(); + // originalFlow is what the editor things it loaded + // - add any newly added nodes from remote diff as they are now part of the record + for (var id in diff.remoteDiff.added) { + if (diff.remoteDiff.added.hasOwnProperty(id)) { + if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { + originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); + } + } + } + RED.nodes.clear(); var imported = RED.nodes.import(newConfig); + + // Restore the original flow so subsequent merge resolutions can properly + // identify new-vs-old + RED.nodes.originalFlow(originalFlow); imported[0].forEach(function(n) { if (nodeChangedStates[n.id] || localChangedStates[n.id]) { n.changed = true; @@ -1402,11 +1420,16 @@ RED.diff = (function() { RED.nodes.version(diff.remoteDiff.rev); + if (isDirty) { + RED.nodes.dirty(true); + } + RED.view.redraw(true); RED.palette.refresh(); RED.workspaces.refresh(); RED.sidebar.config.refresh(); } + function showTestFlowDiff(index) { if (index === 1) { var localFlow = RED.nodes.createCompleteNodeSet(); From 3bcff91328ef4de44643c7ceb9d93745b5fd13ae Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Feb 2019 23:44:50 +0000 Subject: [PATCH 9/9] Add Status Node to Subflow to allow subflow-specific status Closes #597 --- .../editor-client/locales/en-US/editor.json | 1 + .../@node-red/editor-client/src/js/history.js | 29 +++- .../@node-red/editor-client/src/js/nodes.js | 31 ++++ .../editor-client/src/js/ui/subflow.js | 135 ++++++++++++--- .../@node-red/editor-client/src/js/ui/view.js | 86 +++++++++- .../src/sass/workspaceToolbar.scss | 9 + .../@node-red/runtime/lib/nodes/flows/Flow.js | 1 - .../runtime/lib/nodes/flows/Subflow.js | 88 +++++++++- .../runtime/lib/nodes/flows/Subflow_spec.js | 161 +++++++++++++++++- 9 files changed, 500 insertions(+), 41 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 1fff8b87e..6a06d3094 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 @@ -273,6 +273,7 @@ "editSubflowProperties": "edit properties", "input": "inputs:", "output": "outputs:", + "status": "status node", "deleteSubflow": "delete subflow", "info": "Description", "category": "Category", diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 95cc78292..2e256564c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -125,14 +125,20 @@ RED.history = (function() { }); } } - if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { - ev.subflow.instances.forEach(function(n) { - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); + if (ev.subflow) { + if (ev.subflow.hasOwnProperty('instances')) { + ev.subflow.instances.forEach(function(n) { + var node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + if (ev.subflow.hasOwnProperty('status')) { + subflow = RED.nodes.subflow(ev.subflow.id); + subflow.status = ev.subflow.status; + } } if (subflow) { RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { @@ -232,6 +238,11 @@ RED.history = (function() { } }); } + if (ev.subflow.hasOwnProperty('status')) { + if (ev.subflow.status) { + delete ev.node.status; + } + } RED.editor.validateNode(ev.node); RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { n.inputs = ev.node.in.length; @@ -290,6 +301,7 @@ RED.history = (function() { RED.workspaces.order(ev.order); } } + Object.keys(modifiedTabs).forEach(function(id) { var subflow = RED.nodes.subflow(id); if (subflow) { @@ -303,6 +315,7 @@ RED.history = (function() { RED.palette.refresh(); RED.workspaces.refresh(); RED.sidebar.config.refresh(); + RED.subflow.refresh(); } } 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 d9ef6ab44..b531f89f0 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 @@ -571,6 +571,18 @@ RED.nodes = (function() { node.icon = n.icon; } } + if (n.status) { + node.status = {x: n.status.x, y: n.status.y, wires:[]}; + links.forEach(function(d) { + if (d.target === n.status) { + if (d.source.type != "subflow") { + node.status.wires.push({id:d.source.id, port:d.sourcePort}) + } else { + node.status.wires.push({id:n.id, port:0}) + } + } + }); + } return node; } @@ -851,6 +863,12 @@ RED.nodes = (function() { output.i = i; output.id = getID(); }); + if (n.status) { + n.status.type = "subflow"; + n.status.direction = "status"; + n.status.z = n.id; + n.status.id = getID(); + } new_subflows.push(n); addSubflow(n,createNewIds); } @@ -1189,6 +1207,19 @@ RED.nodes = (function() { }); delete output.wires; }); + if (n.status) { + n.status.wires.forEach(function(wire) { + var link; + if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) { + link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status}; + } else { + link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status}; + } + addLink(link); + new_links.push(link); + }); + delete n.status.wires; + } } RED.workspaces.refresh(); 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 ba303152f..eb179c205 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -16,7 +16,6 @@ RED.subflow = (function() { - var _subflowEditTemplate = ''; var _subflowTemplateEditTemplate = ''; - - function getSubflow() { - return RED.nodes.subflow(RED.workspaces.active()); - } - function findAvailableSubflowIOPosition(subflow,isInput) { var pos = {x:50,y:30}; if (!isInput) { pos.x += 110; } - for (var i=0;i ').appendTo(toolbar); + + // Inputs $(' '+ '
'+ '0'+ '1'+ '
').appendTo(toolbar); + // Outputs $('
'+ ''+ '
3
'+ ''+ '
').appendTo(toolbar); + // Status + $('').appendTo(toolbar); + // $(' ').appendTo(toolbar); // $(' ').appendTo(toolbar); + + // Delete $(' ').appendTo(toolbar); + toolbar.i18n(); @@ -274,6 +339,7 @@ RED.subflow = (function() { RED.view.redraw(true); } }); + $("#workspace-subflow-output-add").click(function(event) { event.preventDefault(); addSubflowOutput(); @@ -283,6 +349,7 @@ RED.subflow = (function() { event.preventDefault(); addSubflowInput(); }); + $("#workspace-subflow-input-remove").click(function(event) { event.preventDefault(); var wasDirty = RED.nodes.dirty(); @@ -307,6 +374,33 @@ RED.subflow = (function() { } }); + $("#workspace-subflow-status").change(function(evt) { + if (this.checked) { + addSubflowStatus(); + } else { + var currentStatus = activeSubflow.status; + var wasChanged = activeSubflow.changed; + var result = removeSubflowStatus(); + if (result) { + activeSubflow.changed = true; + var wasDirty = RED.nodes.dirty(); + RED.history.push({ + t:'delete', + links:result.links, + changed: wasChanged, + dirty:wasDirty, + subflow: { + id: activeSubflow.id, + status: currentStatus + } + }); + RED.view.select(); + RED.nodes.dirty(true); + RED.view.redraw(); + } + } + }) + $("#workspace-subflow-edit").click(function(event) { RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active())); event.preventDefault(); @@ -328,6 +422,7 @@ RED.subflow = (function() { $("#chart").css({"margin-top": "40px"}); $("#workspace-toolbar").show(); } + function hideWorkspaceToolbar() { $("#workspace-toolbar").hide().empty(); $("#chart").css({"margin-top": "0"}); @@ -373,6 +468,7 @@ RED.subflow = (function() { subflows: [activeSubflow] } } + function init() { RED.events.on("workspace:change",function(event) { var activeSubflow = RED.nodes.subflow(event.workspace); @@ -436,6 +532,7 @@ RED.subflow = (function() { } return x; } + function convertToSubflow() { var selection = RED.view.selection(); if (!selection.nodes) { @@ -451,7 +548,6 @@ RED.subflow = (function() { var candidateOutputs = []; var candidateInputNodes = {}; - var boundingBox = [selection.nodes[0].x, selection.nodes[0].y, selection.nodes[0].x, @@ -467,8 +563,8 @@ RED.subflow = (function() { Math.max(boundingBox[3],n.y) ] } - var offsetX = snapToGrid(boundingBox[0] - 180); - var offsetY = snapToGrid(boundingBox[1] - 60); + var offsetX = snapToGrid(boundingBox[0] - 200); + var offsetY = snapToGrid(boundingBox[1] - 80); var center = [ @@ -643,8 +739,6 @@ RED.subflow = (function() { RED.view.redraw(true); } - - return { init: init, createSubflow: createSubflow, @@ -652,6 +746,7 @@ RED.subflow = (function() { removeSubflow: removeSubflow, refresh: refresh, removeInput: removeSubflowInput, - removeOutput: removeSubflowOutput + removeOutput: removeSubflowOutput, + removeStatus: removeSubflowStatus } })(); 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 9405b60dd..4eca5b1c5 100644 --- 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 @@ -1261,6 +1261,13 @@ RED.view = (function() { moving_set.push({n:n}); } }); + if (activeSubflow.status) { + activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); + if (activeSubflow.status.selected) { + activeSubflow.status.dirty = true; + moving_set.push({n:activeSubflow.status}); + } + } } updateSelection(); lasso.remove(); @@ -1367,6 +1374,13 @@ RED.view = (function() { moving_set.push({n:n}); } }); + if (activeSubflow.status) { + if (!activeSubflow.status.selected) { + activeSubflow.status.selected = true; + activeSubflow.status.dirty = true; + moving_set.push({n:activeSubflow.status}); + } + } } selected_link = null; @@ -1552,6 +1566,7 @@ RED.view = (function() { var removedLinks = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; + var removedSubflowStatus = undefined; var subflowInstances = []; var startDirty = RED.nodes.dirty(); @@ -1573,6 +1588,8 @@ RED.view = (function() { removedSubflowOutputs.push(node); } else if (node.direction === "in") { removedSubflowInputs.push(node); + } else if (node.direction === "status") { + removedSubflowStatus = node; } node.dirty = true; } @@ -1590,12 +1607,19 @@ RED.view = (function() { removedLinks = removedLinks.concat(result.links); } } + if (removedSubflowStatus) { + result = RED.subflow.removeStatus(); + if (result) { + removedLinks = removedLinks.concat(result.links); + } + } + var instances = RED.subflow.refresh(true); if (instances) { subflowInstances = instances.instances; } moving_set = []; - if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) { + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) { RED.nodes.dirty(true); } } @@ -1651,10 +1675,14 @@ RED.view = (function() { subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { + id: activeSubflow?activeSubflow.id:undefined, instances: subflowInstances }, dirty:startDirty }; + if (removedSubflowStatus) { + historyEvent.subflow.status = removedSubflowStatus; + } } RED.history.push(historyEvent); @@ -2420,6 +2448,49 @@ RED.view = (function() { inGroup.append("svg:text").attr("class","port_label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); + var subflowStatus = nodeLayer.selectAll(".subflowstatus").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); + subflowStatus.exit().remove(); + + var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","node subflowstatus").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); + statusGroup.each(function(d,i) { + d.w=40; + d.h=40; + }); + statusGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) + // TODO: This is exactly the same set of handlers used for regular nodes - DRY + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",function(d) { + var obj = d3.select(this); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + touchStartTime = setTimeout(function() { + showTouchMenu(obj,pos); + },touchLongPressTimeout); + nodeMouseDown.call(this,d) + }) + .on("touchend", function(d) { + clearTimeout(touchStartTime); + touchStartTime = null; + if (RED.touch.radialMenu.active()) { + d3.event.stopPropagation(); + return; + } + nodeMouseUp.call(this,d); + }); + + statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) + .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); + + statusGroup.append("svg:text").attr("class","port_label").attr("x",22).attr("y",20).style("font-size","10px").text("status"); + subflowOutputs.each(function(d,i) { if (d.dirty) { var output = d3.select(this); @@ -2439,9 +2510,22 @@ RED.view = (function() { d.dirty = false; } }); + subflowStatus.each(function(d,i) { + if (d.dirty) { + var output = d3.select(this); + output.selectAll(".subflowport").classed("node_selected",function(d) { return d.selected; }) + output.selectAll(".port_index").text(function(d){ return d.i+1}); + output.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); + dirtyNodes[d.id] = d; + d.dirty = false; + } + }); + + } else { nodeLayer.selectAll(".subflowoutput").remove(); nodeLayer.selectAll(".subflowinput").remove(); + nodeLayer.selectAll(".subflowstatus").remove(); } var node = nodeLayer.selectAll(".nodegroup").data(activeNodes,function(d){return d.id}); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss index 26d45bcf8..3224707fe 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss @@ -33,6 +33,15 @@ transition: right 0.2s ease; overflow: hidden; + label { + padding: 1px 8px; + margin: 0; + font-size: 12px; + } + input[type="checkbox"] { + margin: 0 3px 0 0 ; + padding: 0; + } .button { @include workspace-button; margin-right: 10px; diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index 7ca6afb77..ca555a5a5 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -265,7 +265,6 @@ class Flow { return Promise.all(promises); } - /** * Update the flow definition. This doesn't change anything that is running. * This should be called after `stop` and before `start`. 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 cf393868e..24ae72a49 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 @@ -17,6 +17,8 @@ const clone = require("clone"); const Flow = require('./Flow').Flow; +const util = require("util"); + const redUtil = require("@node-red/util").util; const flowUtil = require("./util"); @@ -104,6 +106,40 @@ class Subflow extends Flow { var self = this; // Create a subflow node to accept inbound messages and route appropriately var Node = require("../Node"); + + if (this.subflowDef.status) { + var subflowStatusConfig = { + id: this.subflowInstance.id+":status", + type: "subflow-status", + z: this.subflowInstance.id, + _flow: this.parent + } + this.statusNode = new Node(subflowStatusConfig); + this.statusNode.on("input", function(msg) { + if (msg.payload !== undefined) { + if (typeof msg.payload === "string") { + // if msg.payload is a String, use it as status text + self.node.status({text:msg.payload}) + return; + } else if (Object.prototype.toString.call(msg.payload) === "[object Object]") { + if (msg.payload.hasOwnProperty('text') || msg.payload.hasOwnProperty('fill') || msg.payload.hasOwnProperty('shape') || Object.keys(msg.payload).length === 0) { + // msg.payload is an object that looks like a status object + self.node.status(msg.payload); + return; + } + } + // Anything else - inspect it and use as status text + var text = util.inspect(msg.payload); + if (text.length > 32) { text = text.substr(0,32) + "..."; } + self.node.status({text:text}); + } else if (msg.status !== undefined) { + // if msg.status exists + self.node.status(msg.status) + } + }) + } + + var subflowInstanceConfig = { id: this.subflowInstance.id, type: this.subflowInstance.type, @@ -168,7 +204,6 @@ class Subflow extends Flow { // Wire the subflow outputs if (this.subflowDef.out) { - var modifiedNodes = {}; for (var i=0;i