From 5430e48c67f2b6e941fb5342446ae022b622ff73 Mon Sep 17 00:00:00 2001 From: Pez Cuckow Date: Thu, 16 Nov 2023 15:16:58 +0100 Subject: [PATCH 01/20] Named export of dashboard components --- .gitignore | 8 +++- docs/contributing/widgets/third-party.md | 8 ++-- ui/public/index.html | 22 ---------- ui/src/widgets/index.mjs | 53 ++++++++++++++---------- 4 files changed, 41 insertions(+), 50 deletions(-) delete mode 100644 ui/public/index.html diff --git a/.gitignore b/.gitignore index f42f9547..d6a147cb 100644 --- a/.gitignore +++ b/.gitignore @@ -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 @@ -132,5 +138,5 @@ dist .github/.DS_Store .DS_Store - +# Vitepress docs/.vitepress/cache/ \ No newline at end of file diff --git a/docs/contributing/widgets/third-party.md b/docs/contributing/widgets/third-party.md index 7c97e3fa..97f35a9c 100644 --- a/docs/contributing/widgets/third-party.md +++ b/docs/contributing/widgets/third-party.md @@ -2,11 +2,11 @@ If you have an idea for a widget that you'd like to build in Dashboard 2.0 we are open to Pull Requests and ideas for additions to the [core collection](../../nodes/widgets.md) of Widgets. -We do also realise though that there are many occassions where a standalone repository/package works better as was very popular in Dashboard 1.0. +We do also realise though that there are many occasions where a standalone repository/package works better as was very popular in Dashboard 1.0. ## Quick Links -Here is a quick summary of the features and fucntionality available to third-party widgets. +Here is a quick summary of the features and functionality available to third-party widgets. - **Custom Dependencies** - ([link](#injecting-head-tags-dependencies)) Injection of external widget dependencies (e.g. other JavaScript libraries) via ``. - **Defining Content to Render** - ([link](#defining-html-to-render)) `format` defines the HTML to render in the Dashboard. @@ -160,12 +160,12 @@ The `ui-template` (which your third-party widget will extend) relies on a `forma ```js -fs.readFile(path.join(__dirname, '../ui', 'UIExample.vue'), 'utf8', (err, html) => { +fs.readFile(path.join(__dirname, '../ui', 'my-html.html'), 'utf8', (err, html) => { config.format = html }) ``` -This will read our `UIExample.vue` as a string, and then bind it to the node. +This will read our `my-html.html` as a string, and then bind it to the node. ### Defining `onInput` Functionality diff --git a/ui/public/index.html b/ui/public/index.html deleted file mode 100644 index 46aeee21..00000000 --- a/ui/public/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - -
-
- - - diff --git a/ui/src/widgets/index.mjs b/ui/src/widgets/index.mjs index c26da185..a6529348 100644 --- a/ui/src/widgets/index.mjs +++ b/ui/src/widgets/index.mjs @@ -1,43 +1,50 @@ -/* -// sorted imports - temporarily disabled until I get it all working again! import UIButton from './ui-button/UIButton.vue' import UIChart from './ui-chart/UIChart.vue' import UIDropdown from './ui-dropdown/UIDropdown.vue' -import UIMarkdown from './ui-markdown/UIMarkdown.vue' -import UISlider from './ui-slider/UISlider.vue' -import UISwitch from './ui-switch/UISwitch.vue' -import UITemplate from './ui-template/UITemplate.vue' -import UIText from './ui-text/UIText.vue' -import UITextInput from './ui-text-input/UITextInput.vue' -*/ - -/* eslint-disable import/order */ -import UIButton from './ui-button/UIButton.vue' -import UIDropdown from './ui-dropdown/UIDropdown.vue' -import UITable from './ui-table/UITable.vue' import UIForm from './ui-form/UIForm.vue' -import UIChart from './ui-chart/UIChart.vue' +import UIMarkdown from './ui-markdown/UIMarkdown.vue' +import UINotification from './ui-notification/UINotification.vue' import UIRadioGroup from './ui-radio-group/UIRadioGroup.vue' import UISlider from './ui-slider/UISlider.vue' import UISwitch from './ui-switch/UISwitch.vue' -import UINotification from './ui-notification/UINotification.vue' -import UIMarkdown from './ui-markdown/UIMarkdown.vue' +import UITable from './ui-table/UITable.vue' import UITemplate from './ui-template/UITemplate.vue' import UIText from './ui-text/UIText.vue' import UITextInput from './ui-text-input/UITextInput.vue' +// Named exports for use in other components +export { + UIButton, + UIChart, + UIDropdown, + UIForm, + UIMarkdown, + UINotification, + UIRadioGroup, + UISlider, + UISwitch, + UITable, + UITemplate, + UIText, + UITextInput +} + +// Component helpers +export { useDataTracker } from './data-tracker.mjs' + +// Exported as an object for look up by widget name export default { 'ui-button': UIButton, + 'ui-chart': UIChart, 'ui-dropdown': UIDropdown, - 'ui-table': UITable, 'ui-form': UIForm, - 'ui-chart': UIChart, + 'ui-markdown': UIMarkdown, + 'ui-notification': UINotification, 'ui-radio-group': UIRadioGroup, 'ui-slider': UISlider, 'ui-switch': UISwitch, - 'ui-notification': UINotification, - 'ui-markdown': UIMarkdown, + 'ui-table': UITable, 'ui-template': UITemplate, - 'ui-text': UIText, - 'ui-text-input': UITextInput + 'ui-text-input': UITextInput, + 'ui-text': UIText } From e73ad6353f5028251c3280cb4aadc2c2e2b1684a Mon Sep 17 00:00:00 2001 From: Pez Cuckow Date: Thu, 16 Nov 2023 15:39:27 +0100 Subject: [PATCH 02/20] Load third party widgets and serve on server --- nodes/config/ui_base.js | 40 ++++++++++++++++++++++- ui/src/widgets/ui-markdown/UIMarkdown.vue | 1 - 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/nodes/config/ui_base.js b/nodes/config/ui_base.js index 03808e22..52c765a9 100644 --- a/nodes/config/ui_base.js +++ b/nodes/config/ui_base.js @@ -1,4 +1,4 @@ -// const Emitter = require('events').EventEmitter +const fs = require('fs') const path = require('path') const v = require('../../package.json').version @@ -55,6 +55,44 @@ module.exports = function (RED) { uiShared.httpMiddleware = uiShared.settings.middleware } } + + /** + * Load in third party widgets + */ + + const packagePath = path.join(RED.settings.userDir, 'package.json') + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')) + + Object.entries(packageJson.dependencies).filter(([packageName, _packageVersion]) => { + return packageName.includes('node-red-dashboard-') + }).map(([packageName, _packageVersion]) => { + const modulePath = path.join(RED.settings.userDir, 'node_modules', packageName) + const packagePath = path.join(modulePath, 'package.json') + try { + // 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]) => { + const source = widgetConfig.source + + // make the `source` file available via our express server and to the UI + const url = config.path + '/widgets/' + packageName + '/' + widgetName + '.vue' + const widgetPath = path.join(modulePath, source) + uiShared.app.use(url, uiShared.httpMiddleware, express.static(widgetPath)) + + uiShared.contribs[widgetName] = { + package: packageName, + name: widgetName, + src: url + } + }) + } + return packageJson + } catch (error) { /* do nothing */ } + return null + }) + /** * Configure Web Server to handle UI traffic */ diff --git a/ui/src/widgets/ui-markdown/UIMarkdown.vue b/ui/src/widgets/ui-markdown/UIMarkdown.vue index f2e2292f..3233f765 100644 --- a/ui/src/widgets/ui-markdown/UIMarkdown.vue +++ b/ui/src/widgets/ui-markdown/UIMarkdown.vue @@ -1,7 +1,6 @@ - ``` -### Functions +Vuetify also comes with a handful of utility classes to assist with styling, which can all be used out of the box: -- `send` - Send a `msg` (defined by the input to this function call) from this node in the Node-RED flow. +- [Responsive Displays](https://vuetifyjs.com/en/styles/display/#display) +- [Flex](https://vuetifyjs.com/en/styles/flex/) +- [Spacing](https://vuetifyjs.com/en/styles/spacing/#how-it-works) +- [Text & Typography](https://vuetifyjs.com/en/styles/text-and-typography/#typography) -Let's consider: -```vue -

- This is a 3rd Party Widget -

+ +### External Dependencies + +Your widget can have any number of `npm` dependencies. These will all be bundled into the `.umd.js` file that Dashboard loads at runtime. + +In `ui-example` we have a dependency on `to-title-case`, which we import into, and use in, our Vue component as follows: + +```js +import toTitleCase from 'to-title-case' + +export default { + // rest of component here + computed: { + titleCase () { + return toTitleCase(this.input.title) + } + } +} ``` -This, when clicked, will send a `msg` in Node-RED to any nodes connected to the output of this node. \ No newline at end of file +You can also load in other Vue components from within your own repository as with any VueJS component. diff --git a/nodes/config/ui_base.js b/nodes/config/ui_base.js index a9d872e0..4c7e7f31 100644 --- a/nodes/config/ui_base.js +++ b/nodes/config/ui_base.js @@ -70,7 +70,7 @@ module.exports = function (RED) { if (packageJson) { Object.entries(packageJson.dependencies).filter(([packageName, _packageVersion]) => { - return packageName.includes('node-red-dashboard-') + 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') From e21a3ac25e02b410fc02153e67e9ec33f440767f Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Wed, 22 Nov 2023 12:33:58 +0000 Subject: [PATCH 20/20] Include "Basics of VueJS' example --- docs/contributing/widgets/third-party.md | 58 +++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/contributing/widgets/third-party.md b/docs/contributing/widgets/third-party.md index dfd9b04c..bffdd60f 100644 --- a/docs/contributing/widgets/third-party.md +++ b/docs/contributing/widgets/third-party.md @@ -119,11 +119,67 @@ module.exports = function(RED) { ## Guides +### The Basics of VueJS + +Aware that a lot of developers that may want to contribute to Dashboard 2.0, may be new to VueJS, so we've detailed a few fundamentals here. + +It is very common place since VueJS to see Vue applications using the "Composition API", whilst this is lighter weight way of building your applications, it isn't the most intuitive for those unfamiliar with VueJS, as such, we're mostly using the "Options API" structure across Dashboard 2.0 and in our examples for readibility. + +With the Options API, a Vue component has the following structure: + +```vue + + + + + +``` + + ### Using Vuetify Components You're free to define complety custom HTML/CSS when defining your widgets, but we've also provided native support for all of [Vuetify's Component Library](https://vuetifyjs.com/en/components/all/) to get your started with a wide range of UI components that you may want to utilise. - ### Accessing Properties When widgets are rendered in a Dashboard layout, they are passed a small collection of properties that can be used to customise the widget's behaviour: