[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',
'no-bitwise': 'off',
'no-magic-numbers': 'off',
'no-negated-condition': 'off',
'no-nested-ternary': 'off',
'no-plusplus': 'off',
'no-shadow': 'off',
'no-ternary': 'off',
'no-undef': 'off',
'no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }],
'no-useless-assignment': 'off',
'one-var': 'off',
'prefer-named-capture-group': 'off',
'sort-keys': 'off',
'vue/html-self-closing': 'off',
'vue/max-attributes-per-line': 'off',
'vue/prop-name-casing': 'off',
'vue/singleline-html-element-content-newline': 'off'
'vue/prop-name-casing': 'off'
}
}
]

View File

@ -265,17 +265,6 @@ export default {
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() {
webapi.lastfm().then(({ data }) => {
this.$store.commit(types.UPDATE_LASTFM, data)
@ -299,12 +288,23 @@ export default {
this.$store.commit(types.UPDATE_LYRICS)
}
},
update_outputs() {
webapi.outputs().then(({ data }) => {
this.$store.commit(types.UPDATE_OUTPUTS, data.outputs)
})
},
update_pairing() {
webapi.pairing().then(({ data }) => {
this.$store.commit(types.UPDATE_PAIRING, data)
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() {
webapi.queue().then(({ 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
},
show_artwork() {
return this.$store.getters.settings_option(
return this.$store.getters.setting(
'webinterface',
'show_cover_artwork_in_album_lists'
).value
@ -122,10 +122,10 @@ export default {
open_remove_podcast_dialog() {
webapi
.library_album_tracks(this.selected_item.id, { limit: 1 })
.then(({ data }) => {
webapi.library_track_playlists(data.items[0].id).then(({ data }) => {
.then(({ data: album }) => {
webapi.library_track_playlists(album.items[0].id).then(({ data }) => {
;[this.rss_playlist_to_remove] = data.items.filter(
(pl) => pl.type === 'rss'
(playlist) => playlist.type === 'rss'
)
this.show_remove_podcast_modal = true
this.show_details_modal = false

View File

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

View File

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

View File

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

View File

@ -103,7 +103,7 @@
</div>
</div>
<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" />
</navbar-item-link>
<navbar-item-link

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,9 @@
<content-with-hero>
<template #heading-left>
<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">
<a class="button is-small is-dark is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" />

View File

@ -1,6 +1,6 @@
<template>
<div>
<content-with-heading v-if="new_episodes.items.length > 0">
<content-with-heading v-if="tracks.items.length > 0">
<template #heading-left>
<p class="title is-4" v-text="$t('page.podcasts.new-episodes')" />
</template>
@ -14,7 +14,7 @@
</template>
<template #content>
<list-tracks
:items="new_episodes"
:items="tracks"
:show_progress="true"
@play-count-changed="reload_new_episodes"
/>
@ -75,7 +75,7 @@ const dataObject = {
set(vm, response) {
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() {
return {
albums: [],
new_episodes: { items: [] },
tracks: { items: [] },
show_url_modal: false
}
},
@ -111,10 +110,10 @@ export default {
methods: {
mark_all_played() {
this.new_episodes.items.forEach((ep) => {
this.tracks.items.forEach((ep) => {
webapi.library_track_update(ep.id, { play_count: 'increment' })
})
this.new_episodes.items = {}
this.tracks.items = {}
},
open_add_podcast_dialog() {
@ -123,7 +122,7 @@ export default {
reload_new_episodes() {
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() {
return this.$store.state.queue.items
},
set(value) {
set() {
/* Do nothing? Send move request in @end event */
}
},
@ -163,11 +163,11 @@ export default {
},
methods: {
move_item(e) {
move_item(event) {
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 newPosition = item.position + (e.newIndex - e.oldIndex)
const newPosition = item.position + (event.newIndex - event.oldIndex)
if (newPosition !== oldPosition) {
webapi.queue_move(item.id, newPosition)
}

View File

@ -22,7 +22,9 @@
keypath="page.search.help"
scope="global"
>
<template #query><code>query:</code></template>
<template #query>
<code>query:</code>
</template>
<template #help>
<a
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 class="tags has-addons">
<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>

View File

@ -22,7 +22,7 @@
<div v-for="query in recent_searches" :key="query" class="control">
<div class="tags has-addons">
<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>
@ -46,7 +46,9 @@
</div>
</div>
</template>
<template #no-more>&nbsp;</template>
<template #no-more>
<br />
</template>
</VueEternalLoading>
</template>
<template v-if="!expanded" #footer>

View File

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

View File

@ -26,57 +26,45 @@
v-text="$t('page.settings.general.navigation-item-selection-info')"
/>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_playlists"
category="webinterface"
name="show_menu_item_playlists"
>
<template #label>
<span v-text="$t('page.settings.general.playlists')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_music"
>
<settings-checkbox category="webinterface" name="show_menu_item_music">
<template #label>
<span v-text="$t('page.settings.general.music')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_podcasts"
category="webinterface"
name="show_menu_item_podcasts"
>
<template #label>
<span v-text="$t('page.settings.general.podcasts')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_audiobooks"
category="webinterface"
name="show_menu_item_audiobooks"
>
<template #label>
<span v-text="$t('page.settings.general.audiobooks')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_radio"
>
<settings-checkbox category="webinterface" name="show_menu_item_radio">
<template #label>
<span v-text="$t('page.settings.general.radio')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_files"
>
<settings-checkbox category="webinterface" name="show_menu_item_files">
<template #label>
<span v-text="$t('page.settings.general.files')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_search"
>
<settings-checkbox category="webinterface" name="show_menu_item_search">
<template #label>
<span v-text="$t('page.settings.general.search')" />
</template>
@ -92,8 +80,8 @@
</template>
<template #content>
<settings-checkbox
category_name="webinterface"
option_name="show_cover_artwork_in_album_lists"
category="webinterface"
name="show_cover_artwork_in_album_lists"
>
<template #label>
<span v-text="$t('page.settings.general.show-coverart')" />
@ -110,8 +98,8 @@
</template>
<template #content>
<settings-checkbox
category_name="webinterface"
option_name="show_composer_now_playing"
category="webinterface"
name="show_composer_now_playing"
>
<template #label>
<span v-text="$t('page.settings.general.show-composer')" />
@ -121,9 +109,9 @@
</template>
</settings-checkbox>
<settings-textfield
category_name="webinterface"
option_name="show_composer_for_genre"
:disabled="!settings_option_show_composer_now_playing"
category="webinterface"
name="show_composer_for_genre"
:disabled="!setting_show_composer_now_playing"
:placeholder="$t('page.settings.general.genres')"
>
<template #label>
@ -145,8 +133,8 @@
</template>
</settings-textfield>
<settings-checkbox
category_name="webinterface"
option_name="show_filepath_now_playing"
category="webinterface"
name="show_filepath_now_playing"
>
<template #label>
<span v-text="$t('page.settings.general.show-path')" />
@ -162,10 +150,7 @@
/>
</template>
<template #content>
<settings-intfield
category_name="webinterface"
option_name="recently_added_limit"
>
<settings-intfield category="webinterface" name="recently_added_limit">
<template #label>
<span
v-text="$t('page.settings.general.recently-added-page-info')"
@ -225,11 +210,11 @@ export default {
}))
}
},
settings_option_show_composer_now_playing() {
return this.$store.getters.settings_option_show_composer_now_playing
setting_show_composer_now_playing() {
return this.$store.getters.setting_show_composer_now_playing
},
settings_option_show_filepath_now_playing() {
return this.$store.getters.settings_option_show_filepath_now_playing
setting_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) {
const wait_ms = 0
const delay = 0
if (savedPosition) {
// Use the saved scroll position (browser back/forward navigation)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(savedPosition)
}, wait_ms)
}, delay)
})
}
if (to.path === from.path && to.hash) {
/*
* 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
return { behavior: 'smooth', el: to.hash, top }
}
if (to.hash) {
// 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) => {
setTimeout(() => {
resolve({ el: to.hash, top: 120 })
}, wait_ms)
}, delay)
})
}
if (to.meta.has_index) {
/*
* 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
setTimeout(() => {
resolve({ el: '#top', top })
}, wait_ms)
}, delay)
})
}

View File

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

View File

@ -4,7 +4,7 @@
<div class="columns is-centered">
<div class="column is-four-fifths">
<section v-if="$slots.options">
<div ref="options_ref" style="height: 1px" />
<div ref="options" style="height: 1px" />
<slot name="options" />
<nav class="buttons is-centered mt-4 mb-2">
<router-link class="button is-small is-white" :to="position">
@ -65,12 +65,12 @@ export default {
rootMargin: '-82px 0px 0px 0px',
threshold: 1.0
})
this.observer.observe(this.$refs.options_ref)
this.observer.observe(this.$refs.options)
}
},
methods: {
onElementObserved(entries) {
entries.forEach(({ target, isIntersecting }) => {
entries.forEach(({ 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
*/
const owntoneUrl = process.env.VITE_OWNTONE_URL ?? 'http://localhost:3689'
const target = process.env.VITE_OWNTONE_URL ?? 'http://localhost:3689'
export default defineConfig({
resolve: { alias: { '@': '/src' } },
build: {
outDir: '../htdocs',
rollupOptions: {
output: {
assetFileNames: `assets/[name].[ext]`,
chunkFileNames: `assets/[name].js`,
entryFileNames: `assets/[name].js`
}
}
},
plugins: [
vue(),
i18n({
include: path.resolve(__dirname, './src/i18n/**.json')
})
],
build: {
outDir: '../htdocs',
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
}
},
resolve: { alias: { '@': '/src' } },
server: {
proxy: {
'/api': {
target: owntoneUrl
},
'/artwork': {
target: owntoneUrl
},
'/stream.mp3': {
target: owntoneUrl
}
'/api': { target },
'/artwork': { target },
'/stream.mp3': { target }
}
}
})