mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 07:05:57 -05:00
[web] Merge branch 'linting'
This commit is contained in:
commit
408057a45d
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -74,8 +74,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
disabled: true,
|
||||
playlist_name: '',
|
||||
loading: false
|
||||
loading: false,
|
||||
playlist_name: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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(() => {
|
||||
|
@ -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(() => {
|
||||
|
@ -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(() => {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -32,7 +32,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #no-more> </template>
|
||||
<template #no-more>
|
||||
<br />
|
||||
</template>
|
||||
</VueEternalLoading>
|
||||
<modal-dialog-artist-spotify
|
||||
:item="artist"
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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: {}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -34,7 +34,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #no-more> </template>
|
||||
<template #no-more>
|
||||
<br />
|
||||
</template>
|
||||
</VueEternalLoading>
|
||||
<modal-dialog-playlist-spotify
|
||||
:item="playlist"
|
||||
|
@ -3,7 +3,9 @@
|
||||
<content-with-hero>
|
||||
<template #heading-left>
|
||||
<h1 class="title is-5" v-text="album.name" />
|
||||
<h2 class="subtitle is-6"> </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" />
|
||||
|
@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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> </template>
|
||||
<template #no-more>
|
||||
<br />
|
||||
</template>
|
||||
</VueEternalLoading>
|
||||
</template>
|
||||
<template v-if="!expanded" #footer>
|
||||
|
@ -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')" />
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
},
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user