From c4831845021254ebae982c5652d8b3f2ce26ef82 Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Fri, 29 Nov 2024 08:57:46 +0100 Subject: [PATCH 1/6] new ui-audio widget --- docs/nodes/widgets.md | 7 +- docs/nodes/widgets/ui-audio.md | 76 ++++++++++++++ nodes/widgets/locales/en-US/ui_audio.html | 31 ++++++ nodes/widgets/locales/en-US/ui_audio.json | 17 +++ nodes/widgets/ui_audio.html | 113 ++++++++++++++++++++ nodes/widgets/ui_audio.js | 61 +++++++++++ package.json | 3 +- ui/src/widgets/index.mjs | 3 + ui/src/widgets/ui-audio/UIAudio.vue | 122 ++++++++++++++++++++++ 9 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 docs/nodes/widgets/ui-audio.md create mode 100644 nodes/widgets/locales/en-US/ui_audio.html create mode 100644 nodes/widgets/locales/en-US/ui_audio.json create mode 100644 nodes/widgets/ui_audio.html create mode 100644 nodes/widgets/ui_audio.js create mode 100644 ui/src/widgets/ui-audio/UIAudio.vue diff --git a/docs/nodes/widgets.md b/docs/nodes/widgets.md index 0cba85fa..1384e4ff 100644 --- a/docs/nodes/widgets.md +++ b/docs/nodes/widgets.md @@ -8,6 +8,11 @@ description: Explore the wide range of widgets available in Node-RED Dashboard 2 import WidgetGrid from '../components/WidgetGrid.vue' const general = [{ + name: 'Audio', + widget: 'ui-audio', + image: '/images/node-examples/ui-audio.png', + description: 'Adds a audio player to your dashboard.' + }, { name: 'Button', widget: 'ui-button', image: '/images/node-examples/ui-button.png', @@ -148,4 +153,4 @@ Here is a list of the third-party widgets we're aware of to make it easier to fi The following are a list of nodes that we've been made aware of, are in active development, but have not yet been published to the Node-RED Palette Manager. -- [@bartbutenaers/ui-svg](https://github.com/bartbutenaers/node-red-dashboard-2-ui-svg/tree/master): Adds an SVG widget to your Dashboard, with dynamic controls over plotting and styling. \ No newline at end of file +- [@bartbutenaers/ui-svg](https://github.com/bartbutenaers/node-red-dashboard-2-ui-svg/tree/master): Adds an SVG widget to your Dashboard, with dynamic controls over plotting and styling. diff --git a/docs/nodes/widgets/ui-audio.md b/docs/nodes/widgets/ui-audio.md new file mode 100644 index 00000000..976bdd33 --- /dev/null +++ b/docs/nodes/widgets/ui-audio.md @@ -0,0 +1,76 @@ +--- +description: "Play dynamically audio files with ui-audio in Node-RED Dashboard 2.0." +props: + Group: Defines which group of the UI Dashboard this widget will render in. + Size: Controls the width of the button with respect to the parent group. Maximum value is the width of the group. + Source: + description: The source is the url where the audio file can be fetched.. + dynamic: true + Autoplay: + description: Specify whether the audio file will start playing automatically. + dynamic: true + Loop: + description: Specify whether the audio should be looping, i.e. start playing automatically again when ended. + dynamic: true + Muted: + description: Specify whether the audio should be muted. + dynamic: true +controls: + enabled: + example: true | false + description: Allow control over whether or not the button is clickable. +dynamic: + Source: + payload: msg.ui_update.source + structure: ["String"] + Autoplay: + payload: msg.ui_update.autoplay + structure: ["'on' | 'off'"] + Loop: + payload: msg.ui_update.loop + structure: ["'on' | 'off'"] + Muted: + payload: msg.ui_update.muted + structure: ["'on' | 'off'"] +--- + + + + + + +# Audio `ui-audio` + + + +Adds a clickable button to your dashboard. + +## Properties + + + +## Dynamic Properties + + + +## Controls + + + +## Example + +### Simple Button + +![Example of a Button](/images/node-examples/ui-button.png "Example of a Button"){data-zoomable} +*Example of a rendered button in a Dashboard.* diff --git a/nodes/widgets/locales/en-US/ui_audio.html b/nodes/widgets/locales/en-US/ui_audio.html new file mode 100644 index 00000000..59503edc --- /dev/null +++ b/nodes/widgets/locales/en-US/ui_audio.html @@ -0,0 +1,31 @@ + diff --git a/nodes/widgets/locales/en-US/ui_audio.json b/nodes/widgets/locales/en-US/ui_audio.json new file mode 100644 index 00000000..8ad28eac --- /dev/null +++ b/nodes/widgets/locales/en-US/ui_audio.json @@ -0,0 +1,17 @@ +{ + "ui-audio": { + "label": { + "group": "Group", + "size": "Size", + "icon": "Icon", + "source": "Source", + "autoplay": "Autoplay", + "loop": "Loop", + "muted": "Muted" + }, + "option": { + "on": "On", + "off": "Off" + } + } +} diff --git a/nodes/widgets/ui_audio.html b/nodes/widgets/ui_audio.html new file mode 100644 index 00000000..4f48273e --- /dev/null +++ b/nodes/widgets/ui_audio.html @@ -0,0 +1,113 @@ + + + diff --git a/nodes/widgets/ui_audio.js b/nodes/widgets/ui_audio.js new file mode 100644 index 00000000..3f801bc4 --- /dev/null +++ b/nodes/widgets/ui_audio.js @@ -0,0 +1,61 @@ +const statestore = require('../store/state.js') + +module.exports = function (RED) { + function AudioNode (config) { + const node = this + + RED.nodes.createNode(this, config) + + // which group are we rendering this widget + const group = RED.nodes.getNode(config.group) + + const evts = { + onAction: true, + beforeSend: function (msg) { + if (msg.ui_update) { + const updates = msg.ui_update + + if (updates) { + if (typeof updates.src !== 'undefined') { + // dynamically set "src" property + statestore.set(group.getBase(), node, msg, 'src', updates.src) + } + if (typeof updates.autoplay !== 'undefined') { + if (['on', 'off'].includes(updates.autoplay)) { + // dynamically set "autoplay" property + statestore.set(group.getBase(), node, msg, 'autoplay', updates.autoplay) + } else { + node.error('Property msg.ui_update.autoplay should be "on" or "off"') + } + } + if (typeof updates.loop !== 'undefined') { + if (['on', 'off'].includes(updates.loop)) { + // dynamically set "loop" property + statestore.set(group.getBase(), node, msg, 'loop', updates.loop) + } else { + node.error('Property msg.ui_update.loop should be "on" or "off"') + } + } + if (typeof updates.muted !== 'undefined') { + if (['on', 'off'].includes(updates.muted)) { + // dynamically set "muted" property + statestore.set(group.getBase(), node, msg, 'muted', updates.muted) + } else { + node.error('Property msg.ui_update.muted should be "on" or "off"') + } + } + } + } + return msg + } + } + + // inform the dashboard UI that we are adding this node + if (group) { + group.register(node, config, evts) + } else { + node.error('No group configured') + } + } + RED.nodes.registerType('ui-audio', AudioNode) +} diff --git a/package.json b/package.json index 356b68a3..be970980 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,8 @@ "ui-markdown": "nodes/widgets/ui_markdown.js", "ui-template": "nodes/widgets/ui_template.js", "ui-event": "nodes/widgets/ui_event.js", - "ui-control": "nodes/widgets/ui_control.js" + "ui-control": "nodes/widgets/ui_control.js", + "ui-audio": "nodes/widgets/ui_audio.js" } }, "overrides": { diff --git a/ui/src/widgets/index.mjs b/ui/src/widgets/index.mjs index 47d49461..ae7da1de 100644 --- a/ui/src/widgets/index.mjs +++ b/ui/src/widgets/index.mjs @@ -1,3 +1,4 @@ +import UIAudio from './ui-audio/UIAudio.vue' import UIButton from './ui-button/UIButton.vue' import UIButtonGroup from './ui-button-group/UIButtonGroup.vue' import UIChart from './ui-chart/UIChart.vue' @@ -21,6 +22,7 @@ import UITextInput from './ui-text-input/UITextInput.vue' // Named exports for use in other components export { + UIAudio, UIButton, UIButtonGroup, UIChart, @@ -48,6 +50,7 @@ export { useDataTracker } from './data-tracker.mjs' // Exported as an object for look up by widget name export default { + 'ui-audio': UIAudio, 'ui-button': UIButton, 'ui-button-group': UIButtonGroup, 'ui-chart': UIChart, diff --git a/ui/src/widgets/ui-audio/UIAudio.vue b/ui/src/widgets/ui-audio/UIAudio.vue new file mode 100644 index 00000000..ede6fe1c --- /dev/null +++ b/ui/src/widgets/ui-audio/UIAudio.vue @@ -0,0 +1,122 @@ + + + + + From 95a9d8a0f43a00eddc8263f57ea8bafa9fe6ede0 Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Fri, 29 Nov 2024 09:21:55 +0100 Subject: [PATCH 2/6] play src from config --- ui/src/widgets/ui-audio/UIAudio.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/widgets/ui-audio/UIAudio.vue b/ui/src/widgets/ui-audio/UIAudio.vue index ede6fe1c..98825c04 100644 --- a/ui/src/widgets/ui-audio/UIAudio.vue +++ b/ui/src/widgets/ui-audio/UIAudio.vue @@ -42,7 +42,7 @@ export default { ...mapState('data', ['messages']), value: function () { // Get the value (i.e. the notification text content) from the last input msg - const value = this.messages[this.id]?.payload + const value = this.messages[this.id]?.payload || this.props.src return value }, loop () { From eb0438a3c4da225df2f9293873cf45b40641e795 Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Wed, 4 Dec 2024 21:18:56 +0100 Subject: [PATCH 3/6] cannot play without src --- nodes/widgets/ui_audio.js | 20 ++++++++++++++++++++ ui/src/widgets/ui-audio/UIAudio.vue | 8 ++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/nodes/widgets/ui_audio.js b/nodes/widgets/ui_audio.js index 3f801bc4..e7eb6a14 100644 --- a/nodes/widgets/ui_audio.js +++ b/nodes/widgets/ui_audio.js @@ -1,3 +1,4 @@ +const datastore = require('../store/data.js') const statestore = require('../store/state.js') module.exports = function (RED) { @@ -11,7 +12,26 @@ module.exports = function (RED) { const evts = { onAction: true, + onInput: function (msg, send) { + // store the latest msg passed to node, only if a source is supplied in the payload + if (typeof msg.payload === 'string') { + datastore.save(group.getBase(), node, msg) + } + // only send msg on if we have passthru enabled + if (config.passthru) { + send(msg) + } + }, beforeSend: function (msg) { + if (msg.playback === 'play') { + const lastMsg = datastore.get(node.id) + // TODO zou eigenlijk de last message met een payload moeten zijn. + const src = lastMsg?.payload || config.src + if (typeof src !== 'string' || src.trim() === '') { + node.warn('Cannot play audio when no source has been specified') + } + } + if (msg.ui_update) { const updates = msg.ui_update diff --git a/ui/src/widgets/ui-audio/UIAudio.vue b/ui/src/widgets/ui-audio/UIAudio.vue index 98825c04..66a82cdc 100644 --- a/ui/src/widgets/ui-audio/UIAudio.vue +++ b/ui/src/widgets/ui-audio/UIAudio.vue @@ -96,13 +96,17 @@ export default { switch (msg.playback) { case 'play': - this.$refs.audio.play() + this.$refs.audio.play().catch((err) => { + console.error('Error playing audio:', err) + }) break case 'stop': this.$refs.audio.src = '' break case 'pause': - this.$refs.audio.pause() + this.$refs.audio.pause().catch((err) => { + console.error('Error pausing audio:', err) + }) break } }, From 2e9548c0161cd270496e1e06a29c006f3d159496 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Fri, 6 Dec 2024 14:09:04 +0000 Subject: [PATCH 4/6] Add Audio docs to nav sidebar --- docs/.vitepress/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 323b392c..2c2f0753 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -130,6 +130,7 @@ export default ({ mode }) => { text: 'Widgets', collapsed: false, items: [ + { text: 'ui-audio', link: '/nodes/widgets/ui-audio' }, { text: 'ui-button', link: '/nodes/widgets/ui-button' }, { text: 'ui-button-group', link: '/nodes/widgets/ui-button-group' }, { text: 'ui-control', link: '/nodes/widgets/ui-control' }, From b9a95a20f20efad9e5dbb8767075393e6a8ca8a1 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Fri, 6 Dec 2024 14:09:24 +0000 Subject: [PATCH 5/6] Re-order the nodes in the NR Editor --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index be970980..d7abdb4e 100644 --- a/package.json +++ b/package.json @@ -143,11 +143,11 @@ "ui-chart": "nodes/widgets/ui_chart.js", "ui-gauge": "nodes/widgets/ui_gauge.js", "ui-notification": "nodes/widgets/ui_notification.js", + "ui-audio": "nodes/widgets/ui_audio.js", "ui-markdown": "nodes/widgets/ui_markdown.js", "ui-template": "nodes/widgets/ui_template.js", "ui-event": "nodes/widgets/ui_event.js", - "ui-control": "nodes/widgets/ui_control.js", - "ui-audio": "nodes/widgets/ui_audio.js" + "ui-control": "nodes/widgets/ui_control.js" } }, "overrides": { From 0a51568f0b797570862ca2015179fd077cf44ff8 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Fri, 6 Dec 2024 14:09:41 +0000 Subject: [PATCH 6/6] Add in CSS for width/height setting, rather than inline HTML attrs --- ui/src/widgets/ui-audio/UIAudio.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/widgets/ui-audio/UIAudio.vue b/ui/src/widgets/ui-audio/UIAudio.vue index 66a82cdc..a9ac8c81 100644 --- a/ui/src/widgets/ui-audio/UIAudio.vue +++ b/ui/src/widgets/ui-audio/UIAudio.vue @@ -2,8 +2,6 @@