Skip to content

Commit

Permalink
Merge pull request #845 from FlowFuse/387-ui-link
Browse files Browse the repository at this point in the history
New Widget - UI Link
  • Loading branch information
joepavitt authored May 14, 2024
2 parents 56a57f5 + 61236e5 commit e82089b
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 44 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default ({ mode }) => {
items: [
{ text: 'ui-base', link: '/nodes/config/ui-base' },
{ text: 'ui-page', link: '/nodes/config/ui-page' },
{ text: 'ui-link', link: '/nodes/config/ui-link' },
{ text: 'ui-group', link: '/nodes/config/ui-group' },
{ text: 'ui-theme', link: '/nodes/config/ui-theme' }
]
Expand Down
23 changes: 23 additions & 0 deletions docs/nodes/config/ui-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
description: Manage your dashboard pages with ease in Node-RED Dashboard 2.0 for a streamlined user experience.
props:
UI: The UI (<code>ui-base</code>) that this page will be added to.
Path: The URL to navigate to when a user selects this link
Icon: Which <a href="https://pictogrammers.com/library/mdi/">Material Designs Icon</a> to use for the page. No need to include the <code>mdi-</code> prefix.
Default State: <ul><li><b>Visibility</b> - Defines the default visibility of this page in hte side navigation menu.</li><li><b>Interactivity</b> - Controls whether the item is disabled/enabled in the side navigation menu.</li></ul><p>Both of these can be overridden by the user at runtime using a <code>ui-control</code> node.</p>
---

<script setup>
</script>

# Config: UI Link `ui-link`

If you want to link to external resources from your Dashboard, you can do so with the `ui-link` config node. This will render a link in the side navigation menu, just like your Dashboard [Pages](./ui-page.md), but will navigate directly to the URL you specify, even if out of the scope of Dashboard 2.0.

## Properties

<PropsTable :hide-dynamic="true"/>

## Adding Links

To add a link to your Dashboard, you can use the Dashboard 2.0 side panel in the Node-RED editor. Click the `+ Link` button to add a new item to the list. You can then configure the link with the relevant properties.
1 change: 1 addition & 0 deletions nodes/config/locales/en-US/ui_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"layout": {
"pages": "Pages",
"page": "Page",
"link": "Link",
"group": "Group",
"edit": "Edit",
"focus": "Focus",
Expand Down
17 changes: 17 additions & 0 deletions nodes/config/locales/en-US/ui_link.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"ui-link": {
"label": {
"linkName": "Link",
"ui": "UI",
"path": "Path",
"icon": "Icon",
"defaultState": "Default State",
"visibility": "Visibility",
"visible": "Visible",
"hidden": "Hidden",
"interactivity": "Interactivity",
"active": "Active",
"disabled": "Disabled"
}
}
}
134 changes: 96 additions & 38 deletions nodes/config/ui_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@

/**
* @typedef {Object} DashboardItem - A widget/group/page/subflow item
* @property {String} itemType - The type of item (e.g. 'widget', 'group', 'page')
* @property {String} itemType - The type of item (e.g. 'widget', 'group', 'page', 'link')
* @property {String} id - The unique id of the item
* @property {String} name - The name of the item
* @property {String} type - The type of the item (e.g. 'ui-button', 'ui-template', 'ui-group', 'ui-page')
Expand Down Expand Up @@ -283,6 +283,7 @@
}
if (hasProperty(node, 'group')) { item.group = node.group }
if (hasProperty(node, 'page')) { item.page = node.page }
if (hasProperty(node, 'link')) { item.link = node.link }
if (hasProperty(node, 'theme')) { item.theme = node.theme }
if (hasProperty(node, 'env') && Array.isArray(node.env) && /subflow:.+/.test(node.type)) {
const envOrder = node.env.find(e => e.key === 'DB2_SF_ORDER')
Expand All @@ -292,6 +293,7 @@
}
switch (node.type) {
case 'ui-page':
case 'ui-link':
case 'ui-group':
case 'ui-theme':
case 'ui-base':
Expand Down Expand Up @@ -517,6 +519,22 @@
return pageNode
}

function addDefaultLink (baseId) {
const link = RED.nodes.getType('ui-link')
const linkNode = {
_def: link,
id: RED.nodes.id(),
type: 'ui-link',
...mapDefaults(link.defaults),
path: '/',
name: 'Link',
ui: baseId
}

addConfigNode(linkNode)
return linkNode
}

function addDefaultGroup (pageId) {
const group = RED.nodes.getType('ui-group')
const groupNode = {
Expand Down Expand Up @@ -672,7 +690,7 @@
* @param {DashboardItem} item - The page/group/widget that these actions are bound to
*/
function addRowActions (parent, item, list) {
const configNodes = ['ui-base', 'ui-page', 'ui-group', 'ui-theme']
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)) {
const focusButton = $('<a href="#" class="nr-db-sb-tab-focus-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-bullseye"></i> ' + c_('layout.focus') + '</a>').appendTo(btnGroup)
Expand Down Expand Up @@ -710,7 +728,7 @@
*/
function addRowStateOptions (parent, dashboardItem) {
const item = dashboardItem.node
const nodes = ['ui-page', 'ui-group']
const nodes = ['ui-page', 'ui-link', 'ui-group']
const btnGroup = $('<div>', { class: 'nrdb2-sb-list-header-state-options', id: item.id }).appendTo(parent)
if (nodes.includes(item.type)) {
const visibleIcon = (item.visible === 'false' || item.visible === false) ? 'fa-eye-slash' : 'fa-eye'
Expand Down Expand Up @@ -810,7 +828,6 @@
connectWith: '.nrdb2-sb-group-list',
addItem: function (container, i, group) {
if (!group || !group.id) {
console.log('add group', group, 'to', pageId)
// this is a new page that's been added and we need to setup the basics
group = addDefaultGroup(pageId)
RED.editor.editConfig('', group.type, group.id)
Expand Down Expand Up @@ -1021,6 +1038,14 @@
.appendTo(buttonGroup)
RED.popover.tooltip(buttonExpand, c_('layout.expand'))

// add link button
$('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> ' + c_('layout.link') + '</a>')
.click(function (evt) {
pagesOL.editableList('addItem', { type: 'ui-link' })
evt.preventDefault()
})
.appendTo(buttonGroup)

// add page button
$('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> ' + c_('layout.page') + '</a>')
.click(function (evt) {
Expand All @@ -1033,6 +1058,7 @@

/** @type {DashboardItemLookup} */
const pages = {}
const links = {}
/** @type {DashboardItemLookup} */
const groupsByPage = {}
const unattachedGroups = []
Expand All @@ -1043,8 +1069,9 @@
RED.nodes.eachConfig(function (n) {
if (n.type === 'ui-page' && !!n.ui) {
pages[n.id] = toDashboardItem(n)
}
if (n.type === 'ui-group') {
} else if (n.type === 'ui-link') {
links[n.id] = toDashboardItem(n)
} else if (n.type === 'ui-group') {
const p = n.page
if (!p) {
unattachedGroups.push(toDashboardItem(n))
Expand Down Expand Up @@ -1116,35 +1143,61 @@
const pagesOL = $('<ol>', { class: 'nrdb2-sb-pages-list' }).appendTo(divTabs).editableList({
sortable: '.nrdb2-sb-pages-list-header',
addButton: false,
addItem: function (container, i, page) {
if (!page || !page.id) {
// this is a new page that's been added and we need to setup the basics
page = addDefaultPage()
RED.editor.editConfig('', page.type, page.id)
}
const groups = groupsByPage[page.id] || []
addItem: function (container, i, item) {
if (item && item.type === 'ui-link') {
// want to create a new link
if (!item || !item.id) {
// create a default link
item = addDefaultLink()
RED.editor.editConfig('', item.type, item.id)
}
// add it to the list of pages/links
container.addClass('nrdb2-sb-pages-list-item')
const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-pages-list-header' }).appendTo(container)

container.addClass('nrdb2-sb-pages-list-item')
// build title row
$('<i class="nrdb2-sb-list-handle nrdb2-sb-page-list-handle fa fa-bars"></i>').appendTo(titleRow)
const linkIcon = 'fa-link'
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-tab-icon fa ' + linkIcon }).appendTo(titleRow)
$('<span>', { class: 'nrdb2-sb-title' }).text(item.name || item.id).appendTo(titleRow)

const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-pages-list-header' }).appendTo(container)
const groupsList = $('<div>', { class: 'nrdb2-sb-group-list-container' }).appendTo(container)
// link - actions
const actions = $('<div>', { class: 'nrdb2-sb-list-header-actions' }).appendTo(titleRow)
// add "Edit" and "Focus" buttons
addRowActions(actions, item)
// Add visibility/disabled options
addRowStateOptions(actions, item)
} else {
// is a page, with groups and widgets inside
if (!item || !item.id) {
// this is a new page that's been added and we need to setup the basics
item = addDefaultPage()
RED.editor.editConfig('', item.type, item.id)
}
const groups = groupsByPage[item.id] || []

// build title row
$('<i class="nrdb2-sb-list-handle nrdb2-sb-page-list-handle fa fa-bars"></i>').appendTo(titleRow)
const chevron = $('<i class="fa fa-angle-down nrdb2-sb-list-chevron">', { style: 'width:10px;' }).appendTo(titleRow)
const tabicon = 'fa-object-group'
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-tab-icon fa ' + tabicon }).appendTo(titleRow)
$('<span>', { class: 'nrdb2-sb-title' }).text(page.name || page.id).appendTo(titleRow)
$('<span>', { class: 'nrdb2-sb-info' }).text(`${groups.length} Groups`).appendTo(titleRow)
container.addClass('nrdb2-sb-pages-list-item')

// adds groups within this page
titleRow.click(titleToggle(page.id, groupsList, chevron))
const groupsOL = addGroupOrderingList(page.id, groupsList, groups, widgetsByGroup)
const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-pages-list-header' }).appendTo(container)
const groupsList = $('<div>', { class: 'nrdb2-sb-group-list-container' }).appendTo(container)

// page - actions
const actions = $('<div>', { class: 'nrdb2-sb-list-header-actions' }).appendTo(titleRow)
addRowActions(actions, page, groupsOL)
addRowStateOptions(actions, page)
// build title row
$('<i class="nrdb2-sb-list-handle nrdb2-sb-page-list-handle fa fa-bars"></i>').appendTo(titleRow)
const chevron = $('<i class="fa fa-angle-down nrdb2-sb-list-chevron">', { style: 'width:10px;' }).appendTo(titleRow)
const tabicon = 'fa-object-group'
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-tab-icon fa ' + tabicon }).appendTo(titleRow)
$('<span>', { class: 'nrdb2-sb-title' }).text(item.name || item.id).appendTo(titleRow)
$('<span>', { class: 'nrdb2-sb-info' }).text(`${groups.length} Groups`).appendTo(titleRow)

// adds groups within this page
titleRow.click(titleToggle(item.id, groupsList, chevron))
const groupsOL = addGroupOrderingList(item.id, groupsList, groups, widgetsByGroup)

// page - actions
const actions = $('<div>', { class: 'nrdb2-sb-list-header-actions' }).appendTo(titleRow)
addRowActions(actions, item, groupsOL)
addRowStateOptions(actions, item)
}
},
sortItems: function (items) {
// track any changes
Expand All @@ -1159,15 +1212,20 @@
}
})

Object.values(pages).sort((a, b) => a.order - b.order).forEach(function (page) {
const groups = groupsByPage[page.id] || []
if (RED._db2debug) { console.log('dashboard 2: ui_base.html: buildLayoutOrderEditor: adding groups', groups) }
if (page) {
pagesOL.editableList('addItem', page)
}
// groups.forEach(() => {
const items = {
...pages,
...links
}

// })
Object.values(items).sort((a, b) => a.order - b.order).forEach(function (item) {
let groups = []
if (item.type === 'ui-page' && item.id) {
if (RED._db2debug) { console.log('dashboard 2: ui_base.html: buildLayoutOrderEditor: adding groups', groups) }
groups = groupsByPage[item.id] || []
}
if (item) {
pagesOL.editableList('addItem', item)
}
})

// add Unattached Groups to the bottom
Expand Down
4 changes: 2 additions & 2 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ module.exports = function (RED) {
const pageNodes = []
const themes = []
RED.nodes.eachNode(n => {
if (n.type === 'ui-page') {
if (n.type === 'ui-page' || n.type === 'ui-link') {
pageNodes.push(n)
} else if (n.type === 'ui-base' && n.id !== node.id) {
baseNodes.push(n)
Expand Down Expand Up @@ -880,7 +880,7 @@ module.exports = function (RED) {

// map pages by their ID
if (page && !node.ui.pages.has(page?.id)) {
const { _user, type, ...p } = page
const { _users, ...p } = page
node.ui.pages.set(page.id, p)
}

Expand Down
Loading

0 comments on commit e82089b

Please sign in to comment.