mirror of
https://github.com/owntone/owntone-server.git
synced 2025-07-21 14:31:19 -04:00
[web] Refactor management of remotes and outputs
This commit is contained in:
parent
95de42e6be
commit
786d8cbc09
File diff suppressed because one or more lines are too long
@ -5,8 +5,8 @@
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
<modal-dialog-remote-pairing
|
||||
:show="pairingActive"
|
||||
@close="pairingActive = false"
|
||||
:show="remotesStore.active"
|
||||
@close="remotesStore.active = false"
|
||||
/>
|
||||
<modal-dialog-update
|
||||
:show="uiStore.showUpdateDialog"
|
||||
@ -67,7 +67,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pairingActive: false,
|
||||
timerId: 0
|
||||
}
|
||||
},
|
||||
@ -254,7 +253,6 @@ export default {
|
||||
updatePairing() {
|
||||
webapi.pairing().then((data) => {
|
||||
this.remotesStore.$state = data
|
||||
this.pairingActive = data.active
|
||||
})
|
||||
},
|
||||
updatePlayerStatus() {
|
||||
|
41
web-src/src/components/ControlPinField.vue
Normal file
41
web-src/src/components/ControlPinField.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input
|
||||
ref="input"
|
||||
v-model="value"
|
||||
class="input"
|
||||
inputmode="numeric"
|
||||
pattern="[\d]{4}"
|
||||
:placeholder="placeholder"
|
||||
@input="validate"
|
||||
/>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ControlPinField',
|
||||
props: {
|
||||
placeholder: { required: true, type: String }
|
||||
},
|
||||
emits: ['input'],
|
||||
data() {
|
||||
return { value: '' }
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.$refs.input.focus()
|
||||
}, 10)
|
||||
},
|
||||
methods: {
|
||||
validate(event) {
|
||||
const { validity } = event.target
|
||||
const invalid = validity.patternMismatch || validity.valueMissing
|
||||
this.$emit('input', this.value, invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -29,10 +29,7 @@ export default {
|
||||
},
|
||||
emits: ['input'],
|
||||
data() {
|
||||
return {
|
||||
disabled: true,
|
||||
value: ''
|
||||
}
|
||||
return { value: '' }
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
@ -42,8 +39,8 @@ export default {
|
||||
methods: {
|
||||
validate(event) {
|
||||
const { validity } = event.target
|
||||
this.disabled = validity.patternMismatch || validity.valueMissing
|
||||
this.$emit('input', this.value, this.disabled)
|
||||
const invalid = validity.patternMismatch || validity.valueMissing
|
||||
this.$emit('input', this.value, invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,32 +7,25 @@
|
||||
>
|
||||
<template #content>
|
||||
<form @submit.prevent="pair">
|
||||
<label class="label" v-text="pairing.remote" />
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
ref="pin_field"
|
||||
v-model="pairing_req.pin"
|
||||
class="input"
|
||||
inputmode="numeric"
|
||||
pattern="[\d]{4}"
|
||||
:placeholder="$t('dialog.remote-pairing.pairing-code')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label" v-text="remoteStore.remote" />
|
||||
<control-pin-field
|
||||
:placeholder="$t('dialog.remote-pairing.pairing-code')"
|
||||
@input="onPinChange"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
</modal-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlPinField from '@/components/ControlPinField.vue'
|
||||
import ModalDialog from '@/components/ModalDialog.vue'
|
||||
import { useRemotesStore } from '@/stores/remotes'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogRemotePairing',
|
||||
components: { ModalDialog },
|
||||
components: { ControlPinField, ModalDialog },
|
||||
props: { show: Boolean },
|
||||
emits: ['close'],
|
||||
setup() {
|
||||
@ -40,38 +33,34 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pairing_req: { pin: '' }
|
||||
disabled: true,
|
||||
pin: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
actions() {
|
||||
return [
|
||||
{ handler: this.cancel, icon: 'cancel', key: 'actions.cancel' },
|
||||
{ handler: this.pair, icon: 'cellphone', key: 'actions.pair' }
|
||||
{
|
||||
disabled: this.disabled,
|
||||
handler: this.pair,
|
||||
icon: 'vector-link',
|
||||
key: 'actions.pair'
|
||||
}
|
||||
]
|
||||
},
|
||||
pairing() {
|
||||
return this.remoteStore.pairing
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show() {
|
||||
if (this.show) {
|
||||
this.loading = false
|
||||
// Delay setting the focus on the input field until it is part of the DOM and visible
|
||||
setTimeout(() => {
|
||||
this.$refs.pin_field.focus()
|
||||
}, 10)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit('close')
|
||||
},
|
||||
onPinChange(pin, disabled) {
|
||||
this.pin = pin
|
||||
this.disabled = disabled
|
||||
},
|
||||
pair() {
|
||||
webapi.pairing_kickoff(this.pairing_req).then(() => {
|
||||
this.pairing_req.pin = ''
|
||||
webapi.pairing_kickoff({ pin: this.pin }).then(() => {
|
||||
this.pin = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ export default {
|
||||
to: { name: 'settings-webinterface' }
|
||||
},
|
||||
{
|
||||
key: 'page.settings.tabs.remotes-and-outputs',
|
||||
to: { name: 'settings-remotes-outputs' }
|
||||
key: 'page.settings.tabs.devices',
|
||||
to: { name: 'settings-devices' }
|
||||
},
|
||||
{
|
||||
key: 'page.settings.tabs.artwork',
|
||||
|
@ -17,7 +17,6 @@
|
||||
"remove": "Entfernen",
|
||||
"rescan": "Neu einlesen",
|
||||
"save": "Speichern",
|
||||
"send": "Senden",
|
||||
"show-more": "Zeige mehr",
|
||||
"shuffle": "Zufallswiedergabe",
|
||||
"update": "Neu einlesen",
|
||||
@ -314,9 +313,9 @@
|
||||
},
|
||||
"tabs": {
|
||||
"artwork": "Artwork",
|
||||
"devices": "Fernbedienungen und Ausgänge",
|
||||
"general": "Allgemein",
|
||||
"online-services": "Online-Services",
|
||||
"remotes-and-outputs": "Fernbedienungen und Ausgänge"
|
||||
"online-services": "Online-Services"
|
||||
}
|
||||
},
|
||||
"spotify": {
|
||||
|
@ -17,7 +17,6 @@
|
||||
"remove": "Remove",
|
||||
"rescan": "Rescan",
|
||||
"save": "Save",
|
||||
"send": "Send",
|
||||
"show-more": "Show more",
|
||||
"shuffle": "Shuffle",
|
||||
"update": "Update",
|
||||
@ -314,9 +313,9 @@
|
||||
},
|
||||
"tabs": {
|
||||
"artwork": "Artwork",
|
||||
"devices": "Remotes and Outputs",
|
||||
"general": "General",
|
||||
"online-services": "Online Services",
|
||||
"remotes-and-outputs": "Remotes and Outputs"
|
||||
"online-services": "Online Services"
|
||||
}
|
||||
},
|
||||
"spotify": {
|
||||
|
@ -17,7 +17,6 @@
|
||||
"remove": "Supprimer",
|
||||
"rescan": "Analyser",
|
||||
"save": "Enregistrer",
|
||||
"send": "Envoyer",
|
||||
"show-more": "Afficher plus",
|
||||
"shuffle": "Lecture aléatoire",
|
||||
"update": "Actualiser",
|
||||
@ -314,9 +313,9 @@
|
||||
},
|
||||
"tabs": {
|
||||
"artwork": "Illustrations",
|
||||
"devices": "Télécommandes et sorties",
|
||||
"general": "Général",
|
||||
"online-services": "Services en ligne",
|
||||
"remotes-and-outputs": "Télécommandes et sorties"
|
||||
"online-services": "Services en ligne"
|
||||
}
|
||||
},
|
||||
"spotify": {
|
||||
|
@ -17,7 +17,6 @@
|
||||
"remove": "移除",
|
||||
"rescan": "重新扫描",
|
||||
"save": "保存",
|
||||
"send": "发送",
|
||||
"show-more": "显示更多",
|
||||
"shuffle": "随机播放",
|
||||
"update": "更新",
|
||||
@ -314,9 +313,9 @@
|
||||
},
|
||||
"tabs": {
|
||||
"artwork": "封面",
|
||||
"devices": "遥控和输出",
|
||||
"general": "概览",
|
||||
"online-services": "在线服务",
|
||||
"remotes-and-outputs": "遥控和输出"
|
||||
"online-services": "在线服务"
|
||||
}
|
||||
},
|
||||
"spotify": {
|
||||
|
@ -17,7 +17,6 @@
|
||||
"remove": "移除",
|
||||
"rescan": "重新掃描",
|
||||
"save": "儲存",
|
||||
"send": "發送",
|
||||
"show-more": "顯示更多",
|
||||
"shuffle": "隨機播放",
|
||||
"update": "更新",
|
||||
@ -314,9 +313,9 @@
|
||||
},
|
||||
"tabs": {
|
||||
"artwork": "封面",
|
||||
"devices": "遙控和輸出",
|
||||
"general": "概覽",
|
||||
"online-services": "在線服務",
|
||||
"remotes-and-outputs": "遙控和輸出"
|
||||
"online-services": "在線服務"
|
||||
}
|
||||
},
|
||||
"spotify": {
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
mdiCancel,
|
||||
mdiCast,
|
||||
mdiCastVariant,
|
||||
mdiCellphone,
|
||||
mdiCheck,
|
||||
mdiChevronDown,
|
||||
mdiChevronLeft,
|
||||
@ -59,6 +58,7 @@ import {
|
||||
mdiSpeaker,
|
||||
mdiSpotify,
|
||||
mdiStop,
|
||||
mdiVectorLink,
|
||||
mdiVolumeHigh,
|
||||
mdiVolumeOff,
|
||||
mdiWeb
|
||||
@ -74,7 +74,6 @@ export const icons = {
|
||||
mdiCancel,
|
||||
mdiCast,
|
||||
mdiCastVariant,
|
||||
mdiCellphone,
|
||||
mdiCheck,
|
||||
mdiChevronDown,
|
||||
mdiChevronLeft,
|
||||
@ -125,6 +124,7 @@ export const icons = {
|
||||
mdiSpeaker,
|
||||
mdiSpotify,
|
||||
mdiStop,
|
||||
mdiVectorLink,
|
||||
mdiVolumeHigh,
|
||||
mdiVolumeOff,
|
||||
mdiWeb
|
||||
|
@ -7,32 +7,25 @@
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div v-if="pairing.active">
|
||||
<form @submit.prevent="kickoffPairing">
|
||||
<label class="label has-text-weight-normal content">
|
||||
<span v-text="$t('page.settings.devices.pairing-request')" />
|
||||
<b v-text="pairing.remote" />
|
||||
</label>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="pairingRequest.pin"
|
||||
class="input"
|
||||
inputmode="numeric"
|
||||
pattern="[\d]{4}"
|
||||
:placeholder="$t('page.settings.devices.pairing-code')"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button"
|
||||
type="submit"
|
||||
v-text="$t('actions.send')"
|
||||
/>
|
||||
</div>
|
||||
<form v-if="remotesStore.active" @submit.prevent="pairRemote">
|
||||
<label class="label has-text-weight-normal content">
|
||||
<span v-text="$t('page.settings.devices.pairing-request')" />
|
||||
<b v-text="remotesStore.remote" />
|
||||
</label>
|
||||
<control-pin-field
|
||||
:placeholder="$t('dialog.remote-pairing.pairing-code')"
|
||||
@input="onRemotePinChange"
|
||||
>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button"
|
||||
type="submit"
|
||||
:disabled="remotePairingDisabled"
|
||||
v-text="$t('actions.verify')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</control-pin-field>
|
||||
</form>
|
||||
<div v-else v-text="$t('page.settings.devices.no-active-pairing')" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
@ -47,7 +40,7 @@
|
||||
class="content"
|
||||
v-text="$t('page.settings.devices.speaker-pairing-info')"
|
||||
/>
|
||||
<div v-for="output in outputs" :key="output.id">
|
||||
<div v-for="output in outputs" :key="output.id" class="field is-grouped">
|
||||
<control-switch
|
||||
v-model="output.selected"
|
||||
@update:model-value="toggleOutput(output.id)"
|
||||
@ -58,19 +51,12 @@
|
||||
</control-switch>
|
||||
<form
|
||||
v-if="output.needs_auth_key"
|
||||
class="mb-5"
|
||||
@submit.prevent="kickoffVerification(output.id)"
|
||||
@submit.prevent="pairOutput(output.id)"
|
||||
>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="verificationRequest.pin"
|
||||
class="input"
|
||||
inputmode="numeric"
|
||||
pattern="[\d]{4}"
|
||||
:placeholder="$t('page.settings.devices.verification-code')"
|
||||
/>
|
||||
</div>
|
||||
<control-pin-field
|
||||
:placeholder="$t('page.settings.devices.verification-code')"
|
||||
@input="onOutputPinChange"
|
||||
>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button"
|
||||
@ -78,7 +64,7 @@
|
||||
v-text="$t('actions.verify')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</control-pin-field>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
@ -87,6 +73,7 @@
|
||||
|
||||
<script>
|
||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||
import ControlPinField from '@/components/ControlPinField.vue'
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import HeadingTitle from '@/components/HeadingTitle.vue'
|
||||
import TabsSettings from '@/components/TabsSettings.vue'
|
||||
@ -95,31 +82,42 @@ import { useRemotesStore } from '@/stores/remotes'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PageSettingsRemotesOutputs',
|
||||
components: { ContentWithHeading, ControlSwitch, HeadingTitle, TabsSettings },
|
||||
name: 'PageSettingsDevices',
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ControlPinField,
|
||||
ControlSwitch,
|
||||
HeadingTitle,
|
||||
TabsSettings
|
||||
},
|
||||
setup() {
|
||||
return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pairingRequest: { pin: '' },
|
||||
verificationRequest: { pin: '' }
|
||||
outputPin: '',
|
||||
remotePairingDisabled: true,
|
||||
remotePin: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
outputs() {
|
||||
return this.outputsStore.outputs
|
||||
},
|
||||
pairing() {
|
||||
return this.remotesStore.pairing
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
kickoffPairing() {
|
||||
webapi.pairing_kickoff(this.pairingRequest)
|
||||
pairRemote() {
|
||||
webapi.pairing_kickoff({ pin: this.remotePin })
|
||||
},
|
||||
kickoffVerification(identifier) {
|
||||
webapi.output_update(identifier, this.verificationRequest)
|
||||
pairOutput(identifier) {
|
||||
webapi.output_update(identifier, { pin: this.outputPin })
|
||||
},
|
||||
onRemotePinChange(pin, disabled) {
|
||||
this.remotePin = pin
|
||||
this.remotePairingDisabled = disabled
|
||||
},
|
||||
onOutputPinChange(pin) {
|
||||
this.outputPin = pin
|
||||
},
|
||||
toggleOutput(identifier) {
|
||||
webapi.output_toggle(identifier)
|
@ -36,8 +36,8 @@ import PageRadioStreams from '@/pages/PageRadioStreams.vue'
|
||||
import PageSearchLibrary from '@/pages/PageSearchLibrary.vue'
|
||||
import PageSearchSpotify from '@/pages/PageSearchSpotify.vue'
|
||||
import PageSettingsArtwork from '@/pages/PageSettingsArtwork.vue'
|
||||
import PageSettingsDevices from '@/pages/PageSettingsDevices.vue'
|
||||
import PageSettingsOnlineServices from '@/pages/PageSettingsOnlineServices.vue'
|
||||
import PageSettingsRemotesOutputs from '@/pages/PageSettingsRemotesOutputs.vue'
|
||||
import PageSettingsWebinterface from '@/pages/PageSettingsWebinterface.vue'
|
||||
|
||||
const TOP_WITH_TABS = 100
|
||||
@ -233,9 +233,9 @@ export const router = createRouter({
|
||||
path: '/settings/online-services'
|
||||
},
|
||||
{
|
||||
component: PageSettingsRemotesOutputs,
|
||||
name: 'settings-remotes-outputs',
|
||||
path: '/settings/remotes-outputs'
|
||||
component: PageSettingsDevices,
|
||||
name: 'settings-devices',
|
||||
path: '/settings/devices'
|
||||
}
|
||||
],
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
||||
|
||||
export const useRemotesStore = defineStore('RemotesStore', {
|
||||
state: () => ({
|
||||
pairing: {}
|
||||
active: false,
|
||||
remote: ''
|
||||
})
|
||||
})
|
||||
|
@ -241,8 +241,8 @@ export default {
|
||||
return axios.get('./api/pairing')
|
||||
},
|
||||
|
||||
pairing_kickoff(pairingReq) {
|
||||
return axios.post('./api/pairing', pairingReq)
|
||||
pairing_kickoff(request) {
|
||||
return axios.post('./api/pairing', request)
|
||||
},
|
||||
|
||||
player_consume(state) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user