diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
index 1fff8b87e..6a06d3094 100755
--- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
+++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
@@ -273,6 +273,7 @@
"editSubflowProperties": "edit properties",
"input": "inputs:",
"output": "outputs:",
+ "status": "status node",
"deleteSubflow": "delete subflow",
"info": "Description",
"category": "Category",
diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js
index 95cc78292..2e256564c 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/history.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/history.js
@@ -125,14 +125,20 @@ RED.history = (function() {
});
}
}
- if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
- ev.subflow.instances.forEach(function(n) {
- var node = RED.nodes.node(n.id);
- if (node) {
- node.changed = n.changed;
- node.dirty = true;
- }
- });
+ if (ev.subflow) {
+ if (ev.subflow.hasOwnProperty('instances')) {
+ ev.subflow.instances.forEach(function(n) {
+ var node = RED.nodes.node(n.id);
+ if (node) {
+ node.changed = n.changed;
+ node.dirty = true;
+ }
+ });
+ }
+ if (ev.subflow.hasOwnProperty('status')) {
+ subflow = RED.nodes.subflow(ev.subflow.id);
+ subflow.status = ev.subflow.status;
+ }
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
@@ -232,6 +238,11 @@ RED.history = (function() {
}
});
}
+ if (ev.subflow.hasOwnProperty('status')) {
+ if (ev.subflow.status) {
+ delete ev.node.status;
+ }
+ }
RED.editor.validateNode(ev.node);
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
@@ -290,6 +301,7 @@ RED.history = (function() {
RED.workspaces.order(ev.order);
}
}
+
Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id);
if (subflow) {
@@ -303,6 +315,7 @@ RED.history = (function() {
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
+ RED.subflow.refresh();
}
}
diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js
index d9ef6ab44..b531f89f0 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js
@@ -571,6 +571,18 @@ RED.nodes = (function() {
node.icon = n.icon;
}
}
+ if (n.status) {
+ node.status = {x: n.status.x, y: n.status.y, wires:[]};
+ links.forEach(function(d) {
+ if (d.target === n.status) {
+ if (d.source.type != "subflow") {
+ node.status.wires.push({id:d.source.id, port:d.sourcePort})
+ } else {
+ node.status.wires.push({id:n.id, port:0})
+ }
+ }
+ });
+ }
return node;
}
@@ -851,6 +863,12 @@ RED.nodes = (function() {
output.i = i;
output.id = getID();
});
+ if (n.status) {
+ n.status.type = "subflow";
+ n.status.direction = "status";
+ n.status.z = n.id;
+ n.status.id = getID();
+ }
new_subflows.push(n);
addSubflow(n,createNewIds);
}
@@ -1189,6 +1207,19 @@ RED.nodes = (function() {
});
delete output.wires;
});
+ if (n.status) {
+ n.status.wires.forEach(function(wire) {
+ var link;
+ if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
+ link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
+ } else {
+ link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
+ }
+ addLink(link);
+ new_links.push(link);
+ });
+ delete n.status.wires;
+ }
}
RED.workspaces.refresh();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
index ba303152f..eb179c205 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js
@@ -16,7 +16,6 @@
RED.subflow = (function() {
-
var _subflowEditTemplate = '';
var _subflowTemplateEditTemplate = '';
-
- function getSubflow() {
- return RED.nodes.subflow(RED.workspaces.active());
- }
-
function findAvailableSubflowIOPosition(subflow,isInput) {
var pos = {x:50,y:30};
if (!isInput) {
pos.x += 110;
}
- for (var i=0;i ').appendTo(toolbar);
+
+ // Inputs
$(' '+
'').appendTo(toolbar);
+ // Outputs
$(' ').appendTo(toolbar);
+ // Status
+ $('').appendTo(toolbar);
+
// $(' ').appendTo(toolbar);
// $(' ').appendTo(toolbar);
+
+ // Delete
$(' ').appendTo(toolbar);
+
toolbar.i18n();
@@ -274,6 +339,7 @@ RED.subflow = (function() {
RED.view.redraw(true);
}
});
+
$("#workspace-subflow-output-add").click(function(event) {
event.preventDefault();
addSubflowOutput();
@@ -283,6 +349,7 @@ RED.subflow = (function() {
event.preventDefault();
addSubflowInput();
});
+
$("#workspace-subflow-input-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
@@ -307,6 +374,33 @@ RED.subflow = (function() {
}
});
+ $("#workspace-subflow-status").change(function(evt) {
+ if (this.checked) {
+ addSubflowStatus();
+ } else {
+ var currentStatus = activeSubflow.status;
+ var wasChanged = activeSubflow.changed;
+ var result = removeSubflowStatus();
+ if (result) {
+ activeSubflow.changed = true;
+ var wasDirty = RED.nodes.dirty();
+ RED.history.push({
+ t:'delete',
+ links:result.links,
+ changed: wasChanged,
+ dirty:wasDirty,
+ subflow: {
+ id: activeSubflow.id,
+ status: currentStatus
+ }
+ });
+ RED.view.select();
+ RED.nodes.dirty(true);
+ RED.view.redraw();
+ }
+ }
+ })
+
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
event.preventDefault();
@@ -328,6 +422,7 @@ RED.subflow = (function() {
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
}
+
function hideWorkspaceToolbar() {
$("#workspace-toolbar").hide().empty();
$("#chart").css({"margin-top": "0"});
@@ -373,6 +468,7 @@ RED.subflow = (function() {
subflows: [activeSubflow]
}
}
+
function init() {
RED.events.on("workspace:change",function(event) {
var activeSubflow = RED.nodes.subflow(event.workspace);
@@ -436,6 +532,7 @@ RED.subflow = (function() {
}
return x;
}
+
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
@@ -451,7 +548,6 @@ RED.subflow = (function() {
var candidateOutputs = [];
var candidateInputNodes = {};
-
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
@@ -467,8 +563,8 @@ RED.subflow = (function() {
Math.max(boundingBox[3],n.y)
]
}
- var offsetX = snapToGrid(boundingBox[0] - 180);
- var offsetY = snapToGrid(boundingBox[1] - 60);
+ var offsetX = snapToGrid(boundingBox[0] - 200);
+ var offsetY = snapToGrid(boundingBox[1] - 80);
var center = [
@@ -643,8 +739,6 @@ RED.subflow = (function() {
RED.view.redraw(true);
}
-
-
return {
init: init,
createSubflow: createSubflow,
@@ -652,6 +746,7 @@ RED.subflow = (function() {
removeSubflow: removeSubflow,
refresh: refresh,
removeInput: removeSubflowInput,
- removeOutput: removeSubflowOutput
+ removeOutput: removeSubflowOutput,
+ removeStatus: removeSubflowStatus
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
index 9405b60dd..4eca5b1c5 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
@@ -1261,6 +1261,13 @@ RED.view = (function() {
moving_set.push({n:n});
}
});
+ if (activeSubflow.status) {
+ activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2);
+ if (activeSubflow.status.selected) {
+ activeSubflow.status.dirty = true;
+ moving_set.push({n:activeSubflow.status});
+ }
+ }
}
updateSelection();
lasso.remove();
@@ -1367,6 +1374,13 @@ RED.view = (function() {
moving_set.push({n:n});
}
});
+ if (activeSubflow.status) {
+ if (!activeSubflow.status.selected) {
+ activeSubflow.status.selected = true;
+ activeSubflow.status.dirty = true;
+ moving_set.push({n:activeSubflow.status});
+ }
+ }
}
selected_link = null;
@@ -1552,6 +1566,7 @@ RED.view = (function() {
var removedLinks = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
+ var removedSubflowStatus = undefined;
var subflowInstances = [];
var startDirty = RED.nodes.dirty();
@@ -1573,6 +1588,8 @@ RED.view = (function() {
removedSubflowOutputs.push(node);
} else if (node.direction === "in") {
removedSubflowInputs.push(node);
+ } else if (node.direction === "status") {
+ removedSubflowStatus = node;
}
node.dirty = true;
}
@@ -1590,12 +1607,19 @@ RED.view = (function() {
removedLinks = removedLinks.concat(result.links);
}
}
+ if (removedSubflowStatus) {
+ result = RED.subflow.removeStatus();
+ if (result) {
+ removedLinks = removedLinks.concat(result.links);
+ }
+ }
+
var instances = RED.subflow.refresh(true);
if (instances) {
subflowInstances = instances.instances;
}
moving_set = [];
- if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
+ if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) {
RED.nodes.dirty(true);
}
}
@@ -1651,10 +1675,14 @@ RED.view = (function() {
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
+ id: activeSubflow?activeSubflow.id:undefined,
instances: subflowInstances
},
dirty:startDirty
};
+ if (removedSubflowStatus) {
+ historyEvent.subflow.status = removedSubflowStatus;
+ }
}
RED.history.push(historyEvent);
@@ -2420,6 +2448,49 @@ RED.view = (function() {
inGroup.append("svg:text").attr("class","port_label").attr("x",18).attr("y",20).style("font-size","10px").text("input");
+ var subflowStatus = nodeLayer.selectAll(".subflowstatus").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;});
+ subflowStatus.exit().remove();
+
+ var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","node subflowstatus").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
+ statusGroup.each(function(d,i) {
+ d.w=40;
+ d.h=40;
+ });
+ statusGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
+ // TODO: This is exactly the same set of handlers used for regular nodes - DRY
+ .on("mouseup",nodeMouseUp)
+ .on("mousedown",nodeMouseDown)
+ .on("touchstart",function(d) {
+ var obj = d3.select(this);
+ var touch0 = d3.event.touches.item(0);
+ var pos = [touch0.pageX,touch0.pageY];
+ startTouchCenter = [touch0.pageX,touch0.pageY];
+ startTouchDistance = 0;
+ touchStartTime = setTimeout(function() {
+ showTouchMenu(obj,pos);
+ },touchLongPressTimeout);
+ nodeMouseDown.call(this,d)
+ })
+ .on("touchend", function(d) {
+ clearTimeout(touchStartTime);
+ touchStartTime = null;
+ if (RED.touch.radialMenu.active()) {
+ d3.event.stopPropagation();
+ return;
+ }
+ nodeMouseUp.call(this,d);
+ });
+
+ statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
+ .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
+ .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
+ .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
+ .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);} )
+ .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
+ .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
+
+ statusGroup.append("svg:text").attr("class","port_label").attr("x",22).attr("y",20).style("font-size","10px").text("status");
+
subflowOutputs.each(function(d,i) {
if (d.dirty) {
var output = d3.select(this);
@@ -2439,9 +2510,22 @@ RED.view = (function() {
d.dirty = false;
}
});
+ subflowStatus.each(function(d,i) {
+ if (d.dirty) {
+ var output = d3.select(this);
+ output.selectAll(".subflowport").classed("node_selected",function(d) { return d.selected; })
+ output.selectAll(".port_index").text(function(d){ return d.i+1});
+ output.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
+ dirtyNodes[d.id] = d;
+ d.dirty = false;
+ }
+ });
+
+
} else {
nodeLayer.selectAll(".subflowoutput").remove();
nodeLayer.selectAll(".subflowinput").remove();
+ nodeLayer.selectAll(".subflowstatus").remove();
}
var node = nodeLayer.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss
index 26d45bcf8..3224707fe 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss
@@ -33,6 +33,15 @@
transition: right 0.2s ease;
overflow: hidden;
+ label {
+ padding: 1px 8px;
+ margin: 0;
+ font-size: 12px;
+ }
+ input[type="checkbox"] {
+ margin: 0 3px 0 0 ;
+ padding: 0;
+ }
.button {
@include workspace-button;
margin-right: 10px;
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
index 7ca6afb77..ca555a5a5 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
@@ -265,7 +265,6 @@ class Flow {
return Promise.all(promises);
}
-
/**
* Update the flow definition. This doesn't change anything that is running.
* This should be called after `stop` and before `start`.
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
index cf393868e..24ae72a49 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
@@ -17,6 +17,8 @@
const clone = require("clone");
const Flow = require('./Flow').Flow;
+const util = require("util");
+
const redUtil = require("@node-red/util").util;
const flowUtil = require("./util");
@@ -104,6 +106,40 @@ class Subflow extends Flow {
var self = this;
// Create a subflow node to accept inbound messages and route appropriately
var Node = require("../Node");
+
+ if (this.subflowDef.status) {
+ var subflowStatusConfig = {
+ id: this.subflowInstance.id+":status",
+ type: "subflow-status",
+ z: this.subflowInstance.id,
+ _flow: this.parent
+ }
+ this.statusNode = new Node(subflowStatusConfig);
+ this.statusNode.on("input", function(msg) {
+ if (msg.payload !== undefined) {
+ if (typeof msg.payload === "string") {
+ // if msg.payload is a String, use it as status text
+ self.node.status({text:msg.payload})
+ return;
+ } else if (Object.prototype.toString.call(msg.payload) === "[object Object]") {
+ if (msg.payload.hasOwnProperty('text') || msg.payload.hasOwnProperty('fill') || msg.payload.hasOwnProperty('shape') || Object.keys(msg.payload).length === 0) {
+ // msg.payload is an object that looks like a status object
+ self.node.status(msg.payload);
+ return;
+ }
+ }
+ // Anything else - inspect it and use as status text
+ var text = util.inspect(msg.payload);
+ if (text.length > 32) { text = text.substr(0,32) + "..."; }
+ self.node.status({text:text});
+ } else if (msg.status !== undefined) {
+ // if msg.status exists
+ self.node.status(msg.status)
+ }
+ })
+ }
+
+
var subflowInstanceConfig = {
id: this.subflowInstance.id,
type: this.subflowInstance.type,
@@ -168,7 +204,6 @@ class Subflow extends Flow {
// Wire the subflow outputs
if (this.subflowDef.out) {
- var modifiedNodes = {};
for (var i=0;i