mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'dev' into pr_2042
This commit is contained in:
commit
5110eaff96
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user