diff --git a/docs/examples/group-dialog-type.json b/docs/examples/group-dialog-type.json new file mode 100644 index 000000000..71e5fdc0f --- /dev/null +++ b/docs/examples/group-dialog-type.json @@ -0,0 +1,215 @@ +[ + { + "id": "0aa38a714b8a3d67", + "type": "ui-form", + "z": "9337c17e1a7f6875", + "name": "", + "group": "c7871ac53089d535", + "label": "New User", + "order": 1, + "width": 0, + "height": 0, + "options": [ + { + "label": "Name", + "key": "name", + "type": "text", + "required": true, + "rows": null + }, + { + "label": "Admin", + "key": "isAdmin", + "type": "switch", + "required": false, + "rows": null + } + ], + "formValue": { + "name": "", + "isAdmin": false + }, + "payload": "", + "submit": "Add", + "cancel": "clear", + "resetOnSubmit": true, + "topic": "topic", + "topicType": "msg", + "splitLayout": "", + "className": "", + "passthru": false, + "dropdownOptions": [], + "x": 220, + "y": 100, + "wires": [ + [ + "adbd76ecf97076a1" + ] + ] + }, + { + "id": "fa4925a5253341ce", + "type": "ui-control", + "z": "9337c17e1a7f6875", + "name": "", + "ui": "c2e1aa56f50f03bd", + "events": "all", + "x": 580, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "adbd76ecf97076a1", + "type": "change", + "z": "9337c17e1a7f6875", + "name": "Hide Dialog", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "{\"groups\":{\"hide\":[\"Active Development:Dialog Group\"]}}", + "tot": "json" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 390, + "y": 100, + "wires": [ + [ + "fa4925a5253341ce", + "b9d77a856b1be020" + ] + ] + }, + { + "id": "294ac777d99f5789", + "type": "ui-button", + "z": "9337c17e1a7f6875", + "group": "497106faf38a5190", + "name": "", + "label": "Add User (Dialog)", + "order": 1, + "width": 0, + "height": 0, + "emulateClick": false, + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "iconPosition": "left", + "payload": "{\"groups\":{\"show\":[\"Active Development:Dialog Group\"]}}", + "payloadType": "json", + "topic": "topic", + "topicType": "msg", + "buttonColor": "", + "textColor": "", + "iconColor": "", + "x": 370, + "y": 140, + "wires": [ + [ + "fa4925a5253341ce", + "b9d77a856b1be020" + ] + ] + }, + { + "id": "b9d77a856b1be020", + "type": "debug", + "z": "9337c17e1a7f6875", + "name": "debug 2572", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 590, + "y": 140, + "wires": [] + }, + { + "id": "c7871ac53089d535", + "type": "ui-group", + "name": "Dialog Group", + "page": "22b5519aa675ad88", + "width": "6", + "height": "1", + "order": 1, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "dialog" + }, + { + "id": "c2e1aa56f50f03bd", + "type": "ui-base", + "name": "Dashboard", + "path": "/dashboard", + "includeClientData": true, + "acceptsClientConfig": [ + "ui-control", + "ui-notification" + ], + "showPathInSidebar": false, + "showPageTitle": false, + "navigationStyle": "icon", + "titleBarStyle": "default" + }, + { + "id": "497106faf38a5190", + "type": "ui-group", + "name": "Button Groups", + "page": "22b5519aa675ad88", + "width": "6", + "height": "1", + "order": 10, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "22b5519aa675ad88", + "type": "ui-page", + "name": "Active Development", + "ui": "c2e1aa56f50f03bd", + "path": "/active-development", + "icon": "forum", + "layout": "grid", + "theme": "129e99574def90a3", + "order": 1, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "129e99574def90a3", + "type": "ui-theme", + "name": "Custom Theme", + "colors": { + "surface": "#000000", + "primary": "#ff4000", + "bgPage": "#f0f0f0", + "groupBg": "#ffffff", + "groupOutline": "#d9d9d9" + }, + "sizes": { + "pagePadding": "24px", + "groupGap": "12px", + "groupBorderRadius": "9px", + "widgetGap": "6px", + "density": "default" + } + } +] \ No newline at end of file diff --git a/docs/nodes/config/ui-group.md b/docs/nodes/config/ui-group.md index 376277908..bb10d7682 100644 --- a/docs/nodes/config/ui-group.md +++ b/docs/nodes/config/ui-group.md @@ -2,13 +2,22 @@ description: Group your widgets effectively in Node-RED Dashboard 2.0 for better organization and user navigation. props: Name: Descriptive name for this group, will show in the Node-RED Editor and as a label in the Dashboard. - Page: The Page (ui-page) that this group will render on. + Page: The Page (ui-page) that this group will render on. + Type: Controls whether the group appears as a default group or as a dialog, which needs to be triggered manually using ui-control. You can choose between 'Default' and 'Dialog' types. Size: The width and height of the group. Height will always be reinforced by this value, the height is generally a minimum height, and will extend to fit it's content. Class: Any custom CSS classes you wish to add to the Group. Default State:

Both of these can be overridden by the user at runtime using a ui-control node.

--- # Config: UI Group `ui-group` @@ -17,4 +26,20 @@ Each group is rendered within a `ui-page` as part of a [Layout](../../contributi ## Properties - \ No newline at end of file + + +## Type + +Defines how the group will be displayed. Either as a regular (**Default**) group or as a **Dialog** group. A 'Default' group is visible by default, while a 'Dialog' group must be triggered manually using the `ui-control` node ([see documentation](/nodes/widgets/ui-control.html#show-hide)). You can choose between these two options based on your layout needs. + +### Default Groups + +![Example of how the type 'Default' option looks](/images/node-examples/ui-group-type-default.png "Example of how the type 'Default' option looks"){data-zoomable} +_Example of how the type 'Default' option looks_ + +### Dialog Groups + +![Example of how the type 'Dialog' option looks](/images/node-examples/ui-group-type-dialog.png "Example of how the type 'Dialog' option looks"){data-zoomable} +_Example of how the type 'Dialog' option looks_ + + \ No newline at end of file diff --git a/docs/public/images/node-examples/ui-group-type-default.png b/docs/public/images/node-examples/ui-group-type-default.png new file mode 100644 index 000000000..252f9cd86 Binary files /dev/null and b/docs/public/images/node-examples/ui-group-type-default.png differ diff --git a/docs/public/images/node-examples/ui-group-type-dialog.png b/docs/public/images/node-examples/ui-group-type-dialog.png new file mode 100644 index 000000000..06f53769f Binary files /dev/null and b/docs/public/images/node-examples/ui-group-type-dialog.png differ diff --git a/nodes/config/locales/en-US/ui_group.html b/nodes/config/locales/en-US/ui_group.html index 22e19c875..d50d848e7 100644 --- a/nodes/config/locales/en-US/ui_group.html +++ b/nodes/config/locales/en-US/ui_group.html @@ -1,6 +1,49 @@ diff --git a/nodes/config/locales/en-US/ui_group.json b/nodes/config/locales/en-US/ui_group.json index 65332191d..c940211d7 100644 --- a/nodes/config/locales/en-US/ui_group.json +++ b/nodes/config/locales/en-US/ui_group.json @@ -14,7 +14,10 @@ "interactivity": "Interactivity", "active": "Active", "disabled": "Disabled", - "openDashboardSidebar": "Open Dashboard 2.0 Sidebar" + "openDashboardSidebar": "Open Dashboard 2.0 Sidebar", + "type": "Type", + "typeDefault": "Default", + "typeDialog": "Dialog" } } } \ No newline at end of file diff --git a/nodes/config/ui_base.html b/nodes/config/ui_base.html index aa3953925..3de08dfea 100644 --- a/nodes/config/ui_base.html +++ b/nodes/config/ui_base.html @@ -989,7 +989,7 @@ const titleRow = $('
', { class: 'nrdb2-sb-list-header nrdb2-sb-groups-list-header' }).appendTo(container) $('').appendTo(titleRow) const chevron = $('', { style: 'width:10px;' }).appendTo(titleRow) - const groupicon = 'fa-table' + const groupicon = group.node.groupType === 'dialog' ? 'fa-window-restore' : 'fa-table' $('', { class: 'nrdb2-sb-icon nrdb2-sb-group-icon fa ' + groupicon }).appendTo(titleRow) $('', { class: 'nrdb2-sb-title' }).text(group.name || group.id).appendTo(titleRow) $('', { class: 'nrdb2-sb-info' }).text(`${widgets.length} Widgets`).appendTo(titleRow) diff --git a/nodes/config/ui_group.html b/nodes/config/ui_group.html index eae33fb17..b7a20b6c3 100644 --- a/nodes/config/ui_group.html +++ b/nodes/config/ui_group.html @@ -13,7 +13,8 @@ showTitle: { value: true }, // show title on group card className: { value: '' }, visible: { value: true }, - disabled: { value: false } + disabled: { value: false }, + groupType: { value: 'default' } }, label: function () { const page = RED.nodes.node(this.page)?.name || '' @@ -45,6 +46,8 @@ this.disabled = true $('#node-config-input-disabled').val('true') } + + $('#node-config-input-groupType').val(this.groupType || 'default') }, oneditsave: function () { // convert string to boolean @@ -75,6 +78,13 @@
+
+ + +
diff --git a/ui/src/layouts/DialogGroup.vue b/ui/src/layouts/DialogGroup.vue new file mode 100644 index 000000000..8a516087a --- /dev/null +++ b/ui/src/layouts/DialogGroup.vue @@ -0,0 +1,92 @@ + + + + diff --git a/ui/src/layouts/Flex.vue b/ui/src/layouts/Flex.vue index 6ef255057..cd97869e5 100644 --- a/ui/src/layouts/Flex.vue +++ b/ui/src/layouts/Flex.vue @@ -31,6 +31,21 @@ :state="widget.state" />
+
+
+ + + +
+
@@ -38,12 +53,14 @@ import { mapGetters, mapState } from 'vuex' import BaselineLayout from './Baseline.vue' +import DialogGroup from './DialogGroup.vue' import WidgetGroup from './Group.vue' export default { name: 'LayoutFlex', components: { BaselineLayout, + DialogGroup, WidgetGroup }, data () { @@ -62,7 +79,7 @@ export default { // only show hte groups that haven't had their "visible" property set to false .filter((g) => { if ('visible' in g) { - return g.visible + return g.visible && g.groupType !== 'dialog' } return true }) @@ -71,6 +88,10 @@ export default { }) return groups }, + dialogGroups () { + const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog') + return groups + }, pageWidgets: function () { return this.widgetsByPage(this.$route.meta.id) }, diff --git a/ui/src/layouts/Grid.vue b/ui/src/layouts/Grid.vue index caf3c3b1e..392827914 100644 --- a/ui/src/layouts/Grid.vue +++ b/ui/src/layouts/Grid.vue @@ -31,6 +31,21 @@ :state="widget.state" /> +
+
+ + + +
+
@@ -38,6 +53,7 @@ import Responsiveness from '../mixins/responsiveness.js' import BaselineLayout from './Baseline.vue' +import DialogGroup from './DialogGroup.vue' import WidgetGroup from './Group.vue' // eslint-disable-next-line import/order, sort-imports @@ -47,6 +63,7 @@ export default { name: 'LayoutGrid', components: { BaselineLayout, + DialogGroup, WidgetGroup }, mixins: [Responsiveness], @@ -60,7 +77,7 @@ export default { // only show hte groups that haven't had their "visible" property set to false .filter((g) => { if ('visible' in g) { - return g.visible + return g.visible && g.groupType !== 'dialog' } return true }) @@ -69,6 +86,10 @@ export default { }) return groups }, + dialogGroups () { + const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog') + return groups + }, pageWidgets: function () { return this.widgetsByPage(this.$route.meta.id) }, diff --git a/ui/src/layouts/Notebook.vue b/ui/src/layouts/Notebook.vue index ee70c2ce0..e42ff0db7 100644 --- a/ui/src/layouts/Notebook.vue +++ b/ui/src/layouts/Notebook.vue @@ -30,6 +30,21 @@ :state="widget.state" /> +
+
+ + + +
+
@@ -39,12 +54,14 @@ import { mapGetters, mapState } from 'vuex' import Responsiveness from '../mixins/responsiveness.js' import BaselineLayout from './Baseline.vue' +import DialogGroup from './DialogGroup.vue' import WidgetGroup from './Group.vue' export default { name: 'LayoutFlex', components: { BaselineLayout, + DialogGroup, WidgetGroup }, mixins: [Responsiveness], @@ -64,7 +81,7 @@ export default { // only show the groups that haven't had their "visible" property set to false .filter((g) => { if ('visible' in g) { - return g.visible + return g.visible && g.groupType !== 'dialog' } return true }) @@ -73,6 +90,10 @@ export default { }) return groups }, + dialogGroups () { + const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog') + return groups + }, pageWidgets: function () { return this.widgetsByPage(this.$route.meta.id) }, diff --git a/ui/src/layouts/Tabs.vue b/ui/src/layouts/Tabs.vue index 02e59c19a..224aafcb7 100644 --- a/ui/src/layouts/Tabs.vue +++ b/ui/src/layouts/Tabs.vue @@ -32,6 +32,21 @@ +
+
+ + + +
+
@@ -41,6 +56,7 @@ import Responsiveness from '../mixins/responsiveness.js' // eslint-disable-next-line import/order import BaselineLayout from './Baseline.vue' +import DialogGroup from './DialogGroup.vue' import WidgetGroup from './Group.vue' // eslint-disable-next-line import/order, sort-imports @@ -50,6 +66,7 @@ export default { name: 'LayoutTabs', components: { BaselineLayout, + DialogGroup, WidgetGroup }, mixins: [Responsiveness], @@ -68,7 +85,7 @@ export default { // only show hte groups that haven't had their "visible" property set to false .filter((g) => { if ('visible' in g) { - return g.visible + return g.visible && g.groupType !== 'dialog' } return true }) @@ -77,6 +94,10 @@ export default { }) return groups }, + dialogGroups () { + const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog') + return groups + }, pageWidgets: function () { return this.widgetsByPage(this.$route.meta.id) }, diff --git a/ui/src/widgets/ui-control/UIControl.vue b/ui/src/widgets/ui-control/UIControl.vue index 4a25c9872..b435c7622 100644 --- a/ui/src/widgets/ui-control/UIControl.vue +++ b/ui/src/widgets/ui-control/UIControl.vue @@ -174,15 +174,31 @@ export default { if ('groups' in payload) { if ('show' in payload.groups) { - // we are setting visibility: true payload.groups.show.forEach((name) => { - setGroup(name, 'visible', true) + const groupName = name.split(':')[1] + const exactGroup = vue.groups ? Object.values(vue.groups).find(group => group.name === groupName) : null + + if (exactGroup?.groupType === 'dialog') { + // we are setting dialog visibility: true + setGroup(name, 'showDialog', `true-${Date.now().toString()}`) + } else { + // we are setting visibility: true + setGroup(name, 'visible', true) + } }) } if ('hide' in payload.groups) { - // we are setting visibility: false payload.groups.hide.forEach((name) => { - setGroup(name, 'visible', false) + const groupName = name.split(':')[1] + const exactGroup = vue.groups ? Object.values(vue.groups).find(group => group.name === groupName) : null + + if (exactGroup?.groupType === 'dialog') { + // we are setting dialog visibiliry: false + setGroup(name, 'showDialog', `false-${Date.now().toString()}`) + } else { + // we are setting visibility: false + setGroup(name, 'visible', false) + } }) } if ('disable' in payload.groups) {