Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Widget - UI Link #845

Merged
merged 3 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading