From a3fd1da0fd134d8cda362a360761809211628c73 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Fri, 25 Sep 2020 10:27:36 +0900 Subject: [PATCH 1/3] Add UI Module prototype --- nodes/ui_base.html | 260 +++++++++++++++++++++++++++++++++++++-- nodes/ui_subgroup_i.html | 45 +++++++ nodes/ui_subgroup_i.js | 14 +++ nodes/ui_subgroup_t.html | 53 ++++++++ nodes/ui_subgroup_t.js | 14 +++ package.json | 4 +- ui.js | 88 +++++++++++++ 7 files changed, 466 insertions(+), 12 deletions(-) create mode 100644 nodes/ui_subgroup_i.html create mode 100644 nodes/ui_subgroup_i.js create mode 100644 nodes/ui_subgroup_t.html create mode 100644 nodes/ui_subgroup_t.js diff --git a/nodes/ui_base.html b/nodes/ui_base.html index 5bdac1ad..18097fe7 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].subflow == n.id) { + 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, + subflow: n.id, + 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) { + 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.subflow == 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.subflow)) { + n.widgetOrder.push(wId); + } + } + + // Add new widget node + for (var cnt = 0; cnt < allNodes.length; cnt++) { + var node = allNodes[cnt]; + if ((n.subflow === 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..3e336437 --- /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..7a9c8560 --- /dev/null +++ b/nodes/ui_subgroup_t.html @@ -0,0 +1,53 @@ + + + + + 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..7f803836 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,89 @@ 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) { + // TODO Cannot get subflow instance when subflow instance is placed in subflow. + //console.log("# Belongs to subflow:", subflow.path); + } + } + + 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]; From 2ef4cf00ebf7cdcd6c0722000e9cd4c468d5f9d2 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ito Date: Fri, 29 Jan 2021 10:52:22 +0900 Subject: [PATCH 2/3] Fix references to other nodes using node property typing --- nodes/ui_base.html | 12 ++++++------ nodes/ui_subgroup_i.html | 2 +- nodes/ui_subgroup_t.html | 5 ++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/nodes/ui_base.html b/nodes/ui_base.html index 18097fe7..a965bae1 100644 --- a/nodes/ui_base.html +++ b/nodes/ui_base.html @@ -1186,7 +1186,7 @@ sftmpls.push(n); var exist = false; for (var cnt = 0; cnt < allNodes.length; cnt++) { - if (allNodes[cnt].subflow == n.id) { + if (allNodes[cnt].z == n.id && allNodes[cnt].type === 'ui_subgroup_t') { exist = true; break; } @@ -1205,7 +1205,6 @@ name: "["+n.name+"]ui subgroup template", width: 6, height: 6, - subflow: n.id, label: function() { return this.name ; } }; RED.nodes.add(sgtmplNode); @@ -1219,7 +1218,8 @@ 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.subflow) { + if (sftmpls[cnt].id == n.z) { // UI-MODULE 2021/1/22 exist = true; break; } @@ -1251,7 +1251,7 @@ // get subgroup template var sgId = ""; RED.nodes.eachConfig(function(n) { - if (n.type == 'ui_subgroup_t' && n.subflow == subflowId) { + if (n.type == 'ui_subgroup_t' && n.z == subflowId) { sgId = n.id; } }); @@ -1314,7 +1314,7 @@ for (var cnt = 0; cnt < wLen; cnt++) { var wId = n.widgetOrder.shift(); var wNode = RED.nodes.node(wId) - if (wNode && (wNode.z === n.subflow)) { + if (wNode && (wNode.z === n.z)) { n.widgetOrder.push(wId); } } @@ -1322,7 +1322,7 @@ // Add new widget node for (var cnt = 0; cnt < allNodes.length; cnt++) { var node = allNodes[cnt]; - if ((n.subflow === 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.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); } diff --git a/nodes/ui_subgroup_i.html b/nodes/ui_subgroup_i.html index 3e336437..3b9dd714 100644 --- a/nodes/ui_subgroup_i.html +++ b/nodes/ui_subgroup_i.html @@ -5,7 +5,7 @@ name: {value: "subgroup"}, group: {type: 'ui_group', required:false}, order: {value: 0}, - subflow: {value: ""}, + subflow: {value: "", type: "*"}, subgroup: {type: 'ui_subgroup_t', required:true} }, paletteLabel: 'dashboard subgroup instance', diff --git a/nodes/ui_subgroup_t.html b/nodes/ui_subgroup_t.html index 7a9c8560..7ad814a5 100644 --- a/nodes/ui_subgroup_t.html +++ b/nodes/ui_subgroup_t.html @@ -3,10 +3,9 @@ category: 'config', defaults: { name: {value: "subgroup"}, - widgetOrder: {value: []}, + widgetOrder: {value: [], type: "*[]"}, width: {value: 1}, - height: {value: 1}, - subflow: {value: ""} + height: {value: 1} }, paletteLabel: 'dashboard subgroup template', inputs:0, From 8f485688bf46b7d05a70947ac309493e8ccf5ad3 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ito Date: Tue, 23 Mar 2021 18:21:54 +0900 Subject: [PATCH 3/3] Added order acquisition code example for widgets under subgroup --- ui.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/ui.js b/ui.js index 7f803836..23543d19 100644 --- a/ui.js +++ b/ui.js @@ -136,8 +136,32 @@ function add(opt) { 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("# Belongs to subflow:", subflow.path); + console.log(" The subflow instance was not found."); } } @@ -399,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');