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 @@