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

Adds an UI command Item #2055

Merged
merged 5 commits into from
Oct 8, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
55 changes: 3 additions & 52 deletions bundles/org.openhab.ui/web/src/components/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -282,8 +283,6 @@ export default {
return {
init: false,
ready: false,
eventSource: null,
audioContext: null,

// Framework7 Parameters
f7params: {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -751,9 +704,7 @@ export default {
window.addEventListener('keydown', this.keyDown)
}

if (localStorage.getItem('openhab.ui:webaudio.enable') === 'enabled') {
this.startEventSource()
}
this.startEventSource()
digitaldan marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
Expand Down
155 changes: 155 additions & 0 deletions bundles/org.openhab.ui/web/src/components/sse-events-mixin.js
Original file line number Diff line number Diff line change
@@ -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
digitaldan marked this conversation as resolved.
Show resolved Hide resolved
}
},
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(/(?<!\\):/)
const combined = segments.join(':')
switch (command) {
case 'navigate':
this.$f7.views.main.router.navigate(combined)
break
case 'popup':
case 'popover':
case 'sheet':
if (combined.indexOf('page:') !== 0 && combined.indexOf('widget:') !== 0 && combined.indexOf('oh-') !== 0) {
console.error('Action target is not of the format page:uid or widget:uid or oh-')
return
}
console.debug(`Opening ${combined} in ${command} modal`)
let modalRoute = {
url: combined + '/' + command,
route: {
}
}
if (command === 'popup') modalRoute.route.popup = { component: OhPopup }
if (command === 'popover') modalRoute.route.popup = { component: OhPopover }
if (command === 'sheet') modalRoute.route.popup = { component: OhSheet }
let modalProps = {
props: {
uid: combined,
modalParams: {}
}
}
this.closePopups()
this.$f7.views.main.router.navigate(modalRoute, modalProps)
break
case 'close':
this.closePopups()
break
case 'back':
this.$f7.views.main.router.back()
break
case 'reload':
window.location.reload()
break
case 'notification':
const payload = {
text: segments[0],
closeButton: true,
swipeToClose: true,
closeTimeout: 5000
}
if (segments.length > 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 () {
florian-h05 marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
}
}
16 changes: 16 additions & 0 deletions bundles/org.openhab.ui/web/src/components/theme-switcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,23 @@
<span v-t="'about.miscellaneous.webaudio.enable'" />
<f7-toggle :checked="webAudio === 'enabled'" @toggle:change="setWebAudio" />
</f7-list-item>
<f7-list-item>
<span v-t="'about.miscellaneous.commandItem.title'" />
<item-picker title="Item" :multiple="false" :value="commandItem" @input="setCommandItem" />
</f7-list-item>
florian-h05 marked this conversation as resolved.
Show resolved Hide resolved
</f7-list>
</f7-col>
</f7-row>
</f7-block>
</template>
<script>
import { loadLocaleMessages } from '@/js/i18n'
import ItemPicker from '@/components/config/controls/item-picker.vue'

export default {
components: {
ItemPicker
},
i18n: {
messages: loadLocaleMessages(require.context('@/assets/i18n/theme-switcher'))
},
Expand Down Expand Up @@ -127,6 +136,10 @@ export default {
setWebAudio (value) {
localStorage.setItem('openhab.ui:webaudio.enable', (value) ? 'enabled' : 'default')
location.reload()
},
setCommandItem (value) {
localStorage.setItem('openhab.ui:commandItem', value)
setTimeout(() => { location.reload() }, 50) // Delay reload, otherwise it doesn't work
}
},
computed: {
Expand Down Expand Up @@ -156,6 +169,9 @@ export default {
},
webAudio () {
return localStorage.getItem('openhab.ui:webaudio.enable') || 'default'
},
commandItem () {
return localStorage.getItem('openhab.ui:commandItem') || ''
}
}
}
Expand Down