mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge branch 'dev' into pr_2042
This commit is contained in:
		
							
								
								
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Gruntfile.js
									
									
									
									
									
								
							| @@ -15,6 +15,7 @@ | ||||
|  **/ | ||||
|  | ||||
| var path = require("path"); | ||||
| var fs = require("fs-extra"); | ||||
|  | ||||
| module.exports = function(grunt) { | ||||
|  | ||||
| @@ -442,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' | ||||
| @@ -553,6 +556,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 +583,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', | ||||
|   | ||||
| @@ -15,7 +15,6 @@ | ||||
|     }, | ||||
|     "templates": { | ||||
|         "systemName": "Node-RED Runtime API", | ||||
|         "theme":"yeti", | ||||
|         "footer": "", | ||||
|         "copyright": "Released under the Apache License v2.0", | ||||
|         "default": { | ||||
|   | ||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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", | ||||
| @@ -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,17 +67,15 @@ | ||||
|         "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": { | ||||
|         "bcrypt": "~2.0.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "chromedriver": "2.45.0", | ||||
|         "grunt": "~1.0.3", | ||||
|         "grunt-chmod": "~1.1.1", | ||||
|         "grunt-cli": "~1.3.2", | ||||
| @@ -107,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", | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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; } | ||||
| }; | ||||
|   | ||||
| @@ -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", | ||||
| @@ -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" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -273,6 +273,7 @@ | ||||
|         "editSubflowProperties": "edit properties", | ||||
|         "input": "inputs:", | ||||
|         "output": "outputs:", | ||||
|         "status": "status node", | ||||
|         "deleteSubflow": "delete subflow", | ||||
|         "info": "Description", | ||||
|         "category": "Category", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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; | ||||
| @@ -262,6 +273,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; | ||||
|                     }); | ||||
| @@ -288,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) { | ||||
| @@ -301,6 +315,7 @@ RED.history = (function() { | ||||
|             RED.palette.refresh(); | ||||
|             RED.workspaces.refresh(); | ||||
|             RED.sidebar.config.refresh(); | ||||
|             RED.subflow.refresh(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -575,6 +575,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; | ||||
|     } | ||||
| @@ -855,6 +867,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); | ||||
|                 } | ||||
| @@ -1194,6 +1212,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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -16,12 +16,12 @@ | ||||
|  | ||||
| RED.subflow = (function() { | ||||
|  | ||||
|  | ||||
|     var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+ | ||||
|         '<div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div>'+ | ||||
|         '<div class="form-row" style="margin-bottom: 0px;"><label style="width: auto;" data-i18n="[append]editor:editor-tab.env"><i class="fa fa-th-list"></i> </label></div>'+ | ||||
|         '<div class="form-row node-input-env-container-row"><ol id="node-input-env-container"></ol></div>'+ | ||||
|         '</script>'; | ||||
|  | ||||
|     var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template">'+ | ||||
|         '<div class="form-row"><i class="fa fa-tag"></i> <label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name"></div>'+ | ||||
|         '<div class="form-row"><i class="fa fa-folder-o"></i> <label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category"></div>'+ | ||||
| @@ -30,26 +30,22 @@ RED.subflow = (function() { | ||||
|         '<div class="form-row form-tips" id="subflow-dialog-user-count"></div>'+ | ||||
|         '</script>'; | ||||
|  | ||||
|  | ||||
|     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<subflow.out.length+subflow.in.length;i++) { | ||||
|             var port; | ||||
|             if (i < subflow.out.length) { | ||||
|                 port = subflow.out[i]; | ||||
|             } else { | ||||
|                 port = subflow.in[i-subflow.out.length]; | ||||
|             } | ||||
|         var ports = [].concat(subflow.out).concat(subflow.in); | ||||
|         if (subflow.status) { | ||||
|             ports.push(subflow.status); | ||||
|         } | ||||
|         ports.sort(function(A,B) { | ||||
|             return A.x-B.x; | ||||
|         }); | ||||
|         for (var i=0; i<ports.length; i++) { | ||||
|             var port = ports[i]; | ||||
|             if (port.x == pos.x && port.y == pos.y) { | ||||
|                 pos.x += 55; | ||||
|                 i=0; | ||||
|             } | ||||
|         } | ||||
|         return pos; | ||||
| @@ -197,6 +193,61 @@ RED.subflow = (function() { | ||||
|         return {subflowOutputs: removedSubflowOutputs, links: removedLinks} | ||||
|     } | ||||
|  | ||||
|     function addSubflowStatus() { | ||||
|         var subflow = RED.nodes.subflow(RED.workspaces.active()); | ||||
|         if (subflow.status) { | ||||
|             return; | ||||
|         } | ||||
|         var position = findAvailableSubflowIOPosition(subflow,false); | ||||
|         var statusNode = { | ||||
|             type:"subflow", | ||||
|             direction:"status", | ||||
|             z:subflow.id, | ||||
|             x:position.x, | ||||
|             y:position.y, | ||||
|             id:RED.nodes.id() | ||||
|         }; | ||||
|         subflow.status = statusNode; | ||||
|         subflow.dirty = true; | ||||
|         var wasDirty = RED.nodes.dirty(); | ||||
|         var wasChanged = subflow.changed; | ||||
|         subflow.changed = true; | ||||
|         var result = refresh(true); | ||||
|         var historyEvent = { | ||||
|             t:'edit', | ||||
|             node:subflow, | ||||
|             dirty:wasDirty, | ||||
|             changed:wasChanged, | ||||
|             subflow: { status: true } | ||||
|         }; | ||||
|         RED.history.push(historyEvent); | ||||
|         RED.view.select(); | ||||
|         RED.nodes.dirty(true); | ||||
|         RED.view.redraw(); | ||||
|         $("#workspace-subflow-status").prop("checked",!!subflow.status); | ||||
|         $("#workspace-subflow-status").parent().parent().toggleClass("active",!!subflow.status); | ||||
|     } | ||||
|  | ||||
|     function removeSubflowStatus() { | ||||
|         var subflow = RED.nodes.subflow(RED.workspaces.active()); | ||||
|         if (!subflow.status) { | ||||
|             return; | ||||
|         } | ||||
|         var subflowRemovedLinks = []; | ||||
|         RED.nodes.eachLink(function(l) { | ||||
|             if (l.target.type == "subflow" && l.target.z == subflow.id && l.target.direction == "status") { | ||||
|                 subflowRemovedLinks.push(l); | ||||
|             } | ||||
|         }); | ||||
|         subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)}); | ||||
|         delete subflow.status; | ||||
|  | ||||
|         $("#workspace-subflow-status").prop("checked",!!subflow.status); | ||||
|         $("#workspace-subflow-status").parent().parent().toggleClass("active",!!subflow.status); | ||||
|  | ||||
|         return { links: subflowRemovedLinks } | ||||
|     } | ||||
|  | ||||
|     function refresh(markChange) { | ||||
|         var activeSubflow = RED.nodes.subflow(RED.workspaces.active()); | ||||
|         refreshToolbar(activeSubflow); | ||||
| @@ -225,12 +276,17 @@ RED.subflow = (function() { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function refreshToolbar(activeSubflow) { | ||||
|         if (activeSubflow) { | ||||
|             $("#workspace-subflow-input-add").toggleClass("active", activeSubflow.in.length !== 0); | ||||
|             $("#workspace-subflow-input-remove").toggleClass("active",activeSubflow.in.length === 0); | ||||
|  | ||||
|             $("#workspace-subflow-output .spinner-value").text(activeSubflow.out.length); | ||||
|  | ||||
|             $("#workspace-subflow-status").prop("checked",!!activeSubflow.status); | ||||
|             $("#workspace-subflow-status").parent().parent().toggleClass("active",!!activeSubflow.status); | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -238,22 +294,32 @@ RED.subflow = (function() { | ||||
|         var toolbar = $("#workspace-toolbar"); | ||||
|         toolbar.empty(); | ||||
|  | ||||
|         // Edit properties | ||||
|         $('<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowProperties"><i class="fa fa-pencil"></i> </a>').appendTo(toolbar); | ||||
|  | ||||
|         // Inputs | ||||
|         $('<span style="margin-left: 5px;" data-i18n="subflow.input"></span> '+ | ||||
|             '<div style="display: inline-block;" class="button-group">'+ | ||||
|             '<a id="workspace-subflow-input-remove" class="button active" href="#">0</a>'+ | ||||
|             '<a id="workspace-subflow-input-add" class="button" href="#">1</a>'+ | ||||
|             '</div>').appendTo(toolbar); | ||||
|  | ||||
|         // Outputs | ||||
|         $('<span style="margin-left: 5px;" data-i18n="subflow.output"></span> <div id="workspace-subflow-output" style="display: inline-block;" class="button-group spinner-group">'+ | ||||
|             '<a id="workspace-subflow-output-remove" class="button" href="#"><i class="fa fa-minus"></i></a>'+ | ||||
|             '<div class="spinner-value">3</div>'+ | ||||
|             '<a id="workspace-subflow-output-add" class="button" href="#"><i class="fa fa-plus"></i></a>'+ | ||||
|             '</div>').appendTo(toolbar); | ||||
|  | ||||
|         // Status | ||||
|         $('<span class="button-group"><span class="button" style="padding:0"><label for="workspace-subflow-status"><input id="workspace-subflow-status" type="checkbox"> <span data-i18n="subflow.status"></span></label></span></span>').appendTo(toolbar); | ||||
|  | ||||
|         // $('<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>').appendTo(toolbar); | ||||
|         // $('<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>').appendTo(toolbar); | ||||
|  | ||||
|         // Delete | ||||
|         $('<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar); | ||||
|  | ||||
|         toolbar.i18n(); | ||||
|  | ||||
|  | ||||
| @@ -280,6 +346,7 @@ RED.subflow = (function() { | ||||
|                 RED.view.redraw(true); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         $("#workspace-subflow-output-add").click(function(event) { | ||||
|             event.preventDefault(); | ||||
|             addSubflowOutput(); | ||||
| @@ -289,6 +356,7 @@ RED.subflow = (function() { | ||||
|             event.preventDefault(); | ||||
|             addSubflowInput(); | ||||
|         }); | ||||
|  | ||||
|         $("#workspace-subflow-input-remove").click(function(event) { | ||||
|             event.preventDefault(); | ||||
|             var wasDirty = RED.nodes.dirty(); | ||||
| @@ -313,6 +381,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(); | ||||
| @@ -334,6 +429,7 @@ RED.subflow = (function() { | ||||
|         $("#chart").css({"margin-top": "40px"}); | ||||
|         $("#workspace-toolbar").show(); | ||||
|     } | ||||
|  | ||||
|     function hideWorkspaceToolbar() { | ||||
|         $("#workspace-toolbar").hide().empty(); | ||||
|         $("#chart").css({"margin-top": "0"}); | ||||
| @@ -379,6 +475,7 @@ RED.subflow = (function() { | ||||
|             subflows: [activeSubflow] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function init() { | ||||
|         RED.events.on("workspace:change",function(event) { | ||||
|             var activeSubflow = RED.nodes.subflow(event.workspace); | ||||
| @@ -436,6 +533,13 @@ 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) { | ||||
| @@ -451,7 +555,6 @@ RED.subflow = (function() { | ||||
|         var candidateOutputs = []; | ||||
|         var candidateInputNodes = {}; | ||||
|  | ||||
|  | ||||
|         var boundingBox = [selection.nodes[0].x, | ||||
|             selection.nodes[0].y, | ||||
|             selection.nodes[0].x, | ||||
| @@ -467,8 +570,14 @@ RED.subflow = (function() { | ||||
|                 Math.max(boundingBox[3],n.y) | ||||
|             ] | ||||
|         } | ||||
|         var offsetX = snapToGrid(boundingBox[0] - 200); | ||||
|         var offsetY = snapToGrid(boundingBox[1] - 80); | ||||
|  | ||||
|         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]) { | ||||
| @@ -525,8 +634,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(), | ||||
| @@ -535,8 +644,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(), | ||||
| @@ -611,6 +720,8 @@ RED.subflow = (function() { | ||||
|                     return isLocalLink; | ||||
|                 }); | ||||
|             } | ||||
|             n.x -= offsetX; | ||||
|             n.y -= offsetY; | ||||
|             n.z = subflow.id; | ||||
|         } | ||||
|  | ||||
| @@ -619,7 +730,9 @@ RED.subflow = (function() { | ||||
|             nodes:[subflowInstance.id], | ||||
|             links:new_links, | ||||
|             subflow: { | ||||
|                 subflow: subflow | ||||
|                 subflow: subflow, | ||||
|                 offsetX: offsetX, | ||||
|                 offsetY: offsetY | ||||
|             }, | ||||
|  | ||||
|             activeWorkspace: RED.workspaces.active(), | ||||
| @@ -633,8 +746,6 @@ RED.subflow = (function() { | ||||
|         RED.view.redraw(true); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     return { | ||||
|         init: init, | ||||
|         createSubflow: createSubflow, | ||||
| @@ -642,6 +753,7 @@ RED.subflow = (function() { | ||||
|         removeSubflow: removeSubflow, | ||||
|         refresh: refresh, | ||||
|         removeInput: removeSubflowInput, | ||||
|         removeOutput: removeSubflowOutput | ||||
|         removeOutput: removeSubflowOutput, | ||||
|         removeStatus: removeSubflowStatus | ||||
|     } | ||||
| })(); | ||||
|   | ||||
| @@ -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}); | ||||
| @@ -2703,6 +2787,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 +3090,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 +3106,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; | ||||
|                     }); | ||||
|                 } | ||||
|             }) | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -1,35 +0,0 @@ | ||||
| <!-- | ||||
|   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. | ||||
| --> | ||||
|  | ||||
| <script type="text/x-red" data-help-name="sentiment"> | ||||
|     <p> Analysiert die ausgewählte Eigenschaft aus dem <code>msg</code> Objekt und fügt ein <code>sentiment</code> Objekt hinzu. </p> | ||||
|     <h3> Ausgaben </h3> | ||||
|     <dl class="message-properties"> | ||||
|         <dt> sentiment  <span class="property-type"> Objekt </span> </dt> | ||||
|         <dd> enthält die resultierende Stimmungslage AFINN-111. </dd> | ||||
|         <dt> sentiment.score  <span class="property-type"> Zahl </span> </dt> | ||||
|         <dd> die Sentiment-Bewertung. </dd> | ||||
|     </dl> | ||||
|     <h3> Eingaben </h3> | ||||
|     <dl class="message-properties"> | ||||
|         <dt> überschreibt  <span class="property-type"> Objekt </span> </dt> | ||||
|         <dd> Ein Objekt mit Wort-Überschreibungen kann angegeben werden- <code> { word:score, ... } </code>. </dd> | ||||
|     </dl> | ||||
|     <h3> Details </h3> | ||||
|     <p> Eine Bewertung größer als Null ist positiv und kleiner als null ist negativ. </p> | ||||
|     <p> Die Bewertung liegt in der Regel im Bereich von -5 bis +5, kann jedoch höher und niedriger sein. </p> | ||||
|     <p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_blank">the Sentiment docs here</a>.</p> | ||||
| </script> | ||||
| @@ -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", | ||||
| @@ -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" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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"); | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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`. | ||||
| @@ -281,11 +280,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 +299,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; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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"); | ||||
|  | ||||
| @@ -113,6 +115,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, | ||||
| @@ -177,7 +213,6 @@ class Subflow extends Flow { | ||||
|  | ||||
|         // Wire the subflow outputs | ||||
|         if (this.subflowDef.out) { | ||||
|             var modifiedNodes = {}; | ||||
|             for (var i=0;i<this.subflowDef.out.length;i++) { | ||||
|                 // i: the output index | ||||
|                 // This is what this Output is wired to | ||||
| @@ -189,7 +224,6 @@ class Subflow extends Flow { | ||||
|                         this.node._updateWires(subflowInstanceConfig.wires); | ||||
|                     } else { | ||||
|                         var node = self.node_map[wires[j].id]; | ||||
|                         modifiedNodes[node.id] = node; | ||||
|                         if (!node._originalWires) { | ||||
|                             node._originalWires = clone(node.wires); | ||||
|                         } | ||||
| @@ -198,6 +232,26 @@ class Subflow extends Flow { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (this.subflowDef.status) { | ||||
|             var subflowStatusId = this.statusNode.id; | ||||
|             wires = this.subflowDef.status.wires; | ||||
|             for (var j=0;j<wires.length;j++) { | ||||
|                 if (wires[j].id === this.subflowDef.id) { | ||||
|                     // A subflow input wired straight to a subflow output | ||||
|                     subflowInstanceConfig.wires[wires[j].port].push(subflowStatusId); | ||||
|                     this.node._updateWires(subflowInstanceConfig.wires); | ||||
|                 } else { | ||||
|                     var node = self.node_map[wires[j].id]; | ||||
|                     if (!node._originalWires) { | ||||
|                         node._originalWires = clone(node.wires); | ||||
|                     } | ||||
|                     node.wires[wires[j].port] = (node.wires[wires[j].port]||[]); | ||||
|                     node.wires[wires[j].port].push(subflowStatusId); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         super.start(diff); | ||||
|     } | ||||
|  | ||||
| @@ -227,6 +281,23 @@ class Subflow extends Flow { | ||||
|         return undefined; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get a node instance from this subflow. | ||||
|      * If the subflow has a status node, check for that, otherwise use | ||||
|      * the super-class function | ||||
|      * @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, cancelBubble) { | ||||
|         if (this.statusNode && this.statusNode.id === id) { | ||||
|             return this.statusNode; | ||||
|         } | ||||
|         return super.getNode(id,cancelBubble); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handle a status event from a node within this flow. | ||||
|      * @param  {Node}    node          The original node that triggered the event | ||||
| @@ -240,10 +311,14 @@ class Subflow extends Flow { | ||||
|     handleStatus(node,statusMessage,reportingNode,muteStatus) { | ||||
|         let handled = super.handleStatus(node,statusMessage,reportingNode,muteStatus); | ||||
|         if (!handled) { | ||||
|             // No status node on this subflow caught the status message. | ||||
|             // Pass up to the parent with this subflow's instance as the | ||||
|             // reporting node | ||||
|             handled = this.parent.handleStatus(node,statusMessage,this.node,true); | ||||
|             if (!this.statusNode || node === this.node) { | ||||
|                 // No status node on this subflow caught the status message. | ||||
|                 // AND there is no Subflow Status node - so the user isn't | ||||
|                 // wanting to manage status messages themselves | ||||
|                 // Pass up to the parent with this subflow's instance as the | ||||
|                 // reporting node | ||||
|                 handled = this.parent.handleStatus(node,statusMessage,this.node,true); | ||||
|             } | ||||
|         } | ||||
|         return handled; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|             } | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
							
								
								
									
										17
									
								
								packages/node_modules/node-red/lib/red.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								packages/node_modules/node-red/lib/red.js
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -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", | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -298,7 +298,6 @@ describe('Subflow', function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("instantiates a subflow inside a subflow and stops it",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
| @@ -452,7 +451,6 @@ describe('Subflow', function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
| @@ -490,9 +488,164 @@ describe('Subflow', function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     describe("status node", function() { | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 { | ||||
|                     id:"sf1", | ||||
|                     type:"subflow", | ||||
|                     name:"Subflow 2", | ||||
|                     info:"", | ||||
|                     in:[{wires:[{id:"sf1-1"}]}], | ||||
|                     out:[{wires:[{id:"sf1-1",port:0}]}], | ||||
|                     status:{wires:[{id:"sf1-1", port:0}]} | ||||
|                 }, | ||||
|                 {id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test-payload"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","test-payload"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 { | ||||
|                     id:"sf1", | ||||
|                     type:"subflow", | ||||
|                     name:"Subflow 2", | ||||
|                     info:"", | ||||
|                     in:[{wires:[{id:"sf1-1"}]}], | ||||
|                     out:[{wires:[{id:"sf1-1",port:0}]}], | ||||
|                     status:{wires:[{id:"sf1-1", port:0}]} | ||||
|                 }, | ||||
|                 {id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:{text:"payload-obj"}}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","payload-obj"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 { | ||||
|                     id:"sf1", | ||||
|                     type:"subflow", | ||||
|                     name:"Subflow 2", | ||||
|                     info:"", | ||||
|                     in:[{wires:[{id:"sf1-1"}]}], | ||||
|                     out:[{wires:[{id:"sf1-1",port:0}]}], | ||||
|                     status:{wires:[{id:"sf1-1", port:0}]} | ||||
|                 }, | ||||
|                 {id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({status:{text:"status-obj"}}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","status-obj"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("does not emit a regular status event if it contains a subflow-status node", function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 { | ||||
|                     id:"sf1", | ||||
|                     type:"subflow", | ||||
|                     name:"Subflow 2", | ||||
|                     info:"", | ||||
|                     in:[{wires:[{id:"sf1-1"}]}], | ||||
|                     out:[{wires:[{id:"sf1-1",port:0}]}], | ||||
|                     status:{wires:[]} | ||||
|                 }, | ||||
|                 {id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test-payload"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",0); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|     }) | ||||
|  | ||||
|     describe("#handleError",function() { | ||||
|         it("passes an error event to the subflow's parent tab catch node - all scope",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
| @@ -526,7 +679,6 @@ describe('Subflow', function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
| @@ -563,7 +715,6 @@ describe('Subflow', function() { | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     describe("#env var", function() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user