diff --git a/bundles/org.openhab.ui/web/src/assets/i18n/theme-switcher/en.json b/bundles/org.openhab.ui/web/src/assets/i18n/theme-switcher/en.json
index a13161dea4..ccf6e6eb4d 100644
--- a/bundles/org.openhab.ui/web/src/assets/i18n/theme-switcher/en.json
+++ b/bundles/org.openhab.ui/web/src/assets/i18n/theme-switcher/en.json
@@ -12,5 +12,7 @@
"about.miscellaneous.home.hideChatInput": "Hide chat input box on home page",
"about.miscellaneous.home.disableCardExpansionAnimation": "Disable card expansion animations",
"about.miscellaneous.theme.disablePageTransition": "Disable page transition animations",
- "about.miscellaneous.webaudio.enable": "Enable Web Audio sink support"
+ "about.miscellaneous.webaudio.enable": "Enable Web Audio sink support",
+ "about.miscellaneous.commandItem.title": "Listen for UI commands",
+ "about.miscellaneous.commandItem.selectItem": "Item"
}
diff --git a/bundles/org.openhab.ui/web/src/components/app.vue b/bundles/org.openhab.ui/web/src/components/app.vue
index 3571730fd9..4a5eaab83f 100644
--- a/bundles/org.openhab.ui/web/src/components/app.vue
+++ b/bundles/org.openhab.ui/web/src/components/app.vue
@@ -256,12 +256,13 @@ import { loadLocaleMessages } from '@/js/i18n'
import auth from './auth-mixin'
import i18n from './i18n-mixin'
import connectionHealth from './connection-health-mixin'
+import sseEvents from './sse-events-mixin'
import dayjs from 'dayjs'
import dayjsLocales from 'dayjs/locale.json'
export default {
- mixins: [auth, i18n, connectionHealth],
+ mixins: [auth, i18n, connectionHealth, sseEvents],
components: {
EmptyStatePlaceholder,
PanelRight,
@@ -282,8 +283,6 @@ export default {
return {
init: false,
ready: false,
- eventSource: null,
- audioContext: null,
// Framework7 Parameters
f7params: {
@@ -604,52 +603,6 @@ export default {
ev.stopPropagation()
ev.preventDefault()
}
- },
- startEventSource () {
- this.eventSource = this.$oh.sse.connect('/rest/events?topics=openhab/webaudio/playurl', null, (event) => {
- const topicParts = event.topic.split('/')
- switch (topicParts[2]) {
- case 'playurl':
- this.playAudioUrl(JSON.parse(event.payload))
- break
- }
- })
- },
- stopEventSource () {
- this.$oh.sse.close(this.eventSource)
- this.eventSource = null
- },
- playAudioUrl (audioUrl) {
- try {
- if (!this.audioContext) {
- window.AudioContext = window.AudioContext || window.webkitAudioContext
- if (typeof (window.AudioContext) !== 'undefined') {
- this.audioContext = new AudioContext()
- unlockAudioContext(this.audioContext)
- }
- }
- console.log('Playing audio URL: ' + audioUrl)
- this.$oh.api.getPlain(audioUrl, '', '*/*', 'arraybuffer').then((data) => {
- this.audioContext.decodeAudioData(data, (buffer) => {
- let source = this.audioContext.createBufferSource()
- source.buffer = buffer
- source.connect(this.audioContext.destination)
- source.start(0)
- })
- })
- } catch (e) {
- console.warn('Error while playing audio URL: ' + e.toString())
- }
- // Safari requires a touch event after the stream has started, hence this workaround
- // Credit: https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos
- function unlockAudioContext (audioContext) {
- if (audioContext.state !== 'suspended') return
- const b = document.body
- const events = ['touchstart', 'touchend', 'mousedown', 'keydown']
- events.forEach(e => b.addEventListener(e, unlock, false))
- function unlock () { audioContext.resume().then(clean) }
- function clean () { events.forEach(e => b.removeEventListener(e, unlock)) }
- }
}
},
created () {
@@ -751,9 +704,7 @@ export default {
window.addEventListener('keydown', this.keyDown)
}
- if (localStorage.getItem('openhab.ui:webaudio.enable') === 'enabled') {
- this.startEventSource()
- }
+ this.startEventSource()
})
}
}
diff --git a/bundles/org.openhab.ui/web/src/components/sse-events-mixin.js b/bundles/org.openhab.ui/web/src/components/sse-events-mixin.js
new file mode 100644
index 0000000000..bbb6de7225
--- /dev/null
+++ b/bundles/org.openhab.ui/web/src/components/sse-events-mixin.js
@@ -0,0 +1,155 @@
+import OhPopup from './widgets/modals/oh-popup.vue'
+import OhSheet from './widgets/modals/oh-sheet.vue'
+import OhPopover from './widgets/modals/oh-popover.vue'
+
+export default {
+ data () {
+ return {
+ eventSource: null,
+ audioContext: null
+ }
+ },
+ methods: {
+ startEventSource () {
+ const topicAudio = 'openhab/webaudio/playurl'
+ const commandItem = localStorage.getItem('openhab.ui:commandItem')
+ const topicCommand = `openhab/items/${commandItem || ''}/command`
+ let topics = null
+ if (localStorage.getItem('openhab.ui:webaudio.enable') === 'enabled') {
+ topics = topicAudio
+ }
+ if (commandItem) {
+ topics = topics ? `${topics},${topicCommand}` : topicCommand
+ }
+ if (!topics) return
+ this.eventSource = this.$oh.sse.connect(`/rest/events?topics=${topics}`, null, (event) => {
+ console.debug('Received SSE event: ' + JSON.stringify(event))
+ switch (event.topic) {
+ case topicAudio:
+ const topicParts = event.topic.split('/')
+ switch (topicParts[2]) {
+ case 'playurl':
+ this.playAudioUrl(JSON.parse(event.payload))
+ break
+ }
+ break
+ case topicCommand:
+ const payload = JSON.parse(event.payload)
+ const [command, ...segments] = payload.value.trim().split(/(? 1) {
+ payload.title = segments[1]
+ }
+ if (segments.length > 2) {
+ payload.subtitle = segments[2]
+ }
+ if (segments.length > 3) {
+ payload.titleRightText = segments[3]
+ }
+ if (segments.length > 4) {
+ payload.closeTimeout = parseInt(segments[4])
+ }
+ this.$f7.notification.create(payload).open()
+ break
+ }
+ break
+ }
+ })
+ },
+ stopEventSource () {
+ this.$oh.sse.close(this.eventSource)
+ this.eventSource = null
+ },
+ playAudioUrl (audioUrl) {
+ try {
+ if (!this.audioContext) {
+ window.AudioContext = window.AudioContext || window.webkitAudioContext
+ if (typeof (window.AudioContext) !== 'undefined') {
+ this.audioContext = new AudioContext()
+ unlockAudioContext(this.audioContext)
+ }
+ }
+ console.log('Playing audio URL: ' + audioUrl)
+ this.$oh.api.getPlain(audioUrl, '', '*/*', 'arraybuffer').then((data) => {
+ this.audioContext.decodeAudioData(data, (buffer) => {
+ let source = this.audioContext.createBufferSource()
+ source.buffer = buffer
+ source.connect(this.audioContext.destination)
+ source.start(0)
+ })
+ })
+ } catch (e) {
+ console.warn('Error while playing audio URL: ' + e.toString())
+ }
+ // Safari requires a touch event after the stream has started, hence this workaround
+ // Credit: https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos
+ function unlockAudioContext (audioContext) {
+ if (audioContext.state !== 'suspended') return
+ const b = document.body
+ const events = ['touchstart', 'touchend', 'mousedown', 'keydown']
+ events.forEach(e => b.addEventListener(e, unlock, false))
+ function unlock () { audioContext.resume().then(clean) }
+ function clean () { events.forEach(e => b.removeEventListener(e, unlock)) }
+ }
+ },
+ closePopups () {
+ const popupEl = this.$el.querySelector('.popup')
+ if (popupEl) {
+ this.$f7.popup.close(popupEl)
+ }
+ const popoverEl = this.$el.querySelector('.popover')
+ if (popoverEl) {
+ this.$f7.popover.close(popoverEl)
+ }
+ const sheetEl = this.$el.querySelector('.sheet-modal')
+ if (sheetEl) {
+ this.$f7.sheet.close(sheetEl)
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.ui/web/src/components/theme-switcher.vue b/bundles/org.openhab.ui/web/src/components/theme-switcher.vue
index 1233edd493..e02510f2c4 100644
--- a/bundles/org.openhab.ui/web/src/components/theme-switcher.vue
+++ b/bundles/org.openhab.ui/web/src/components/theme-switcher.vue
@@ -74,6 +74,10 @@
+
+
+
+
@@ -81,7 +85,12 @@