Skip to content

Commit

Permalink
Merge pull request #1430 from FlowFuse/1415-spacer
Browse files Browse the repository at this point in the history
Add ui-spacer
  • Loading branch information
joepavitt authored Nov 4, 2024
2 parents 6d22afb + d5e6613 commit ef28476
Show file tree
Hide file tree
Showing 15 changed files with 494 additions and 11 deletions.
108 changes: 108 additions & 0 deletions cypress/fixtures/flows/dashboard-spacer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
[
{
"id": "node-red-tab-text",
"type": "tab",
"label": "UI Text",
"disabled": false,
"info": "",
"env": []
},
{
"id": "dashboard-ui-base",
"type": "ui-base",
"name": "UI Name",
"path": "/dashboard",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
]
},
{
"id": "dashboard-ui-group",
"type": "ui-group",
"name": "Group 1",
"page": "dashboard-ui-page-1",
"width": "6",
"height": "1",
"order": 1,
"showTitle": true,
"className": "",
"visible": "true",
"disabled": "false",
"groupType": "default"
},
{
"id": "dashboard-ui-page-1",
"type": "ui-page",
"name": "Page 1",
"ui": "dashboard-ui-base",
"path": "/page1",
"icon": "",
"layout": "grid",
"theme": "dashboard-ui-theme",
"order": 1,
"className": "",
"visible": "true",
"disabled": false
},
{
"id": "dashboard-ui-theme",
"type": "ui-theme",
"name": "Theme Name",
"colors": {
"surface": "#ffffff",
"primary": "#0094ce",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
}
},
{
"id": "dashboard-ui-spacer",
"type": "ui-spacer",
"group": "dashboard-ui-group",
"name": "spacer",
"order": 1,
"width": "3",
"height": "3",
"className": "class1 class2"
},
{
"id": "2a04f859315b5a00",
"type": "ui-button",
"z": "node-red-tab-text",
"group": "dashboard-ui-group",
"name": "",
"label": "button",
"order": 2,
"width": "3",
"height": "3",
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "",
"payloadType": "str",
"topic": "topic",
"topicType": "msg",
"buttonColor": "",
"textColor": "",
"iconColor": "",
"enableClick": true,
"enablePointerdown": false,
"pointerdownPayload": "",
"pointerdownPayloadType": "str",
"enablePointerup": false,
"pointerupPayload": "",
"pointerupPayloadType": "str",
"x": 110,
"y": 100,
"wires": [
[]
]
}
]
31 changes: 31 additions & 0 deletions cypress/tests/widgets/spacer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
describe('Node-RED Dashboard 2.0 - Spacer', () => {
beforeEach(() => {
cy.deployFixture('dashboard-spacer')
cy.visit('/dashboard/page1')
})

it('Is rendered', () => {
// should have one child
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').children().should('have.length', 1)
// and that child should be a div
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').children().eq(0).should('have.prop', 'tagName', 'DIV')

// inner div should have no content and no children
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').children().eq(0).should('not.have.text')
cy.get('#nrdb-ui-widget-dashboard-ui-spacer > div').children().should('not.exist')
})

it('Has correct class names and styling', () => {
// default classes are applied to main widget
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').should('have.class', 'nrdb-ui-widget')
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').should('have.class', 'nrdb-ui-spacer')
// custom classes .class1 and .class2 are applied to main widget div
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').should('have.class', 'class1')
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').should('have.class', 'class2')
// check widget styling
cy.get('#nrdb-ui-widget-dashboard-ui-spacer').children().eq(0).should('have.css', 'grid-row-end', 'span 3')
// check inner div classes
cy.get('#nrdb-ui-widget-dashboard-ui-spacer > div').should('have.class', 'nrdb-spacer')
cy.get('#nrdb-ui-widget-dashboard-ui-spacer > div').should('have.class', 'v-spacer') // vuetify class
})
})
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default ({ mode }) => {
{ text: 'ui-number-input', link: '/nodes/widgets/ui-number-input' },
{ text: 'ui-radio-group', link: '/nodes/widgets/ui-radio-group' },
{ text: 'ui-slider', link: '/nodes/widgets/ui-slider' },
{ text: 'ui-spacer', link: '/nodes/widgets/ui-spacer' },
{ text: 'ui-switch', link: '/nodes/widgets/ui-switch' },
{ text: 'ui-table', link: '/nodes/widgets/ui-table' },
{ text: 'ui-template', link: '/nodes/widgets/ui-template' },
Expand Down
4 changes: 4 additions & 0 deletions docs/nodes/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ description: Explore the wide range of widgets available in Node-RED Dashboard 2
widget: 'ui-text',
image: '/images/node-examples/ui-text.png',
description: 'Displays a non-editable text field on your dashboard.'
}, {
name: 'Spacer',
widget: 'ui-spacer',
description: 'Adds a simple spacer to your group.'
}]

const form = [{
Expand Down
34 changes: 34 additions & 0 deletions docs/nodes/widgets/ui-spacer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: The Spacer widget is a simple spacer to aid with element layout.
props:
Group: Defines which group of the UI Dashboard this widget will render in.
Size: Controls the width and height of the spacer with respect to the parent group. Maximum value is the width of the group.
---

<script setup>
import AddedIn from '../../components/AddedIn.vue';
import TryDemo from "./../../components/TryDemo.vue";
</script>

<TryDemo href="spacer">

# Spacer `ui-spacer` <AddedIn version="1.9.0" />

</TryDemo>

This widget allows users to add a simple spacer to their dashboard groups, aiding with element layout.

## Properties

<PropsTable/>


## Current Limitations

_Currently_
* the spacer widgets belong to the global flow and are not exported along with the group unless the entire flow is exported. This means that if you want to export a group with spacer widgets, you will need to either export the entire flow or add the spacer widgets manually after importing the group.
* Editing a spacer is only possible on the Dashboard 2.0 sidbar and not from the the Node-RED config sidebar.

## Using the Spacer Widget

From the Dashboard 2.0 sidebar, hover over the group you want to add the spacer to and click the `↔ spacer` button. This will add a spacer to the group that you can then drag to the desired location. Opening the spacer's edit dialog will allow you to set the size of the spacer.
2 changes: 2 additions & 0 deletions nodes/config/locales/en-US/ui_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"focus": "Focus",
"collapse": "Collapse",
"expand": "Expand",
"delete": "Delete",
"spacer": "Add Spacer",
"unattachedGroups": "Unattached Groups"
},
"themes": {
Expand Down
73 changes: 64 additions & 9 deletions nodes/config/ui_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,25 @@
}
return linkNode
}

function createSpacerNode ({ name, order, group } = {}) {
if (RED._db2debug) { console.log('dashboard 2: ui_base.html: createSpacerNode', name, group) }
const spacer = RED.nodes.getType('ui-spacer')
const spacerNode = {
_def: spacer,
id: RED.nodes.id(),
type: 'ui-spacer',
...mapDefaults(spacer.defaults),
users: [],
name: name ?? 'spacer',
group: group || null,
order: order ?? 0,
hasUsers: false,
dirty: true
}
return spacerNode
}

function createPageNode ({ name, order, layout = 'grid' } = {}) {
if (RED._db2debug) { console.log('dashboard 2: ui_base.html: createPageNode', name, layout) }
const page = RED.nodes.getType('ui-page')
Expand Down Expand Up @@ -1071,11 +1090,12 @@
* Add group of actions to the right-side of a row in the sidebar editable list.
* @param {Object} parent - jQuery object to add this button group as a child element to
* @param {DashboardItem} item - The page/group/widget that these actions are bound to
* @param {Object} list - The editableList instance associated with this item
*/
function addRowActions (parent, item, list) {
const configNodes = ['ui-base', 'ui-page', 'ui-link', 'ui-group', 'ui-theme']
const btnGroup = $('<div>', { class: 'nrdb2-sb-list-header-button-group', id: item.id }).appendTo(parent)
if (!configNodes.includes(item.type)) {
if (!configNodes.includes(item.type) && item.type !== 'ui-spacer') {
const focusButton = $(`<a href="#" class="nr-db-sb-tab-focus-button editor-button editor-button-small nr-db-sb-list-header-button" title="${c_('layout.focus')}"><i class="fa fa-bullseye"></i></a>`).appendTo(btnGroup)
focusButton.on('click', function (evt) {
RED.view.reveal(item.id)
Expand All @@ -1085,7 +1105,7 @@
}

// button to edit node via node-red editor panel
const editButton = $(`<a href="#" class="nr-db-sb-tab-edit-button editor-button editor-button-small nr-db-sb-list-header-button" title=${c_('layout.edit')}><i class="fa fa-cog"></i></a>`).appendTo(btnGroup)
const editButton = $(`<a href="#" class="nr-db-sb-tab-edit-button editor-button editor-button-small nr-db-sb-list-header-button" title="${c_('layout.edit')}"><i class="fa fa-cog"></i></a>`).appendTo(btnGroup)
editButton.on('click', function (evt) {
if (configNodes.includes(item.type)) {
RED.editor.editConfig('', item.type, item.id)
Expand Down Expand Up @@ -1160,6 +1180,24 @@
evt.preventDefault()
})
}

// if this is a group & it is not an unattached group, add the "_ spacer" button
if (item.type === 'ui-group' && !!item.page) {
// add the "+ spacer" button
$(`<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button" title="${c_('layout.spacer')}"><i class="fa fa-plus"></i></a>`)
.on('click', function (evt) {
const spacerNode = createSpacerNode({ name: 'spacer', group: item.id, order: list.editableList('items').length + 1 })
const history = []
addNode(spacerNode, history)
assignGroup(spacerNode, item.node, history)
recordEvents(history)
evt.preventDefault()
const widget = toDashboardItem(spacerNode)
list.editableList('addItem', widget)
RED.editor.edit(widget.node)
})
.appendTo(btnGroup)
}
}

/**
Expand Down Expand Up @@ -1316,16 +1354,17 @@
$('<span>', { class: 'nrdb2-sb-info' }).text(`${widgets.length} Widgets`).appendTo(titleRow)

const actions = $('<div>', { class: 'nrdb2-sb-list-header-actions' }).appendTo(titleRow)
addRowActions(actions, group)
addRowStateOptions(actions, group)

// adds widgets within this group
const widgetsList = $('<div>', { class: 'nrdb2-sb-widget-list-container' }).appendTo(container)

// add chevron/list toggle
titleRow.click(titleToggle(group.id, widgetsList, chevron))

addWidgetToList(group.id, widgetsList, widgets)
const widgetsOL = addWidgetToList(group.id, widgetsList, widgets)

addRowActions(actions, group, widgetsOL)
addRowStateOptions(actions, group)

const events = []
updateItemOrder(groupsOL.editableList('items'), events)
Expand Down Expand Up @@ -1392,9 +1431,14 @@

// Set the icon
const ico = $('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-widget-icon' }).appendTo(titleRow)
let widgetIcon = RED.utils.getNodeIcon(widget.node?._def, widget.node) || 'fa-question'
if (widgetIcon.startsWith('font-awesome/')) {
widgetIcon = widgetIcon.replace(/^font-awesome\//, 'fa ')
let widgetIcon
if (widget.type === 'ui-spacer') {
widgetIcon = 'fa fa-arrows-h'
} else {
widgetIcon = RED.utils.getNodeIcon(widget.node?._def, widget.node) || 'fa-question'
if (widgetIcon.startsWith('font-awesome/')) {
widgetIcon = widgetIcon.replace(/^font-awesome\//, 'fa ')
}
}
if (widget.isSubflowInstance) {
// In this MVP, subflow instances are constrained to stay within own group.
Expand All @@ -1411,7 +1455,7 @@

$('<span>', { class: 'nrdb2-sb-title' }).text(widget.label?.trim() || widget.id).appendTo(titleRow)
const actions = $('<div>', { class: 'nrdb2-sb-list-header-actions' }).appendTo(titleRow)
addRowActions(actions, widget)
addRowActions(actions, widget, widgetsOL)

const events = []
updateItemOrder(widgetsOL.editableList('items'), events)
Expand Down Expand Up @@ -1454,6 +1498,8 @@
widgets.forEach(function (w) {
widgetsOL.editableList('addItem', w)
})

return widgetsOL
}

// expand / collapse buttons
Expand Down Expand Up @@ -1598,6 +1644,15 @@
}
}
})
// get ui-spacers too (they are config nodes so as not to be displayed in the editor node palette)
RED.nodes.eachConfig(function (n) {
if (n.type === 'ui-spacer' && n.group) {
if (!widgetsByGroup[n.group]) {
widgetsByGroup[n.group] = []
}
widgetsByGroup[n.group].push(toDashboardItem(n))
}
})

// update `widgetsByGroup` for subflow instances where its env has an entry which
// is a ui-group
Expand Down
Loading

0 comments on commit ef28476

Please sign in to comment.