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: Audio #1506

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion docs/nodes/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.
- [@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.
76 changes: 76 additions & 0 deletions docs/nodes/widgets/ui-audio.md
Original file line number Diff line number Diff line change
@@ -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'"]
---

<script setup>
import { ref } from 'vue'

import ExampleButtonHold from '../../examples/ui-button-hold.json'

import TryDemo from "./../../components/TryDemo.vue"
import FlowViewer from '../../components/FlowViewer.vue'

const examples = ref({
'hold': ExampleButtonHold
})
</script>


<TryDemo href="button-example">

# Audio `ui-audio`

</TryDemo>

Adds a clickable button to your dashboard.

## Properties

<PropsTable/>

## Dynamic Properties

<DynamicPropsTable/>

## Controls

<ControlsTable/>

## 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.*
31 changes: 31 additions & 0 deletions nodes/widgets/locales/en-US/ui_audio.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script type="text/html" data-help-name="ui-audio">
<p>
Plays an audio file in the dashboard.
</p>
<p>
Each received <code>msg.payload</code> will contain a new source, i.e. a new audio file url.
</p>
<h3>Properties</h3>
<dl class="message-properties">
<dt>Source <span class="property-type">string</span></dt>
<dd>The source is the url where the audio file can be fetched.</dd>
<dt>Autoplay <span class="property-type">list</span></dt>
<dd>Specify whether the audio file will start playing automatically.</dd>
<dt>Loop <span class="property-type">list</span></dt>
<dd>Specify whether the audio should be looping, i.e. start playing automatically again when ended.</dd>
<dt>Muted <span class="property-type">list</span></dt>
<dd>Specify whether the audio should be muted.</dd>
</dl>
<h3>Dynamic Properties (Inputs)</h3>
<p>Any of the following can be appended to a <code>msg.ui_update</code> in order to override or set properties on this node at runtime.</p>
<dl class="message-properties">
<dt class="optional">src<span class="property-type">string</span></dt>
<dd>Override the configured audio source.</dd>
<dt class="optional">autoplay<span class="property-type">'on' | 'off'</span></dt>
<dd>Override the configured autoplay setting .</dd>
<dt class="optional">loop<span class="property-type">'on' | 'off'</span></dt>
<dd>Override the configured looping behaviour.</dd>
<dt class="optional">muted<span class="property-type">'on' | 'off'</span></dt>
<dd>Override the configured muted setting.</dd>
</dl>
</script>
17 changes: 17 additions & 0 deletions nodes/widgets/locales/en-US/ui_audio.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
113 changes: 113 additions & 0 deletions nodes/widgets/ui_audio.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script type="text/javascript">
(function () {
RED.nodes.registerType('ui-audio', {
category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.medium'),
defaults: {
group: { type: 'ui-group', required: true },
name: { value: '' },
order: { value: 0 },
width: {
value: 0,
validate: function (v) {
const width = v || 0
const currentGroup = $('#node-input-group').val() || this.group
const groupNode = RED.nodes.node(currentGroup)
const valid = !groupNode || +width >= 0
$('#node-input-size').toggleClass('input-error', !valid)
return valid
}
},
height: { value: 0 },
src: { value: ''},
autoplay: { value: 'off' },
loop: { value: 'off' },
muted: { value: 'off' }
},
inputs: 1,
outputs: 1,
align: 'right',
icon: 'font-awesome/fa-volume-up',
paletteLabel: 'audio',
label: function () { return this.name },
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
oneditprepare: function () {
// if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
// as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
if (RED.nodes.subflow(this.z)) {
// change inputs from hidden to text & display them
$('#node-input-width').attr('type', 'text')
$('#node-input-height').attr('type', 'text')
$('div.form-row.nr-db-ui-element-sizer-row').hide()
$('div.form-row.nr-db-ui-manual-size-row').show()
} else {
// not in a subflow, use the elementSizer
$('div.form-row.nr-db-ui-element-sizer-row').show()
$('div.form-row.nr-db-ui-manual-size-row').hide()
$('#node-input-size').elementSizer({
width: '#node-input-width',
height: '#node-input-height',
group: '#node-input-group'
})
}

// use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
$('.ui-node-popover-title').tooltip({
show: {
effect: 'slideDown',
delay: 150
}
})
}
})
})()
</script>

<script type="text/html" data-template-name="ui-audio">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
<div class="form-row">
<label for="node-input-group"><i class="fa fa-table"></i> <span data-i18n="ui-audio.label.group"></label>
<input type="text" id="node-input-group">
</div>
<div class="form-row nr-db-ui-element-sizer-row">
<label><i class="fa fa-object-group"></i> <span data-i18n="ui-audio.label.size"></label>
<button class="editor-button" id="node-input-size"></button>
</div>
<div class="form-row nr-db-ui-manual-size-row">
<label><i class="fa fa-arrows-h"></i> <span data-i18n="ui-audio.label.width">Width</label>
<input type="hidden" id="node-input-width">
</div>
<div class="form-row nr-db-ui-manual-size-row">
<label><i class="fa fa-arrows-v"></i> <span data-i18n="ui-audio.label.height">Height</label>
<input type="hidden" id="node-input-height">
</div>
<div class="form-row">
<label for="node-input-src"><i class="fa fa-globe"></i> <span data-i18n="ui-audio.label.source"></label>
<input type="text" id="node-input-src">
</div>
<div class="form-row">
<label for="node-input-autoplay"><i class="fa fa-play-circle"></i> <span data-i18n="ui-audio.label.autoplay"></label>
<select id="node-input-autoplay" style="width:70%;">
<option value="on" data-i18n="ui-audio.option.on"></option>
<option value="off" data-i18n="ui-audio.option.off"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-loop"><i class="fa fa-retweet"></i> <span data-i18n="ui-audio.label.loop"></label>
<select id="node-input-loop" style="width:70%;">
<option value="on" data-i18n="ui-audio.option.on"></option>
<option value="off" data-i18n="ui-audio.option.off"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-muted"><i class="fa fa-volume-up"></i> <span data-i18n="ui-audio.label.muted"></label>
<select id="node-input-muted" style="width:70%;">
<option value="on" data-i18n="ui-audio.option.on"></option>
<option value="off" data-i18n="ui-audio.option.off"></option>
</select>
</div>
<div class="form-tips"><b>Note</b>: Autoplay will only work after a user gesture (e.g. click on the dashboard).</span></div>
</script>
61 changes: 61 additions & 0 deletions nodes/widgets/ui_audio.js
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 3 additions & 0 deletions ui/src/widgets/index.mjs
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -21,6 +22,7 @@ import UITextInput from './ui-text-input/UITextInput.vue'

// Named exports for use in other components
export {
UIAudio,
UIButton,
UIButtonGroup,
UIChart,
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading