[web] Merge branch 'linting'

This commit is contained in:
Alain Nussbaumer 2024-05-19 22:42:45 +02:00
commit 408057a45d
44 changed files with 291 additions and 399 deletions

View File

@ -25,21 +25,16 @@ export default [
'max-statements': 'off', 'max-statements': 'off',
'no-bitwise': 'off', 'no-bitwise': 'off',
'no-magic-numbers': 'off', 'no-magic-numbers': 'off',
'no-negated-condition': 'off',
'no-nested-ternary': 'off', 'no-nested-ternary': 'off',
'no-plusplus': 'off', 'no-plusplus': 'off',
'no-shadow': 'off',
'no-ternary': 'off', 'no-ternary': 'off',
'no-undef': 'off', 'no-undef': 'off',
'no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }], 'no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }],
'no-useless-assignment': 'off',
'one-var': 'off', 'one-var': 'off',
'prefer-named-capture-group': 'off',
'sort-keys': 'off', 'sort-keys': 'off',
'vue/html-self-closing': 'off', 'vue/html-self-closing': 'off',
'vue/max-attributes-per-line': 'off', 'vue/max-attributes-per-line': 'off',
'vue/prop-name-casing': 'off', 'vue/prop-name-casing': 'off'
'vue/singleline-html-element-content-newline': 'off'
} }
} }
] ]

View File

@ -265,17 +265,6 @@ export default {
document.querySelector('html').classList.remove('is-clipped') document.querySelector('html').classList.remove('is-clipped')
} }
}, },
update_outputs() {
webapi.outputs().then(({ data }) => {
this.$store.commit(types.UPDATE_OUTPUTS, data.outputs)
})
},
update_player_status() {
webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data)
this.update_lyrics()
})
},
update_lastfm() { update_lastfm() {
webapi.lastfm().then(({ data }) => { webapi.lastfm().then(({ data }) => {
this.$store.commit(types.UPDATE_LASTFM, data) this.$store.commit(types.UPDATE_LASTFM, data)
@ -299,12 +288,23 @@ export default {
this.$store.commit(types.UPDATE_LYRICS) this.$store.commit(types.UPDATE_LYRICS)
} }
}, },
update_outputs() {
webapi.outputs().then(({ data }) => {
this.$store.commit(types.UPDATE_OUTPUTS, data.outputs)
})
},
update_pairing() { update_pairing() {
webapi.pairing().then(({ data }) => { webapi.pairing().then(({ data }) => {
this.$store.commit(types.UPDATE_PAIRING, data) this.$store.commit(types.UPDATE_PAIRING, data)
this.pairing_active = data.active this.pairing_active = data.active
}) })
}, },
update_player_status() {
webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data)
this.update_lyrics()
})
},
update_queue() { update_queue() {
webapi.queue().then(({ data }) => { webapi.queue().then(({ data }) => {
this.$store.commit(types.UPDATE_QUEUE, data) this.$store.commit(types.UPDATE_QUEUE, data)

View File

@ -94,7 +94,7 @@ export default {
return this.media_kind || this.selected_item.media_kind return this.media_kind || this.selected_item.media_kind
}, },
show_artwork() { show_artwork() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_cover_artwork_in_album_lists' 'show_cover_artwork_in_album_lists'
).value ).value
@ -122,10 +122,10 @@ export default {
open_remove_podcast_dialog() { open_remove_podcast_dialog() {
webapi webapi
.library_album_tracks(this.selected_item.id, { limit: 1 }) .library_album_tracks(this.selected_item.id, { limit: 1 })
.then(({ data }) => { .then(({ data: album }) => {
webapi.library_track_playlists(data.items[0].id).then(({ data }) => { webapi.library_track_playlists(album.items[0].id).then(({ data }) => {
;[this.rss_playlist_to_remove] = data.items.filter( ;[this.rss_playlist_to_remove] = data.items.filter(
(pl) => pl.type === 'rss' (playlist) => playlist.type === 'rss'
) )
this.show_remove_podcast_modal = true this.show_remove_podcast_modal = true
this.show_details_modal = false this.show_details_modal = false

View File

@ -53,7 +53,7 @@ export default {
computed: { computed: {
show_artwork() { show_artwork() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_cover_artwork_in_album_lists' 'show_cover_artwork_in_album_lists'
).value ).value

View File

@ -55,31 +55,28 @@ export default {
const parsed = [] const parsed = []
if (raw) { if (raw) {
// Parse the lyrics // Parse the lyrics
const regex = /(\[(\d+):(\d+)(?:\.\d+)?\] ?)?(.*)/u const regex =
raw.split('\n').forEach((item, index) => { /\[(?<minutes>\d+):(?<seconds>\d+)(?:\.(?<hundredths>\d+))?\] ?(?<text>.*)/u
const matches = regex.exec(item) raw.split('\n').forEach((line) => {
if (matches && matches[4]) { const { text, minutes, seconds, hundredths } = regex.exec(line).groups
if (text) {
const verse = { const verse = {
text: matches[4], text,
time: matches[2] * 60 + Number(matches[3]) time:
minutes * 60 + Number(seconds) + Number(`.${hundredths || 0}`)
} }
parsed.push(verse) parsed.push(verse)
} }
}) })
// Split the verses into words // Split the verses into words
parsed.forEach((verse, index, lyrics) => { parsed.forEach((verse, index, lyrics) => {
const duration = const unitDuration =
index < lyrics.length - 1 ? lyrics[index + 1].time - verse.time : 3 (lyrics[index + 1].time - verse.time || 3) / verse.text.length
const unitDuration = duration / verse.text.length
let delay = 0 let delay = 0
verse.words = verse.text.match(/\S+\s*/gu).map((text) => { verse.words = verse.text.match(/\S+\s*/gu).map((text) => {
const duration = text.length * unitDuration const duration = text.length * unitDuration
delay += duration delay += duration
return { return { duration, delay, text }
duration,
delay,
text
}
}) })
}) })
} }
@ -117,25 +114,25 @@ export default {
} }
// Not found, then start a binary search // Not found, then start a binary search
let end = la.length - 1, let end = la.length - 1,
index = 0, index = -1,
start = 0 start = 0
while (start <= end) { while (start <= end) {
index = (start + end) >> 1 index = (start + end) >> 1
const currentVerse = la[index] const currentVerseTime = la[index].time
const nextVerse = la[index + 1] const nextVerseTime = la[index + 1]?.time
if ( if (
currentVerse.time <= currentTime && currentVerseTime <= currentTime &&
(nextVerse?.time > currentTime || !nextVerse) (nextVerseTime > currentTime || !nextVerseTime)
) { ) {
return index break
} }
if (currentVerse.time < currentTime) { if (currentVerseTime < currentTime) {
start = index + 1 start = index + 1
} else { } else {
end = index - 1 end = index - 1
} }
} }
return -1 return index
} }
this.reset_scrolling() this.reset_scrolling()
return -1 return -1
@ -175,13 +172,11 @@ export default {
pane.scrollTop pane.scrollTop
}) })
}, },
start_scrolling(e) { start_scrolling(event) {
// Consider only user events // Consider only user events
if (e.screenX || e.screenX !== 0 || e.screenY || e.screenY !== 0) { if (event.screenX ?? event.screenY) {
this.autoScrolling = false this.autoScrolling = false
if (this.scrollingTimer) { clearTimeout(this.scrollingTimer)
clearTimeout(this.scrollingTimer)
}
// Reenable automatic scrolling after 2 seconds // Reenable automatic scrolling after 2 seconds
this.scrollingTimer = setTimeout((this.autoScrolling = true), 2000) this.scrollingTimer = setTimeout((this.autoScrolling = true), 2000)
} }

View File

@ -74,8 +74,8 @@ export default {
data() { data() {
return { return {
disabled: true, disabled: true,
playlist_name: '', loading: false,
loading: false playlist_name: ''
} }
}, },

View File

@ -103,7 +103,7 @@
</div> </div>
</div> </div>
<div class="navbar-brand is-flex-grow-1"> <div class="navbar-brand is-flex-grow-1">
<navbar-item-link :to="{ name: 'queue' }" exact class="mr-auto"> <navbar-item-link :to="{ name: 'queue' }" class="mr-auto">
<mdicon class="icon" name="playlist-play" size="24" /> <mdicon class="icon" name="playlist-play" size="24" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link <navbar-item-link

View File

@ -1,10 +1,5 @@
<template> <template>
<a <a class="navbar-item" :href="href" @click.stop.prevent="open">
class="navbar-item"
:class="{ 'is-active': is_active }"
:href="full_path()"
@click.stop.prevent="open_link()"
>
<slot /> <slot />
</a> </a>
</template> </template>
@ -15,47 +10,21 @@ import * as types from '@/store/mutation_types'
export default { export default {
name: 'NavbarItemLink', name: 'NavbarItemLink',
props: { props: {
exact: Boolean,
to: { required: true, type: Object } to: { required: true, type: Object }
}, },
computed: { computed: {
is_active() { href() {
if (this.exact) { return this.$router.resolve(this.to).href
return this.$route.path === this.to
}
return this.$route.path.startsWith(this.to)
},
show_burger_menu: {
get() {
return this.$store.state.show_burger_menu
},
set(value) {
this.$store.commit(types.SHOW_BURGER_MENU, value)
}
},
show_player_menu: {
get() {
return this.$store.state.show_player_menu
},
set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value)
}
} }
}, },
methods: { methods: {
full_path() { open() {
const resolved = this.$router.resolve(this.to) if (this.$store.state.show_burger_menu) {
return resolved.href
},
open_link() {
if (this.show_burger_menu) {
this.$store.commit(types.SHOW_BURGER_MENU, false) this.$store.commit(types.SHOW_BURGER_MENU, false)
} }
if (this.show_player_menu) { if (this.$store.state.show_player_menu) {
this.$store.commit(types.SHOW_PLAYER_MENU, false) this.$store.commit(types.SHOW_PLAYER_MENU, false)
} }
this.$router.push(this.to) this.$router.push(this.to)

View File

@ -54,7 +54,7 @@
<mdicon class="icon" name="music-box-multiple" size="16" /> <mdicon class="icon" name="music-box-multiple" size="16" />
<b v-text="$t('navigation.playlists')" /> <b v-text="$t('navigation.playlists')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link :to="{ name: 'music' }" exact> <navbar-item-link :to="{ name: 'music' }">
<mdicon class="icon" name="music" size="16" /> <mdicon class="icon" name="music" size="16" />
<b v-text="$t('navigation.music')" /> <b v-text="$t('navigation.music')" />
</navbar-item-link> </navbar-item-link>
@ -138,7 +138,7 @@ export default {
} }
}, },
show_audiobooks() { show_audiobooks() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_menu_item_audiobooks' 'show_menu_item_audiobooks'
).value ).value
@ -152,40 +152,34 @@ export default {
} }
}, },
show_files() { show_files() {
return this.$store.getters.settings_option( return this.$store.getters.setting('webinterface', 'show_menu_item_files')
'webinterface', .value
'show_menu_item_files'
).value
}, },
show_music() { show_music() {
return this.$store.getters.settings_option( return this.$store.getters.setting('webinterface', 'show_menu_item_music')
'webinterface', .value
'show_menu_item_music'
).value
}, },
show_player_menu() { show_player_menu() {
return this.$store.state.show_player_menu return this.$store.state.show_player_menu
}, },
show_playlists() { show_playlists() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_menu_item_playlists' 'show_menu_item_playlists'
).value ).value
}, },
show_podcasts() { show_podcasts() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_menu_item_podcasts' 'show_menu_item_podcasts'
).value ).value
}, },
show_radio() { show_radio() {
return this.$store.getters.settings_option( return this.$store.getters.setting('webinterface', 'show_menu_item_radio')
'webinterface', .value
'show_menu_item_radio'
).value
}, },
show_search() { show_search() {
return this.$store.getters.settings_option( return this.$store.getters.setting(
'webinterface', 'webinterface',
'show_menu_item_search' 'show_menu_item_search'
).value ).value

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="field"> <div class="field">
<input <input
:id="option.name" :id="setting.name"
v-model="option.value" v-model="setting.value"
type="checkbox" type="checkbox"
class="switch is-rounded mr-2" class="switch is-rounded mr-2"
@change="update_setting" @change="update_setting"
/> />
<label class="pt-0" :for="option.name"> <label class="pt-0" :for="setting.name">
<slot name="label" /> <slot name="label" />
</label> </label>
<i <i
@ -27,8 +27,8 @@ import webapi from '@/webapi'
export default { export default {
name: 'SettingsCheckbox', name: 'SettingsCheckbox',
props: { props: {
category_name: { required: true, type: String }, category: { required: true, type: String },
option_name: { required: true, type: String } name: { required: true, type: String }
}, },
data() { data() {
@ -54,40 +54,37 @@ export default {
is_success() { is_success() {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
option() { setting() {
const option = this.$store.getters.settings_option( const setting = this.$store.getters.setting(this.category, this.name)
this.category_name, if (!setting) {
this.option_name
)
if (!option) {
return { return {
category: this.category_name, category: this.category,
name: this.option_name, name: this.name,
value: false value: false
} }
} }
return option return setting
} }
}, },
methods: { methods: {
clear_status() { clear_status() {
if (this.is_error) { if (this.is_error) {
this.option.value = !this.option.value this.setting.value = !this.setting.value
} }
this.statusUpdate = '' this.statusUpdate = ''
}, },
update_setting() { update_setting() {
this.timerId = -1 this.timerId = -1
const option = { const setting = {
category: this.category_name, category: this.category,
name: this.option_name, name: this.name,
value: this.option.value value: this.setting.value
} }
webapi webapi
.settings_update(this.category_name, option) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_settings_option', option) this.$store.dispatch('update_setting', setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -16,7 +16,7 @@
inputmode="numeric" inputmode="numeric"
min="0" min="0"
:placeholder="placeholder" :placeholder="placeholder"
:value="value" :value="setting.value"
@input="set_update_timer" @input="set_update_timer"
/> />
</div> </div>
@ -33,9 +33,9 @@ import webapi from '@/webapi'
export default { export default {
name: 'SettingsIntfield', name: 'SettingsIntfield',
props: { props: {
category_name: { required: true, type: String }, category: { required: true, type: String },
disabled: Boolean, disabled: Boolean,
option_name: { required: true, type: String }, name: { required: true, type: String },
placeholder: { default: '', type: String } placeholder: { default: '', type: String }
}, },
@ -48,11 +48,6 @@ export default {
}, },
computed: { computed: {
category() {
return this.$store.state.settings.categories.find(
(elem) => elem.name === this.category_name
)
},
info() { info() {
if (this.statusUpdate === 'success') { if (this.statusUpdate === 'success') {
return this.$t('setting.saved') return this.$t('setting.saved')
@ -67,16 +62,8 @@ export default {
is_success() { is_success() {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
option() { setting() {
if (!this.category) { return this.$store.getters.setting(this.category, this.name)
return {}
}
return this.category.options.find(
(elem) => elem.name === this.option_name
)
},
value() {
return this.option.value
} }
}, },
@ -100,15 +87,15 @@ export default {
this.statusUpdate = '' this.statusUpdate = ''
return return
} }
const option = { const setting = {
category: this.category.name, category: this.category,
name: this.option_name, name: this.name,
value: newValue value: newValue
} }
webapi webapi
.settings_update(this.category.name, option) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_settings_option', option) this.$store.dispatch('update_setting', setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -15,7 +15,7 @@
class="input" class="input"
type="text" type="text"
:placeholder="placeholder" :placeholder="placeholder"
:value="value" :value="setting.value"
@input="set_update_timer" @input="set_update_timer"
/> />
</div> </div>
@ -32,9 +32,9 @@ import webapi from '@/webapi'
export default { export default {
name: 'SettingsTextfield', name: 'SettingsTextfield',
props: { props: {
category_name: { required: true, type: String }, category: { required: true, type: String },
disabled: Boolean, disabled: Boolean,
option_name: { required: true, type: String }, name: { required: true, type: String },
placeholder: { default: '', type: String } placeholder: { default: '', type: String }
}, },
@ -47,11 +47,6 @@ export default {
}, },
computed: { computed: {
category() {
return this.$store.state.settings.categories.find(
(elem) => elem.name === this.category_name
)
},
info() { info() {
if (this.statusUpdate === 'success') { if (this.statusUpdate === 'success') {
return this.$t('setting.saved') return this.$t('setting.saved')
@ -66,16 +61,8 @@ export default {
is_success() { is_success() {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
option() { setting() {
if (!this.category) { return this.$store.getters.setting(this.category, this.name)
return {}
}
return this.category.options.find(
(elem) => elem.name === this.option_name
)
},
value() {
return this.option.value
} }
}, },
@ -101,15 +88,15 @@ export default {
this.statusUpdate = '' this.statusUpdate = ''
return return
} }
const option = { const setting = {
category: this.category.name, category: this.category,
name: this.option_name, name: this.name,
value: newValue value: newValue
} }
webapi webapi
.settings_update(this.category.name, option) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_settings_option', option) this.$store.dispatch('update_setting', setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -31,8 +31,8 @@ export const filters = {
.setLocale(locale.value) .setLocale(locale.value)
.toLocaleString(DateTime.DATETIME_MED) .toLocaleString(DateTime.DATETIME_MED)
}, },
durationInDays(value_ms) { durationInDays(value) {
const minutes = Math.floor(value_ms / 60000) const minutes = Math.floor(value / 60000)
if (minutes > 1440) { if (minutes > 1440) {
return Duration.fromObject({ minutes }) return Duration.fromObject({ minutes })
.shiftTo('days', 'hours', 'minutes') .shiftTo('days', 'hours', 'minutes')
@ -44,9 +44,9 @@ export const filters = {
} }
return Duration.fromObject({ minutes }).shiftTo('minutes').toHuman() return Duration.fromObject({ minutes }).shiftTo('minutes').toHuman()
}, },
durationInHours(value_ms) { durationInHours(value) {
const format = value_ms >= 3600000 ? 'h:mm:ss' : 'm:ss' const format = value >= 3600000 ? 'h:mm:ss' : 'm:ss'
return Duration.fromMillis(value_ms).toFormat(format) return Duration.fromMillis(value).toFormat(format)
}, },
number(value) { number(value) {
return value.toLocaleString(locale.value) return value.toLocaleString(locale.value)

View File

@ -29,10 +29,10 @@ export default {
this.context = new (window.AudioContext || window.webkitAudioContext)() this.context = new (window.AudioContext || window.webkitAudioContext)()
const source = this.context.createMediaElementSource(this.audio) const source = this.context.createMediaElementSource(this.audio)
source.connect(this.context.destination) source.connect(this.context.destination)
this.audio.addEventListener('canplaythrough', (e) => { this.audio.addEventListener('canplaythrough', () => {
this.audio.play() this.audio.play()
}) })
this.audio.addEventListener('canplay', (e) => { this.audio.addEventListener('canplay', () => {
this.audio.play() this.audio.play()
}) })
return this.audio return this.audio
@ -42,17 +42,17 @@ export default {
stop() { stop() {
try { try {
this.audio.pause() this.audio.pause()
} catch (e) { } catch (error) {
// Continue regardless of error // Continue regardless of error
} }
try { try {
this.audio.stop() this.audio.stop()
} catch (e) { } catch (error) {
// Continue regardless of error // Continue regardless of error
} }
try { try {
this.audio.close() this.audio.close()
} catch (e) { } catch (error) {
// Continue regardless of error // Continue regardless of error
} }
} }

View File

@ -6,7 +6,7 @@ const NO_INDEX = 'NO_INDEX'
const numberComparator = (a, b) => a - b const numberComparator = (a, b) => a - b
const stringComparator = (a, b) => a.localeCompare(b, locale.value) const stringComparator = (a, b) => a.localeCompare(b, locale.value)
const dateComparator = (a, b) => const dateComparator = (a, b) =>
new Date(a) - new Date(b) || (!a ? -1 : !b ? 1 : 0) new Date(a) - new Date(b) || (a ? (b ? 0 : 1) : -1)
const createComparators = (criteria) => const createComparators = (criteria) =>
criteria.map(({ field, type, order = 1 }) => { criteria.map(({ field, type, order = 1 }) => {
@ -18,7 +18,7 @@ const createComparators = (criteria) =>
case Date: case Date:
return (a, b) => dateComparator(a[field], b[field]) * order return (a, b) => dateComparator(a[field], b[field]) * order
default: default:
return (a, b) => 0 return () => 0
} }
}) })
@ -61,7 +61,7 @@ const createIndexer = ({ field, type } = {}) => {
case 'Digits': case 'Digits':
return (item) => numberIndex(item[field]) return (item) => numberIndex(item[field])
default: default:
return (item) => NO_INDEX return () => NO_INDEX
} }
} }
@ -80,7 +80,6 @@ export class GroupedList {
} }
group({ criteria = [], filters = [], index } = {}) { group({ criteria = [], filters = [], index } = {}) {
const indexer = createIndexer(index)
const itemsFiltered = this.items.filter((item) => const itemsFiltered = this.items.filter((item) =>
filters.every((filter) => filter(item)) filters.every((filter) => filter(item))
) )
@ -94,13 +93,15 @@ export class GroupedList {
) )
) )
// Group item list // Group item list
const indexer = createIndexer(index)
this.itemsGrouped = itemsSorted.reduce((map, item) => { this.itemsGrouped = itemsSorted.reduce((map, item) => {
const index = indexer(item) const key = indexer(item)
map.set(index, [...(map.get(index) || []), item]) map.set(key, [...(map.get(key) || []), item])
return map return map
}, new Map()) }, new Map())
// Create index list // Create index list
this.indices = Array.from(this.itemsGrouped.keys()) this.indices = Array.from(this.itemsGrouped.keys())
return this
} }
*generate() { *generate() {

View File

@ -158,7 +158,7 @@
<a href="https://vuejs.org/">Vue.js</a> <a href="https://vuejs.org/">Vue.js</a>
</template> </template>
<template #axios> <template #axios>
<a href="https://github.com/mzabriskie/axios">axios</a> <a href="https://github.com/axios/axios">axios</a>
</template> </template>
<template #others> <template #others>
<a <a

View File

@ -41,8 +41,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.albums.sort.title')" /> <p class="heading mb-5" v-text="$t('page.albums.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -101,7 +101,7 @@ export default {
data() { data() {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.albums.sort.name'), name: this.$t('page.albums.sort.name'),
@ -151,16 +151,14 @@ export default {
computed: { computed: {
albums() { albums() {
const grouping = this.grouping_options.find( const { options } = this.groupings.find(
(o) => o.id === this.selected_grouping_option_id (grouping) => grouping.id === this.selected_grouping_id
) )
grouping.options.filters = [ options.filters = [
(album) => !this.hide_singles || album.track_count > 2, (album) => !this.hide_singles || album.track_count > 2,
(album) => !this.hide_spotify || album.data_kind !== 'spotify' (album) => !this.hide_spotify || album.data_kind !== 'spotify'
] ]
this.albums_list.group(grouping.options) return this.albums_list.group(options)
return this.albums_list
}, },
hide_singles: { hide_singles: {
get() { get() {
@ -178,7 +176,7 @@ export default {
this.$store.commit(types.HIDE_SPOTIFY, value) this.$store.commit(types.HIDE_SPOTIFY, value)
} }
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.albums_sort return this.$store.state.albums_sort
}, },

View File

@ -24,8 +24,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -112,7 +112,7 @@ export default {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
artist: {}, artist: {},
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.artist.sort.name'), name: this.$t('page.artist.sort.name'),
@ -130,14 +130,13 @@ export default {
computed: { computed: {
albums() { albums() {
const grouping = this.grouping_options.find( const { options } = this.groupings.find(
(o) => o.id === this.selected_grouping_option_id (grouping) => grouping.id === this.selected_grouping_id
) )
grouping.options.filters = [ options.filters = [
(album) => !this.hide_spotify || album.data_kind !== 'spotify' (album) => !this.hide_spotify || album.data_kind !== 'spotify'
] ]
this.albums_list.group(grouping.options) return this.albums_list.group(options)
return this.albums_list
}, },
hide_spotify: { hide_spotify: {
get() { get() {
@ -147,7 +146,7 @@ export default {
this.$store.commit(types.HIDE_SPOTIFY, value) this.$store.commit(types.HIDE_SPOTIFY, value)
} }
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artist_albums_sort return this.$store.state.artist_albums_sort
}, },

View File

@ -32,7 +32,9 @@
</div> </div>
</div> </div>
</template> </template>
<template #no-more>&nbsp;</template> <template #no-more>
<br />
</template>
</VueEternalLoading> </VueEternalLoading>
<modal-dialog-artist-spotify <modal-dialog-artist-spotify
:item="artist" :item="artist"

View File

@ -25,8 +25,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -114,7 +114,7 @@ export default {
data() { data() {
return { return {
artist: {}, artist: {},
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.artist.sort.name'), name: this.$t('page.artist.sort.name'),
@ -150,7 +150,7 @@ export default {
this.$store.commit(types.HIDE_SPOTIFY, value) this.$store.commit(types.HIDE_SPOTIFY, value)
} }
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artist_tracks_sort return this.$store.state.artist_tracks_sort
}, },
@ -165,14 +165,13 @@ export default {
return this.tracks_list.items.map((item) => item.uri).join() return this.tracks_list.items.map((item) => item.uri).join()
}, },
tracks() { tracks() {
const grouping = this.grouping_options.find( const { options } = this.groupings.find(
(o) => o.id === this.selected_grouping_option_id (grouping) => grouping.id === this.selected_grouping_id
) )
grouping.options.filters = [ options.filters = [
(track) => !this.hide_spotify || track.data_kind !== 'spotify' (track) => !this.hide_spotify || track.data_kind !== 'spotify'
] ]
this.tracks_list.group(grouping.options) return this.tracks_list.group(options)
return this.tracks_list
} }
}, },

View File

@ -41,8 +41,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artists.sort.title')" /> <p class="heading mb-5" v-text="$t('page.artists.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -101,7 +101,7 @@ export default {
data() { data() {
return { return {
artists_list: new GroupedList(), artists_list: new GroupedList(),
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.artists.sort.name'), name: this.$t('page.artists.sort.name'),
@ -122,19 +122,15 @@ export default {
computed: { computed: {
// Wraps GroupedList and updates it if filter or sort changes // Wraps GroupedList and updates it if filter or sort changes
artists() { artists() {
if (!this.artists_list) { const { options } = this.groupings.find(
return [] (grouping) => grouping.id === this.selected_grouping_id
}
const grouping = this.grouping_options.find(
(o) => o.id === this.selected_grouping_option_id
) )
grouping.options.filters = [ options.filters = [
(artist) => (artist) =>
!this.hide_singles || artist.track_count > artist.album_count * 2, !this.hide_singles || artist.track_count > artist.album_count * 2,
(artist) => !this.hide_spotify || artist.data_kind !== 'spotify' (artist) => !this.hide_spotify || artist.data_kind !== 'spotify'
] ]
this.artists_list.group(grouping.options) return this.artists_list.group(options)
return this.artists_list
}, },
hide_singles: { hide_singles: {
get() { get() {
@ -152,7 +148,7 @@ export default {
this.$store.commit(types.HIDE_SPOTIFY, value) this.$store.commit(types.HIDE_SPOTIFY, value)
} }
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artists_sort return this.$store.state.artists_sort
}, },

View File

@ -7,8 +7,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -102,7 +102,7 @@ export default {
data() { data() {
return { return {
composer: {}, composer: {},
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.composer.sort.name'), name: this.$t('page.composer.sort.name'),
@ -126,7 +126,7 @@ export default {
expression() { expression() {
return `composer is "${this.composer.name}" and media_kind is music` return `composer is "${this.composer.name}" and media_kind is music`
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.composer_tracks_sort return this.$store.state.composer_tracks_sort
}, },
@ -135,11 +135,10 @@ export default {
} }
}, },
tracks() { tracks() {
const grouping = this.grouping_options.find( const { options } = this.groupings.find(
(o) => o.id === this.selected_grouping_option_id (grouping) => grouping.id === this.selected_grouping_id
) )
this.tracks_list.group(grouping.options) return this.tracks_list.group(options)
return this.tracks_list
} }
}, },

View File

@ -125,7 +125,7 @@ export default {
webapi.player_play_expression(this.play_expression, false) webapi.player_play_expression(this.play_expression, false)
}, },
transform(path) { transform(path) {
return { path, name: path.slice(path.lastIndexOf('/') + 1) } return { name: path.slice(path.lastIndexOf('/') + 1), path }
} }
} }
} }

View File

@ -7,8 +7,8 @@
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.genre.sort.title')" /> <p class="heading mb-5" v-text="$t('page.genre.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_option_id" v-model:value="selected_grouping_id"
:options="grouping_options" :options="groupings"
/> />
</div> </div>
</div> </div>
@ -97,7 +97,7 @@ export default {
data() { data() {
return { return {
genre: {}, genre: {},
grouping_options: [ groupings: [
{ {
id: 1, id: 1,
name: this.$t('page.genre.sort.name'), name: this.$t('page.genre.sort.name'),
@ -122,7 +122,7 @@ export default {
expression() { expression() {
return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}` return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}`
}, },
selected_grouping_option_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.genre_tracks_sort return this.$store.state.genre_tracks_sort
}, },
@ -131,11 +131,10 @@ export default {
} }
}, },
tracks() { tracks() {
const grouping = this.grouping_options.find( const { options } = this.groupings.find(
(o) => o.id === this.selected_grouping_option_id (grouping) => grouping.id === this.selected_grouping_id
) )
this.tracks_list.group(grouping.options) return this.tracks_list.group(options)
return this.tracks_list
} }
}, },

View File

@ -1,13 +1,12 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<!-- Recently added -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-added.title')" /> <p class="title is-4" v-text="$t('page.music.recently-added.title')" />
</template> </template>
<template #content> <template #content>
<list-albums :items="recently_added" /> <list-albums :items="albums" />
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">
@ -22,13 +21,12 @@
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<!-- Recently played -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-played.title')" /> <p class="title is-4" v-text="$t('page.music.recently-played.title')" />
</template> </template>
<template #content> <template #content>
<list-tracks :items="recently_played" /> <list-tracks :items="tracks" />
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">
@ -73,8 +71,8 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.recently_added = new GroupedList(response[0].data.albums) vm.albums = new GroupedList(response[0].data.albums)
vm.recently_played = new GroupedList(response[1].data.tracks) vm.tracks = new GroupedList(response[1].data.tracks)
} }
} }
@ -90,8 +88,8 @@ export default {
data() { data() {
return { return {
recently_added: [], albums: [],
recently_played: { items: [] }, tracks: { items: [] },
selected_track: {} selected_track: {}
} }
} }

View File

@ -6,7 +6,7 @@
<p class="title is-4" v-text="$t('page.music.recently-added.title')" /> <p class="title is-4" v-text="$t('page.music.recently-added.title')" />
</template> </template>
<template #content> <template #content>
<list-albums :items="recently_added" /> <list-albums :items="albums" />
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -22,7 +22,7 @@ import webapi from '@/webapi'
const dataObject = { const dataObject = {
load(to) { load(to) {
const limit = store.getters.settings_option_recently_added_limit const limit = store.getters.setting_recently_added_limit
return webapi.search({ return webapi.search({
expression: expression:
'media_kind is music having track_count > 3 order by time_added desc', 'media_kind is music having track_count > 3 order by time_added desc',
@ -32,7 +32,7 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.recently_added = new GroupedList(response.data.albums, { vm.albums = new GroupedList(response.data.albums, {
criteria: [{ field: 'time_added', order: -1, type: Date }], criteria: [{ field: 'time_added', order: -1, type: Date }],
index: { field: 'time_added', type: Date } index: { field: 'time_added', type: Date }
}) })
@ -51,7 +51,7 @@ export default {
data() { data() {
return { return {
recently_added: new GroupedList() albums: new GroupedList()
} }
} }
} }

View File

@ -6,7 +6,7 @@
<p class="title is-4" v-text="$t('page.music.recently-played.title')" /> <p class="title is-4" v-text="$t('page.music.recently-played.title')" />
</template> </template>
<template #content> <template #content>
<list-tracks :items="recently_played" /> <list-tracks :items="tracks" />
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -30,7 +30,7 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.recently_played = new GroupedList(response.data.tracks) vm.tracks = new GroupedList(response.data.tracks)
} }
} }
@ -46,7 +46,7 @@ export default {
data() { data() {
return { return {
recently_played: {} tracks: {}
} }
} }
} }

View File

@ -1,13 +1,12 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<!-- New Releases -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <p class="title is-4" v-text="$t('page.spotify.music.new-releases')" />
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="new_releases" /> <list-albums-spotify :items="albums" />
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">
@ -22,7 +21,6 @@
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<!-- Featured Playlists -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p <p
@ -31,7 +29,7 @@
/> />
</template> </template>
<template #content> <template #content>
<list-playlists-spotify :items="featured_playlists" /> <list-playlists-spotify :items="playlists" />
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">
@ -76,8 +74,8 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.new_releases = response[0].albums.items vm.albums = response[0].albums.items
vm.featured_playlists = response[1].playlists.items vm.playlists = response[1].playlists.items
} }
} }
@ -98,8 +96,8 @@ export default {
data() { data() {
return { return {
featured_playlists: [], playlists: [],
new_releases: [] albums: []
} }
} }
} }

View File

@ -9,7 +9,7 @@
/> />
</template> </template>
<template #content> <template #content>
<list-playlists-spotify :items="featured_playlists" /> <list-playlists-spotify :items="playlists" />
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -35,7 +35,7 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.featured_playlists = response.playlists.items vm.playlists = response.playlists.items
} }
} }
@ -55,7 +55,7 @@ export default {
data() { data() {
return { return {
featured_playlists: [] playlists: []
} }
} }
} }

View File

@ -6,7 +6,7 @@
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <p class="title is-4" v-text="$t('page.spotify.music.new-releases')" />
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="new_releases" /> <list-albums-spotify :items="albums" />
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -32,7 +32,7 @@ const dataObject = {
}, },
set(vm, response) { set(vm, response) {
vm.new_releases = response.albums.items vm.albums = response.albums.items
} }
} }
@ -52,7 +52,7 @@ export default {
data() { data() {
return { return {
new_releases: [] albums: []
} }
} }
} }

View File

@ -76,8 +76,8 @@ export default {
data() { data() {
return { return {
cursor: mdiCancel,
INTERVAL, INTERVAL,
cursor: mdiCancel,
interval_id: 0, interval_id: 0,
is_dragged: false, is_dragged: false,
selected_item: {}, selected_item: {},
@ -87,11 +87,11 @@ export default {
computed: { computed: {
composer() { composer() {
if (this.settings_option_show_composer_now_playing) { if (this.setting_show_composer_now_playing) {
if ( if (
!this.settings_option_show_composer_for_genre || !this.setting_show_composer_for_genre ||
(this.track.genre && (this.track.genre &&
this.settings_option_show_composer_for_genre this.setting_show_composer_for_genre
.toLowerCase() .toLowerCase()
.split(',') .split(',')
.findIndex( .findIndex(
@ -105,7 +105,7 @@ export default {
return null return null
}, },
filepath() { filepath() {
if (this.settings_option_show_filepath_now_playing) { if (this.setting_show_filepath_now_playing) {
return this.track.path return this.track.path
} }
return null return null
@ -119,14 +119,14 @@ export default {
player() { player() {
return this.$store.state.player return this.$store.state.player
}, },
settings_option_show_composer_for_genre() { setting_show_composer_for_genre() {
return this.$store.getters.settings_option_show_composer_for_genre return this.$store.getters.setting_show_composer_for_genre
}, },
settings_option_show_composer_now_playing() { setting_show_composer_now_playing() {
return this.$store.getters.settings_option_show_composer_now_playing return this.$store.getters.setting_show_composer_now_playing
}, },
settings_option_show_filepath_now_playing() { setting_show_filepath_now_playing() {
return this.$store.getters.settings_option_show_filepath_now_playing return this.$store.getters.setting_show_filepath_now_playing
}, },
track() { track() {
return this.$store.getters.now_playing return this.$store.getters.now_playing

View File

@ -66,7 +66,7 @@ export default {
computed: { computed: {
playlists() { playlists() {
this.playlists_list.group({ return this.playlists_list.group({
filters: [ filters: [
(playlist) => (playlist) =>
playlist.folder || playlist.folder ||
@ -75,7 +75,6 @@ export default {
playlist.item_count > playlist.stream_count playlist.item_count > playlist.stream_count
] ]
}) })
return this.playlists_list
}, },
radio_playlists() { radio_playlists() {
return this.$store.state.config.radio_playlists return this.$store.state.config.radio_playlists

View File

@ -34,7 +34,9 @@
</div> </div>
</div> </div>
</template> </template>
<template #no-more>&nbsp;</template> <template #no-more>
<br />
</template>
</VueEternalLoading> </VueEternalLoading>
<modal-dialog-playlist-spotify <modal-dialog-playlist-spotify
:item="playlist" :item="playlist"

View File

@ -3,7 +3,9 @@
<content-with-hero> <content-with-hero>
<template #heading-left> <template #heading-left>
<h1 class="title is-5" v-text="album.name" /> <h1 class="title is-5" v-text="album.name" />
<h2 class="subtitle is-6">&nbsp;</h2> <h2 class="subtitle is-6">
<br />
</h2>
<div class="buttons fd-is-centered-mobile mt-5"> <div class="buttons fd-is-centered-mobile mt-5">
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" /> <mdicon class="icon" name="play" size="16" />

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<content-with-heading v-if="new_episodes.items.length > 0"> <content-with-heading v-if="tracks.items.length > 0">
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.podcasts.new-episodes')" /> <p class="title is-4" v-text="$t('page.podcasts.new-episodes')" />
</template> </template>
@ -14,7 +14,7 @@
</template> </template>
<template #content> <template #content>
<list-tracks <list-tracks
:items="new_episodes" :items="tracks"
:show_progress="true" :show_progress="true"
@play-count-changed="reload_new_episodes" @play-count-changed="reload_new_episodes"
/> />
@ -75,7 +75,7 @@ const dataObject = {
set(vm, response) { set(vm, response) {
vm.albums = new GroupedList(response[0].data) vm.albums = new GroupedList(response[0].data)
vm.new_episodes = new GroupedList(response[1].data.tracks) vm.tracks = new GroupedList(response[1].data.tracks)
} }
} }
@ -97,8 +97,7 @@ export default {
data() { data() {
return { return {
albums: [], albums: [],
new_episodes: { items: [] }, tracks: { items: [] },
show_url_modal: false show_url_modal: false
} }
}, },
@ -111,10 +110,10 @@ export default {
methods: { methods: {
mark_all_played() { mark_all_played() {
this.new_episodes.items.forEach((ep) => { this.tracks.items.forEach((ep) => {
webapi.library_track_update(ep.id, { play_count: 'increment' }) webapi.library_track_update(ep.id, { play_count: 'increment' })
}) })
this.new_episodes.items = {} this.tracks.items = {}
}, },
open_add_podcast_dialog() { open_add_podcast_dialog() {
@ -123,7 +122,7 @@ export default {
reload_new_episodes() { reload_new_episodes() {
webapi.library_podcasts_new_episodes().then(({ data }) => { webapi.library_podcasts_new_episodes().then(({ data }) => {
this.new_episodes = new GroupedList(data.tracks) this.tracks = new GroupedList(data.tracks)
}) })
}, },

View File

@ -150,7 +150,7 @@ export default {
get() { get() {
return this.$store.state.queue.items return this.$store.state.queue.items
}, },
set(value) { set() {
/* Do nothing? Send move request in @end event */ /* Do nothing? Send move request in @end event */
} }
}, },
@ -163,11 +163,11 @@ export default {
}, },
methods: { methods: {
move_item(e) { move_item(event) {
const oldPosition = const oldPosition =
e.oldIndex + (this.show_only_next_items && this.current_position) event.oldIndex + (this.show_only_next_items && this.current_position)
const item = this.queue_items[oldPosition] const item = this.queue_items[oldPosition]
const newPosition = item.position + (e.newIndex - e.oldIndex) const newPosition = item.position + (event.newIndex - event.oldIndex)
if (newPosition !== oldPosition) { if (newPosition !== oldPosition) {
webapi.queue_move(item.id, newPosition) webapi.queue_move(item.id, newPosition)
} }

View File

@ -22,7 +22,9 @@
keypath="page.search.help" keypath="page.search.help"
scope="global" scope="global"
> >
<template #query><code>query:</code></template> <template #query>
<code>query:</code>
</template>
<template #help> <template #help>
<a <a
href="https://owntone.github.io/owntone-server/smart-playlists/" href="https://owntone.github.io/owntone-server/smart-playlists/"
@ -37,7 +39,7 @@
<div v-for="query in recent_searches" :key="query" class="control"> <div v-for="query in recent_searches" :key="query" class="control">
<div class="tags has-addons"> <div class="tags has-addons">
<a class="tag" @click="open_search(query)" v-text="query" /> <a class="tag" @click="open_search(query)" v-text="query" />
<a class="tag is-delete" @click="remove_search(query)"></a> <a class="tag is-delete" @click="remove_search(query)" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
<div v-for="query in recent_searches" :key="query" class="control"> <div v-for="query in recent_searches" :key="query" class="control">
<div class="tags has-addons"> <div class="tags has-addons">
<a class="tag" @click="open_search(query)" v-text="query" /> <a class="tag" @click="open_search(query)" v-text="query" />
<a class="tag is-delete" @click="remove_search(query)"></a> <a class="tag is-delete" @click="remove_search(query)" />
</div> </div>
</div> </div>
</div> </div>
@ -46,7 +46,9 @@
</div> </div>
</div> </div>
</template> </template>
<template #no-more>&nbsp;</template> <template #no-more>
<br />
</template>
</VueEternalLoading> </VueEternalLoading>
</template> </template>
<template v-if="!expanded" #footer> <template v-if="!expanded" #footer>

View File

@ -16,8 +16,8 @@
/> />
<settings-checkbox <settings-checkbox
v-if="spotify.spotify_logged_in" v-if="spotify.spotify_logged_in"
category_name="artwork" category="artwork"
option_name="use_artwork_source_spotify" name="use_artwork_source_spotify"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.artwork.spotify')" /> <span v-text="$t('page.settings.artwork.spotify')" />
@ -26,10 +26,7 @@
</a> </a>
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox category="artwork" name="use_artwork_source_discogs">
category_name="artwork"
option_name="use_artwork_source_discogs"
>
<template #label> <template #label>
<span v-text="$t('page.settings.artwork.discogs')" /> <span v-text="$t('page.settings.artwork.discogs')" />
<a href="https://www.discogs.com/" target="_blank"> <a href="https://www.discogs.com/" target="_blank">
@ -38,8 +35,8 @@
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="artwork" category="artwork"
option_name="use_artwork_source_coverartarchive" name="use_artwork_source_coverartarchive"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.artwork.coverartarchive')" /> <span v-text="$t('page.settings.artwork.coverartarchive')" />

View File

@ -26,57 +26,45 @@
v-text="$t('page.settings.general.navigation-item-selection-info')" v-text="$t('page.settings.general.navigation-item-selection-info')"
/> />
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_menu_item_playlists" name="show_menu_item_playlists"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.playlists')" /> <span v-text="$t('page.settings.general.playlists')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox category="webinterface" name="show_menu_item_music">
category_name="webinterface"
option_name="show_menu_item_music"
>
<template #label> <template #label>
<span v-text="$t('page.settings.general.music')" /> <span v-text="$t('page.settings.general.music')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_menu_item_podcasts" name="show_menu_item_podcasts"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.podcasts')" /> <span v-text="$t('page.settings.general.podcasts')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_menu_item_audiobooks" name="show_menu_item_audiobooks"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.audiobooks')" /> <span v-text="$t('page.settings.general.audiobooks')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox category="webinterface" name="show_menu_item_radio">
category_name="webinterface"
option_name="show_menu_item_radio"
>
<template #label> <template #label>
<span v-text="$t('page.settings.general.radio')" /> <span v-text="$t('page.settings.general.radio')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox category="webinterface" name="show_menu_item_files">
category_name="webinterface"
option_name="show_menu_item_files"
>
<template #label> <template #label>
<span v-text="$t('page.settings.general.files')" /> <span v-text="$t('page.settings.general.files')" />
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox category="webinterface" name="show_menu_item_search">
category_name="webinterface"
option_name="show_menu_item_search"
>
<template #label> <template #label>
<span v-text="$t('page.settings.general.search')" /> <span v-text="$t('page.settings.general.search')" />
</template> </template>
@ -92,8 +80,8 @@
</template> </template>
<template #content> <template #content>
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_cover_artwork_in_album_lists" name="show_cover_artwork_in_album_lists"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.show-coverart')" /> <span v-text="$t('page.settings.general.show-coverart')" />
@ -110,8 +98,8 @@
</template> </template>
<template #content> <template #content>
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_composer_now_playing" name="show_composer_now_playing"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.show-composer')" /> <span v-text="$t('page.settings.general.show-composer')" />
@ -121,9 +109,9 @@
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-textfield <settings-textfield
category_name="webinterface" category="webinterface"
option_name="show_composer_for_genre" name="show_composer_for_genre"
:disabled="!settings_option_show_composer_now_playing" :disabled="!setting_show_composer_now_playing"
:placeholder="$t('page.settings.general.genres')" :placeholder="$t('page.settings.general.genres')"
> >
<template #label> <template #label>
@ -145,8 +133,8 @@
</template> </template>
</settings-textfield> </settings-textfield>
<settings-checkbox <settings-checkbox
category_name="webinterface" category="webinterface"
option_name="show_filepath_now_playing" name="show_filepath_now_playing"
> >
<template #label> <template #label>
<span v-text="$t('page.settings.general.show-path')" /> <span v-text="$t('page.settings.general.show-path')" />
@ -162,10 +150,7 @@
/> />
</template> </template>
<template #content> <template #content>
<settings-intfield <settings-intfield category="webinterface" name="recently_added_limit">
category_name="webinterface"
option_name="recently_added_limit"
>
<template #label> <template #label>
<span <span
v-text="$t('page.settings.general.recently-added-page-info')" v-text="$t('page.settings.general.recently-added-page-info')"
@ -225,11 +210,11 @@ export default {
})) }))
} }
}, },
settings_option_show_composer_now_playing() { setting_show_composer_now_playing() {
return this.$store.getters.settings_option_show_composer_now_playing return this.$store.getters.setting_show_composer_now_playing
}, },
settings_option_show_filepath_now_playing() { setting_show_filepath_now_playing() {
return this.$store.getters.settings_option_show_filepath_now_playing return this.$store.getters.setting_show_filepath_now_playing
} }
} }
} }

View File

@ -300,16 +300,15 @@ export const router = createRouter({
} }
], ],
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
const wait_ms = 0 const delay = 0
if (savedPosition) { if (savedPosition) {
// Use the saved scroll position (browser back/forward navigation) // Use the saved scroll position (browser back/forward navigation)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
resolve(savedPosition) resolve(savedPosition)
}, wait_ms) }, delay)
}) })
} }
if (to.path === from.path && to.hash) { if (to.path === from.path && to.hash) {
/* /*
* Staying on the same page and jumping to an anchor (e. g. index nav) * Staying on the same page and jumping to an anchor (e. g. index nav)
@ -318,16 +317,14 @@ export const router = createRouter({
const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS
return { behavior: 'smooth', el: to.hash, top } return { behavior: 'smooth', el: to.hash, top }
} }
if (to.hash) { if (to.hash) {
// We are navigating to an anchor of a new page, add a timeout to let the transition effect finish before scrolling // We are navigating to an anchor of a new page, add a timeout to let the transition effect finish before scrolling
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
resolve({ el: to.hash, top: 120 }) resolve({ el: to.hash, top: 120 })
}, wait_ms) }, delay)
}) })
} }
if (to.meta.has_index) { if (to.meta.has_index) {
/* /*
* Navigate to a page with index nav that should be hidden automatically * Navigate to a page with index nav that should be hidden automatically
@ -337,7 +334,7 @@ export const router = createRouter({
const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS
setTimeout(() => { setTimeout(() => {
resolve({ el: '#top', top }) resolve({ el: '#top', top })
}, wait_ms) }, delay)
}) })
} }

View File

@ -70,24 +70,24 @@ export default createStore({
getters: { getters: {
now_playing: (state) => now_playing: (state) =>
state.queue.items.find((e) => e.id === state.player.item_id) ?? {}, state.queue.items.find((item) => item.id === state.player.item_id) ?? {},
settings_option: (state) => (categoryName, optionName) => setting: (state) => (categoryName, optionName) =>
state.settings.categories state.settings.categories
.find((category) => category.name === categoryName) .find((category) => category.name === categoryName)
?.options.find((option) => option.name === optionName) ?? {}, ?.options.find((option) => option.name === optionName) ?? {},
settings_option_recently_added_limit: (state, getters) => setting_recently_added_limit: (state, getters) =>
getters.settings_webinterface?.options.find( getters.settings_webinterface?.options.find(
(option) => option.name === 'recently_added_limit' (option) => option.name === 'recently_added_limit'
)?.value ?? 100, )?.value ?? 100,
settings_option_show_composer_for_genre: (state, getters) => setting_show_composer_for_genre: (state, getters) =>
getters.settings_webinterface?.options.find( getters.settings_webinterface?.options.find(
(option) => option.name === 'show_composer_for_genre' (option) => option.name === 'show_composer_for_genre'
)?.value ?? null, )?.value ?? null,
settings_option_show_composer_now_playing: (state, getters) => setting_show_composer_now_playing: (state, getters) =>
getters.settings_webinterface?.options.find( getters.settings_webinterface?.options.find(
(option) => option.name === 'show_composer_now_playing' (option) => option.name === 'show_composer_now_playing'
)?.value ?? false, )?.value ?? false,
settings_option_show_filepath_now_playing: (state, getters) => setting_show_filepath_now_playing: (state, getters) =>
getters.settings_webinterface?.options.find( getters.settings_webinterface?.options.find(
(option) => option.name === 'show_filepath_now_playing' (option) => option.name === 'show_filepath_now_playing'
)?.value ?? false, )?.value ?? false,
@ -179,7 +179,7 @@ export default createStore({
}, },
actions: { actions: {
add_notification({ commit, state }, notification) { add_notification({ state }, notification) {
const newNotification = { const newNotification = {
id: state.notifications.next_id++, id: state.notifications.next_id++,
text: notification.text, text: notification.text,
@ -203,7 +203,7 @@ export default createStore({
}, notification.timeout) }, notification.timeout)
} }
}, },
add_recent_search({ commit, state }, query) { add_recent_search({ state }, query) {
const index = state.recent_searches.indexOf(query) const index = state.recent_searches.indexOf(query)
if (index !== -1) { if (index !== -1) {
state.recent_searches.splice(index, 1) state.recent_searches.splice(index, 1)
@ -213,24 +213,24 @@ export default createStore({
state.recent_searches.pop() state.recent_searches.pop()
} }
}, },
delete_notification({ commit, state }, notification) { delete_notification({ state }, notification) {
const index = state.notifications.list.indexOf(notification) const index = state.notifications.list.indexOf(notification)
if (index !== -1) { if (index !== -1) {
state.notifications.list.splice(index, 1) state.notifications.list.splice(index, 1)
} }
}, },
remove_recent_search({ commit, state }, query) { remove_recent_search({ state }, query) {
const index = state.recent_searches.indexOf(query) const index = state.recent_searches.indexOf(query)
if (index !== -1) { if (index !== -1) {
state.recent_searches.splice(index, 1) state.recent_searches.splice(index, 1)
} }
}, },
update_settings_option({ commit, state }, option) { update_setting({ state }, option) {
const settingCategory = state.settings.categories.find( const settingCategory = state.settings.categories.find(
(e) => e.name === option.category (category) => category.name === option.category
), ),
settingOption = settingCategory.options.find( settingOption = settingCategory.options.find(
(e) => e.name === option.name (setting) => setting.name === option.name
) )
settingOption.value = option.value settingOption.value = option.value
} }

View File

@ -4,7 +4,7 @@
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<section v-if="$slots.options"> <section v-if="$slots.options">
<div ref="options_ref" style="height: 1px" /> <div ref="options" style="height: 1px" />
<slot name="options" /> <slot name="options" />
<nav class="buttons is-centered mt-4 mb-2"> <nav class="buttons is-centered mt-4 mb-2">
<router-link class="button is-small is-white" :to="position"> <router-link class="button is-small is-white" :to="position">
@ -65,12 +65,12 @@ export default {
rootMargin: '-82px 0px 0px 0px', rootMargin: '-82px 0px 0px 0px',
threshold: 1.0 threshold: 1.0
}) })
this.observer.observe(this.$refs.options_ref) this.observer.observe(this.$refs.options)
} }
}, },
methods: { methods: {
onElementObserved(entries) { onElementObserved(entries) {
entries.forEach(({ target, isIntersecting }) => { entries.forEach(({ isIntersecting }) => {
this.options_visible = isIntersecting this.options_visible = isIntersecting
}) })
}, },

View File

@ -9,37 +9,31 @@ import vue from '@vitejs/plugin-vue'
* *
* export VITE_OWNTONE_URL=http://owntone.local:3689; npm run serve * export VITE_OWNTONE_URL=http://owntone.local:3689; npm run serve
*/ */
const owntoneUrl = process.env.VITE_OWNTONE_URL ?? 'http://localhost:3689' const target = process.env.VITE_OWNTONE_URL ?? 'http://localhost:3689'
export default defineConfig({ export default defineConfig({
resolve: { alias: { '@': '/src' } }, build: {
outDir: '../htdocs',
rollupOptions: {
output: {
assetFileNames: `assets/[name].[ext]`,
chunkFileNames: `assets/[name].js`,
entryFileNames: `assets/[name].js`
}
}
},
plugins: [ plugins: [
vue(), vue(),
i18n({ i18n({
include: path.resolve(__dirname, './src/i18n/**.json') include: path.resolve(__dirname, './src/i18n/**.json')
}) })
], ],
build: { resolve: { alias: { '@': '/src' } },
outDir: '../htdocs',
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
}
},
server: { server: {
proxy: { proxy: {
'/api': { '/api': { target },
target: owntoneUrl '/artwork': { target },
}, '/stream.mp3': { target }
'/artwork': {
target: owntoneUrl
},
'/stream.mp3': {
target: owntoneUrl
}
} }
} }
}) })