diff --git a/nodes/ui_base.html b/nodes/ui_base.html
index 5bdac1ad..a965bae1 100644
--- a/nodes/ui_base.html
+++ b/nodes/ui_base.html
@@ -442,6 +442,18 @@
height: nodes[cnt].height,
auto: nodes[cnt].width == 0 ? true : false
};
+
+
+ // UI-MODULE PROTO
+ if (RED.nodes.subflow(nodes[cnt].z)) { continue; }
+
+ if (widget.type === 'ui_subgroup_i') {
+ widget.width = RED.nodes.node(nodes[cnt].subgroup).width;
+ widget.height = RED.nodes.node(nodes[cnt].subgroup).height;
+ widget.auto = false;
+ }
+ // UI-MODULE PROTO
+
group.widgets.push(widget);
if (!isLayoutToolSupported(nodes[cnt].type)){
@@ -770,7 +782,11 @@
var node = el.data('_gridstack_node');
var auto = (el[0].dataset.noderedsizeauto == 'true') ? true : false;
var type = el[0].dataset.noderedtype;
- grid.resizable(el, !auto);
+ if (el[0].dataset.noderedtype != 'ui_subgroup_i') { // UI-MODULE PROTO
+ grid.resizable(el, !auto);
+ } else { // UI-MODULE PROTO
+ grid.resizable(el, false); // UI-MODULE PROTO
+ }
if (auto === true) {
grid.resize(el, width, getDefaultHeight(node.id, width));
}
@@ -1157,6 +1173,169 @@
RED.nodes.dirty(true);
}
}
+
+
+ // UI-MODULE PROTO
+
+ // Subgroups are automatically generated based on subflows.
+ var allNodes = RED.nodes.createCompleteNodeSet(false);
+
+ // Add subgroup template
+ var sftmpls = [];
+ RED.nodes.eachSubflow(function(n) {
+ sftmpls.push(n);
+ var exist = false;
+ for (var cnt = 0; cnt < allNodes.length; cnt++) {
+ if (allNodes[cnt].z == n.id && allNodes[cnt].type === 'ui_subgroup_t') {
+ exist = true;
+ break;
+ }
+ }
+
+ if (!exist) {
+ // add ui_subgroup_t node
+ var sgtmplNode = {
+ _def: RED.nodes.getType("ui_subgroup_t"),
+ type: "ui_subgroup_t",
+ z: n.id,
+ hasUsers: false,
+ users: [],
+ id: RED.nodes.id(),
+ widgetOrder: [],
+ name: "["+n.name+"]ui subgroup template",
+ width: 6,
+ height: 6,
+ label: function() { return this.name ; }
+ };
+ RED.nodes.add(sgtmplNode);
+ RED.nodes.dirty(true);
+ RED.view.redraw(true);
+ }
+ });
+
+ // Remove subgroup template
+ RED.nodes.eachConfig(function(n) {
+ if (n.type === 'ui_subgroup_t') {
+ var exist = false;
+ for (var cnt = 0; cnt < sftmpls.length; cnt++) {
+ //if (sftmpls[cnt].id == n.subflow) {
+ if (sftmpls[cnt].id == n.z) { // UI-MODULE 2021/1/22
+ exist = true;
+ break;
+ }
+ }
+
+ if (!exist) {
+ RED.nodes.remove(n.id);
+ RED.nodes.dirty(true);
+ RED.view.redraw(true);
+ }
+ }
+ });
+
+ // Add subgroup instance
+ var sfinsts = [];
+ for (var cnt = 0; cnt < allNodes.length; cnt++) {
+ if (allNodes[cnt].type.substring(0,8) == "subflow:") {
+ sfinsts.push(allNodes[cnt]);
+ var subflowId = allNodes[cnt].type.substring(8);
+ var exist = false;
+ for (var cnt2 = 0; cnt2 < allNodes.length; cnt2++) {
+ if (allNodes[cnt2].subflow == allNodes[cnt].id) {
+ exist = true;
+ break;
+ }
+ }
+
+ if (!exist) {
+ // get subgroup template
+ var sgId = "";
+ RED.nodes.eachConfig(function(n) {
+ if (n.type == 'ui_subgroup_t' && n.z == subflowId) {
+ sgId = n.id;
+ }
+ });
+ // belongs to subflow
+ var zId = "";
+ RED.nodes.eachSubflow(function(n) {
+ if (n.id === allNodes[cnt].z) {
+ zId = n.id;
+ }
+ });
+
+ // add ui_subgroup_i node
+ var sginstlNode = {
+ _def: RED.nodes.getType("ui_subgroup_i"),
+ type: "ui_subgroup_i",
+ z: zId,
+ hasUsers: false,
+ users: [],
+ id: RED.nodes.id(),
+ group: "",
+ order: 0,
+ name: "["+ RED.nodes.subflow(subflowId).name +"]ui subgroup instance",
+ subflow: allNodes[cnt].id,
+ subgroup: sgId,
+ label: function() { return this.name; }
+ };
+ RED.nodes.add(sginstlNode);
+ RED.nodes.dirty(true);
+ RED.view.redraw(true);
+ }
+ }
+ }
+
+ // ui_subgroup_i (ui_subgroup_i)
+ RED.nodes.eachConfig(function(n) {
+ if (n.type === 'ui_subgroup_i') {
+ var exist = false;
+ for (var cnt = 0; cnt < sfinsts.length; cnt++) {
+ if (sfinsts[cnt].id == n.subflow) {
+ exist = true;
+ break;
+ }
+ }
+
+ if (!exist) {
+ RED.nodes.remove(n.id);
+ RED.nodes.dirty(true);
+ RED.view.redraw(true);
+ }
+ }
+ });
+
+ // Add widget node to widgetOrder
+ allNodes = RED.nodes.createCompleteNodeSet(false);
+ RED.nodes.eachConfig(function(n) {
+ if (n.type === 'ui_subgroup_t') {
+
+ // Remove widget node from widgetOrder
+ var wLen = n.widgetOrder.length;
+ for (var cnt = 0; cnt < wLen; cnt++) {
+ var wId = n.widgetOrder.shift();
+ var wNode = RED.nodes.node(wId)
+ if (wNode && (wNode.z === n.z)) {
+ n.widgetOrder.push(wId);
+ }
+ }
+
+ // Add new widget node
+ for (var cnt = 0; cnt < allNodes.length; cnt++) {
+ var node = allNodes[cnt];
+ if ((n.z === node.z)&&(/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control' && node.type !== 'ui_audio' && node.type !== 'ui_base' && node.type !== 'ui_group' && node.type !== 'ui_tab' && node.type !== 'ui_subgroup_t')){
+ if (n.widgetOrder.indexOf(node.id) === -1) {
+ n.widgetOrder.push(node.id);
+ }
+ }
+ }
+ }
+ });
+
+ // Side bar refresh
+ RED.sidebar.info.refresh();
+
+ // UI-MODULE PROTO
+
}
var content = $("
").css({"position":"relative","height":"100%"});
@@ -2106,12 +2285,17 @@
addClass: 'grid-stack-item-content',
title: dispLabel + ':' + dispType
});
-
- if (node.auto === true) {
- itemContent.append('
');
- } else {
- itemContent.append('
');
+
+ // UI-MODULE PROTO
+ if (node.type != 'ui_subgroup_i') {
+ if (node.auto === true) {
+ itemContent.append('
');
+ } else {
+ itemContent.append('
');
+ }
}
+ // UI-MODULE PROTO
+
itemContent.append('
'+ dispLabel +''+ dispType);
item.append(itemContent);
grid.addWidget(
@@ -2128,7 +2312,14 @@
el = $(el);
var node = el.data('_gridstack_node');
var auto = (el[0].dataset.noderedsizeauto == 'true') ? true : false;
- grid.resizable(el, !auto);
+
+ // UI-MODULE PROTO
+ if (el[0].dataset.noderedtype != 'ui_subgroup_i') {
+ grid.resizable(el, !auto);
+ } else {
+ grid.resizable(el, false);
+ }
+ // UI-MODULE PROTO
});
// Group width change
@@ -2264,7 +2455,15 @@
elementParents[widgetNode.id] = groupNode.id;
var titleRow = $('
',{class:"nr-db-sb-list-header nr-db-sb-widget-list-header"}).appendTo(container);
$('').appendTo(titleRow);
- $('').click(function(e) { e.preventDefault(); RED.search.show(widgetNode.id); }).appendTo(titleRow);
+
+ // UI-MODULE PROTO
+ if (widgetNode.type === 'ui_subgroup_i') {
+ $('').click(function(e) { e.preventDefault(); RED.search.show(widgetNode.id); }).appendTo(titleRow);
+ } else {
+ $('').click(function(e) { e.preventDefault(); RED.search.show(widgetNode.id); }).appendTo(titleRow);
+ }
+ // UI-MODULE PROTO
+
var l = widgetNode._def.label;
try {
l = (typeof l === "function" ? l.call(widgetNode) : l)||"";
@@ -2288,7 +2487,15 @@
RED.view.redraw();
});
editButton.on('click',function(evt) {
- RED.editor.edit(widgetNode);
+
+ // UI-MODULE PROTO
+ if (widgetNode.type === 'ui_subgroup_i') {
+ RED.editor.editConfig("", "ui_subgroup_i", widgetNode.id);
+ } else {
+ RED.editor.edit(widgetNode);
+ }
+ // UI-MODULE PROTO
+
evt.stopPropagation();
evt.preventDefault();
});
@@ -2347,7 +2554,13 @@
evt.preventDefault();
});
group.widgets.forEach(function(widget) {
- ol.editableList('addItem',widget);
+
+ // UI-MODULE PROTO
+ if (!RED.nodes.subflow(widget.z)) {
+ ol.editableList('addItem',widget);
+ }
+ // UI-MODULE PROTO
+
})
},
sortItems: function(items) {
@@ -2610,6 +2823,23 @@
}
}
});
+
+
+ // UI-MODULE PROTO
+ RED.nodes.eachConfig(function(node) {
+ switch (node.type) {
+ case 'ui_subgroup_i': {
+ // Subflow instances placed in subflows are not displayed.
+ if (groups.hasOwnProperty(node.group) && !RED.nodes.subflow(node.z)) {
+ groupElements[node.group] = groupElements[node.group]||[];
+ groupElements[node.group].push(node);
+ }
+ break;
+ }
+ }
+ });
+ // UI-MODULE PROTO
+
for (groupId in groups) {
if (groups.hasOwnProperty(groupId)) {
group = groups[groupId];
@@ -3171,6 +3401,7 @@
var fixedWidth = false;
var fixedHeight = false;
var group = $(that.options.group).val();
+ var subflow = $(that.options.subflow).val(); // UI-MODULE PROTO
if (group) {
var groupNode = RED.nodes.node(group);
if (groupNode) {
@@ -3179,7 +3410,14 @@
fixedWidth = true;
}
maxHeight = Math.max(6,+height+1);
- }
+
+ // UI-MODULE PROTO
+ } else if (subflow) {
+ gridWidth = Math.max(12,+width);
+ maxWidth = gridWidth;
+ maxHeight = height+3;
+ fixedHeight = true;
+ } // UI-MODULE PROTO
else {
gridWidth = Math.max(12,+width);
maxWidth = gridWidth;
diff --git a/nodes/ui_subgroup_i.html b/nodes/ui_subgroup_i.html
new file mode 100644
index 00000000..3b9dd714
--- /dev/null
+++ b/nodes/ui_subgroup_i.html
@@ -0,0 +1,45 @@
+
+
+
+
+
diff --git a/nodes/ui_subgroup_i.js b/nodes/ui_subgroup_i.js
new file mode 100644
index 00000000..338a9a0b
--- /dev/null
+++ b/nodes/ui_subgroup_i.js
@@ -0,0 +1,14 @@
+module.exports = function(RED) {
+ function SubGroupInsNode(config) {
+ RED.nodes.createNode(this, config);
+ this.config = {
+ name: config.name,
+ group: config.group,
+ order: config.order,
+ subflow: config.subflow,
+ subgroup: config.subgroup
+ };
+ }
+
+ RED.nodes.registerType("ui_subgroup_i", SubGroupInsNode);
+};
diff --git a/nodes/ui_subgroup_t.html b/nodes/ui_subgroup_t.html
new file mode 100644
index 00000000..7ad814a5
--- /dev/null
+++ b/nodes/ui_subgroup_t.html
@@ -0,0 +1,52 @@
+
+
+
+
+
diff --git a/nodes/ui_subgroup_t.js b/nodes/ui_subgroup_t.js
new file mode 100644
index 00000000..7b347b55
--- /dev/null
+++ b/nodes/ui_subgroup_t.js
@@ -0,0 +1,14 @@
+module.exports = function(RED) {
+ function SubGroupNode(config) {
+ RED.nodes.createNode(this, config);
+ this.config = {
+ name: config.name,
+ widgetOrder: config.order,
+ width: config.width,
+ height: config.height,
+ subflow: config.subflow
+ };
+ }
+
+ RED.nodes.registerType("ui_subgroup_t", SubGroupNode);
+};
diff --git a/package.json b/package.json
index e6267d7a..d1219aaf 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,9 @@
"ui_link": "nodes/ui_link.js",
"ui_tab": "nodes/ui_tab.js",
"ui_group": "nodes/ui_group.js",
- "ui_spacer": "nodes/ui_spacer.js"
+ "ui_spacer": "nodes/ui_spacer.js",
+ "ui_subgroup_t": "nodes/ui_subgroup_t.js",
+ "ui_subgroup_i": "nodes/ui_subgroup_i.js"
}
},
"dependencies": {
diff --git a/ui.js b/ui.js
index 52d3f5e7..23543d19 100644
--- a/ui.js
+++ b/ui.js
@@ -1,7 +1,10 @@
var inited = false;
+var nodes = {}; // UI-MODULE PROTO
module.exports = function(RED) {
+ nodes = RED.nodes; // UI-MODULE PROTO
+
if (!inited) {
inited = true;
init(RED.server, RED.httpNode || RED.httpAdmin, RED.log, RED.settings);
@@ -44,6 +47,8 @@ var ev = new events.EventEmitter();
var params = {};
ev.setMaxListeners(0);
+var displayed_subgroup = []; // UI-MODULE PROTO
+
// default manifest.json to be returned as required.
var mani = {
"name": "Node-RED Dashboard",
@@ -122,6 +127,113 @@ options:
if the returned msg has a property _dontSend, then it won't get sent.
*/
function add(opt) {
+
+ // UI-MODULE PROTO
+ //console.log("\n#",opt.node.type,"/",opt.control.label,"/",opt.node.id,"/",opt.node["_alias"]);
+ var isSubgroup = false;
+ if (opt.node["_alias"]) {
+ isSubgroup = true;
+
+ var subflow = nodes.getNode(opt.node.z);
+ if (subflow) {
+ var widget = opt.node["_alias"];
+ console.log("# Widget:", widget, opt.node.type);
+ console.log(" Belongs to Subflow instalce :", subflow.path);
+
+ var subflows = subflow.path.split('/');
+
+ // Get orders for all layers by repeating for each layer
+ var sf_inst_id = subflows.pop(); // Get the trailing subflow instance
+ while(sf_inst_id) {
+ var order = -1;
+ if (subflows.length > 0) {
+ sg_inst = getSubgroupInstance(sf_inst_id); // Get a subgroup instance
+ order = getOrderBySubgroup(sg_inst, widget); // Get order value for subgroup
+ console.log(" subflow instance=",sf_inst_id);
+ console.log(" widget or subgroup instance=", widget);
+ console.log(" order=", order);
+ } else {
+ console.log(" subgroup instance=", widget);
+ console.log(" order=", sg_inst.order);
+ }
+ widget = sg_inst.id; // Get the order value of the obtained subgroup instance
+ sf_inst_id = subflows.pop();
+ }
+ } else {
+ // TODO Cannot get subflow instance when subflow instance is placed in subflow.
+ console.log(" The subflow instance was not found.");
+ }
+ }
+
+ if (isSubgroup) {
+ //console.log("# SUBGROUP:", displayed_subgroup);
+ if (displayed_subgroup.indexOf(opt.node.z) != -1) {
+ //console.log("# ALREDY DISPLAY: subflow iinstance:", opt.node.z);
+ return;
+ }
+
+ var sg_height = 1;
+ var sg_width = 1;
+ var sg_order = 0;
+ var sg_group_id = null;
+
+ nodes.eachNode(function(node) {
+ if (node.type == 'ui_subgroup_i') {
+ if (node.subflow == opt.node.z) {
+ sg_group_id = node.group;
+ sg_order = node.order;
+ }
+ }
+ });
+
+ if (!sg_group_id) {
+ //console.log("# SUBGROUP-INSTANCE NOT FOUND:", opt.node.z);
+ return;
+ }
+
+ nodes.eachNode(function(node) {
+ if (node.type == 'ui_subgroup_t') {
+ var idx = node.widgetOrder.indexOf(opt.node["_alias"]);
+ if (idx != -1) {
+ sg_height = node.height;
+ sg_width = node.width;
+ }
+ };
+ });
+
+ var sg_group = nodes.getNode(sg_group_id);
+ if (!sg_group) {
+ //console.log("# GROUP NOT FOUND:", sg_group_id);
+ return;
+ }
+
+ var sg_tab = nodes.getNode(sg_group.config.tab);
+ if (!sg_tab) {
+ //console.log("# TAB NOT FOUND:", sg_tab);
+ return;
+ }
+
+ var sg_control = {
+ type: 'spacer',
+ order: sg_order,
+ width: sg_width,
+ height: sg_height
+ };
+
+ //console.log("# ADD SPACER FOR SUBGROUP");
+ var remove_sg = addControl(sg_tab, sg_group, sg_control);
+ displayed_subgroup.push(opt.node.z);
+
+ ev.on(updateValueEventName, function() {});
+ return function() {
+ ev.removeListener(updateValueEventName, function() {});
+ remove_sg();
+ displayed_subgroup = [];
+ };
+ }
+ // UI-MODULE PROTO
+
+
clearTimeout(removeStateTimers[opt.node.id]);
delete removeStateTimers[opt.node.id];
@@ -311,6 +423,28 @@ function add(opt) {
};
}
+// Get subgroup instance definition from subflow instance ID
+function getSubgroupInstance(sf_inst_id) {
+ var sg_inst = null;
+ nodes.eachNode(function(node) {
+ if (node.type == 'ui_subgroup_i' && node.subflow == sf_inst_id) {
+ sg_inst = node;
+ }
+ });
+ return sg_inst;
+}
+
+// Get order value from subgroup instance definition
+function getOrderBySubgroup(sg_inst, widget) {
+ var order = -1;
+ nodes.eachNode(function(node) {
+ if (node.type == 'ui_subgroup_t' && node.id == sg_inst.subgroup) {
+ order = node.widgetOrder.indexOf(widget);
+ }
+ });
+ return order;
+}
+
//from: https://stackoverflow.com/a/28592528/3016654
function join() {
var trimRegex = new RegExp('^\\/|\\/$','g');