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' }, 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..e7eb6a14 --- /dev/null +++ b/nodes/widgets/ui_audio.js @@ -0,0 +1,81 @@ +const datastore = require('../store/data.js') +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, + 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 + + 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 886f2545..30cde2f3 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "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", 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..a9ac8c81 --- /dev/null +++ b/ui/src/widgets/ui-audio/UIAudio.vue @@ -0,0 +1,128 @@ + + + + +