Skip to content

Commit

Permalink
Merge pull request #351 from FlowFuse/feat-307-third-party-widgets
Browse files Browse the repository at this point in the history
Third Party Widget Support
  • Loading branch information
joepavitt committed Nov 22, 2023
2 parents d8dbe51 + e21a3ac commit 1ea33a4
Show file tree
Hide file tree
Showing 17 changed files with 906 additions and 471 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Custom
node_modules
dist
dist-ssr
*.local

# Runtime data
pids
*.pid
Expand Down Expand Up @@ -132,5 +138,5 @@ dist
.github/.DS_Store
.DS_Store


# Vitepress
docs/.vitepress/cache/
445 changes: 272 additions & 173 deletions docs/contributing/widgets/third-party.md

Large diffs are not rendered by default.

54 changes: 50 additions & 4 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// const Emitter = require('events').EventEmitter
const fs = require('fs')
const path = require('path')

const v = require('../../package.json').version
Expand Down Expand Up @@ -30,7 +30,8 @@ module.exports = function (RED) {
ioServer: null,
/** @type {Object.<string, Socket>} */
connections: {},
settings: {}
settings: {},
contribs: {}
}

/**
Expand All @@ -56,6 +57,41 @@ module.exports = function (RED) {
uiShared.httpMiddleware = uiShared.settings.middleware
}
}

/**
* Load in third party widgets
*/
let packagePath, packageJson
if (RED.settings?.userDir) {
packagePath = path.join(RED.settings.userDir, 'package.json')
packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
} else {
node.log('Cannot import third party widgets. No access to Node-RED package.json')
}

if (packageJson) {
Object.entries(packageJson.dependencies).filter(([packageName, _packageVersion]) => {
return packageName.includes('node-red-dashboard-2-')
}).map(([packageName, _packageVersion]) => {
const modulePath = path.join(RED.settings.userDir, 'node_modules', packageName)
const packagePath = path.join(modulePath, 'package.json')
// get third party package.json
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
if (packageJson?.['node-red-dashboard-2']) {
// loop over object of widgets
Object.entries(packageJson['node-red-dashboard-2'].widgets).forEach(([widgetName, widgetConfig]) => {
uiShared.contribs[widgetName] = {
package: packageName,
name: widgetName,
src: widgetConfig.output,
component: widgetConfig.component
}
})
}
return packageJson
})
}

/**
* Configure Web Server to handle UI traffic
*/
Expand Down Expand Up @@ -279,10 +315,15 @@ module.exports = function (RED) {

// check if any widgets have defined custom socket events
// most common with third-party widgets that are not part of core Dashboard 2.0
const registered = [] // track which widget types we've already subscribed for
node.ui?.widgets?.forEach((widget) => {
if (widget.hooks?.onSocket) {
for (const [eventName, handler] of Object.entries(widget.hooks.onSocket)) {
socket.on(eventName, handler)
// we only need add the listener for a given event type the once
if (registered.indexOf(widget.type) === -1) {
socket.on(eventName, handler.bind(null, socket))
registered.push(widget.type)
}
}
}
})
Expand Down Expand Up @@ -471,6 +512,10 @@ module.exports = function (RED) {
widgets: new Map()
}

node.stores = {
data: datastore
}

/**
* Queue up a config emit to the UI. This is a debounced function
* NOTES:
Expand Down Expand Up @@ -520,7 +565,8 @@ module.exports = function (RED) {
enabled: datastore.get(widgetConfig.id)?.enabled || true,
visible: datastore.get(widgetConfig.id)?.visible || true
},
hooks: widgetEvents
hooks: widgetEvents,
src: uiShared.contribs[widgetConfig.type]
}

delete widget.props.id
Expand Down
5 changes: 5 additions & 0 deletions nodes/config/ui_group.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ module.exports = function (RED) {
const group = config
page.deregister(group, widgetNode)
}

// Return the UI Base Node this group lives in
node.getBase = function () {
return RED.nodes.getNode(config.page).getBase()
}
}
RED.nodes.registerType('ui-group', UIGroupNode)
}
5 changes: 5 additions & 0 deletions nodes/config/ui_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ module.exports = function (RED) {
ui.deregister(page, group, widgetNode)
}
}

// Return the UI Base Node this page lives in
node.getBase = function () {
return RED.nodes.getNode(config.ui)
}
}
RED.nodes.registerType('ui-page', UIPageNode)
}

0 comments on commit 1ea33a4

Please sign in to comment.