mirror of
https://github.com/owntone/owntone-server.git
synced 2025-11-11 14:30:20 -05:00
[web] Format web sources with prettier and run fix linting errors
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths has-text-centered-mobile">
|
||||
<p class="heading"><b>OwnTone</b> - version {{ config.version }}</p>
|
||||
<h1 class="title is-4">{{ config.library_name }}</h1>
|
||||
<h1 class="title is-4">
|
||||
{{ config.library_name }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,8 +27,14 @@
|
||||
|
||||
<!-- Right side -->
|
||||
<div class="level-right">
|
||||
<div v-if="library.updating"><a class="button is-small is-loading">Update</a></div>
|
||||
<div v-else><a @click="showUpdateDialog()" class="button is-small">Update</a></div>
|
||||
<div v-if="library.updating">
|
||||
<a class="button is-small is-loading">Update</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a class="button is-small" @click="showUpdateDialog()"
|
||||
>Update</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -34,27 +42,50 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Artists</th>
|
||||
<td class="has-text-right">{{ $filters.number(library.artists) }}</td>
|
||||
<td class="has-text-right">
|
||||
{{ $filters.number(library.artists) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Albums</th>
|
||||
<td class="has-text-right">{{ $filters.number(library.albums) }}</td>
|
||||
<td class="has-text-right">
|
||||
{{ $filters.number(library.albums) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tracks</th>
|
||||
<td class="has-text-right">{{ $filters.number(library.songs) }}</td>
|
||||
<td class="has-text-right">
|
||||
{{ $filters.number(library.songs) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total playtime</th>
|
||||
<td class="has-text-right">{{ $filters.duration(library.db_playtime * 1000, 'y [years], d [days], h [hours], m [minutes]') }}</td>
|
||||
<td class="has-text-right">
|
||||
{{
|
||||
$filters.duration(
|
||||
library.db_playtime * 1000,
|
||||
'y [years], d [days], h [hours], m [minutes]'
|
||||
)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Library updated</th>
|
||||
<td class="has-text-right">{{ $filters.timeFromNow(library.updated_at) }} <span class="has-text-grey">({{ $filters.time(library.updated_at, 'lll') }})</span></td>
|
||||
<td class="has-text-right">
|
||||
{{ $filters.timeFromNow(library.updated_at) }}
|
||||
<span class="has-text-grey"
|
||||
>({{ $filters.time(library.updated_at, 'lll') }})</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Uptime</th>
|
||||
<td class="has-text-right">{{ $filters.timeFromNow(library.started_at, true) }} <span class="has-text-grey">({{ $filters.time(library.started_at, 'll') }})</span></td>
|
||||
<td class="has-text-right">
|
||||
{{ $filters.timeFromNow(library.started_at, true) }}
|
||||
<span class="has-text-grey"
|
||||
>({{ $filters.time(library.started_at, 'll') }})</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -68,8 +99,20 @@
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths">
|
||||
<div class="content has-text-centered-mobile">
|
||||
<p class="is-size-7">Compiled with support for {{ config.buildoptions.join(', ') }}.</p>
|
||||
<p class="is-size-7">Web interface built with <a href="http://bulma.io">Bulma</a>, <a href="https://materialdesignicons.com/">Material Design Icons</a>, <a href="https://vuejs.org/">Vue.js</a>, <a href="https://github.com/mzabriskie/axios">axios</a> and <a href="https://github.com/owntone/owntone-server/network/dependencies">more</a>.</p>
|
||||
<p class="is-size-7">
|
||||
Compiled with support for {{ config.buildoptions.join(', ') }}.
|
||||
</p>
|
||||
<p class="is-size-7">
|
||||
Web interface built with <a href="http://bulma.io">Bulma</a>,
|
||||
<a href="https://materialdesignicons.com/"
|
||||
>Material Design Icons</a
|
||||
>, <a href="https://vuejs.org/">Vue.js</a>,
|
||||
<a href="https://github.com/mzabriskie/axios">axios</a> and
|
||||
<a
|
||||
href="https://github.com/owntone/owntone-server/network/dependencies"
|
||||
>more</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +127,7 @@ import * as types from '@/store/mutation_types'
|
||||
export default {
|
||||
name: 'PageAbout',
|
||||
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
show_update_dropdown: false,
|
||||
show_update_library: false
|
||||
@@ -92,24 +135,23 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
config () {
|
||||
config() {
|
||||
return this.$store.state.config
|
||||
},
|
||||
library () {
|
||||
library() {
|
||||
return this.$store.state.library
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClickOutside (event) {
|
||||
onClickOutside(event) {
|
||||
this.show_update_dropdown = false
|
||||
},
|
||||
showUpdateDialog () {
|
||||
showUpdateDialog() {
|
||||
this.$store.commit(types.SHOW_UPDATE_DIALOG, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,31 +1,48 @@
|
||||
<template>
|
||||
<content-with-hero>
|
||||
<template v-slot:heading-left>
|
||||
<h1 class="title is-5">{{ album.name }}</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"><a class="has-text-link" @click="open_artist">{{ album.artist }}</a></h2>
|
||||
<template #heading-left>
|
||||
<h1 class="title is-5">
|
||||
{{ album.name }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
|
||||
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a>
|
||||
</h2>
|
||||
|
||||
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
<a class="button is-small is-light is-rounded" @click="show_album_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_album_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<p class="image is-square fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="album.artwork_url"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
@click="show_album_details_modal = true" />
|
||||
@click="show_album_details_modal = true"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">{{ album.track_count }} tracks</p>
|
||||
<list-tracks :tracks="tracks" :uris="album.uri"></list-tracks>
|
||||
<modal-dialog-album :show="show_album_details_modal" :album="album" @close="show_album_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
|
||||
{{ album.track_count }} tracks
|
||||
</p>
|
||||
<list-tracks :tracks="tracks" :uris="album.uri" />
|
||||
<modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="album"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-hero>
|
||||
</template>
|
||||
@@ -55,7 +72,20 @@ export default {
|
||||
name: 'PageAlbum',
|
||||
components: { ContentWithHero, ListTracks, ModalDialogAlbum, CoverArtwork },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
album: {},
|
||||
tracks: [],
|
||||
@@ -73,22 +103,8 @@ export default {
|
||||
play: function () {
|
||||
webapi.player_play_uri(this.album.uri, true)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,43 +1,60 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="albums_list.indexList"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="albums_list.indexList" />
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px;">Filter</p>
|
||||
<p class="heading" style="margin-bottom: 24px">Filter</p>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input id="switchHideSingles" type="checkbox" name="switchHideSingles" class="switch" v-model="hide_singles">
|
||||
<input
|
||||
id="switchHideSingles"
|
||||
v-model="hide_singles"
|
||||
type="checkbox"
|
||||
name="switchHideSingles"
|
||||
class="switch"
|
||||
/>
|
||||
<label for="switchHideSingles">Hide singles</label>
|
||||
</div>
|
||||
<p class="help">If active, hides singles and albums with tracks that only appear in playlists.</p>
|
||||
<p class="help">
|
||||
If active, hides singles and albums with tracks that only appear
|
||||
in playlists.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="spotify_enabled">
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<div class="control">
|
||||
<input id="switchHideSpotify" type="checkbox" name="switchHideSpotify" class="switch" v-model="hide_spotify">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
name="switchHideSpotify"
|
||||
class="switch"
|
||||
/>
|
||||
<label for="switchHideSpotify">Hide albums from Spotify</label>
|
||||
</div>
|
||||
<p class="help">If active, hides albums that only appear in your Spotify library.</p>
|
||||
<p class="help">
|
||||
If active, hides albums that only appear in your Spotify
|
||||
library.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px;">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options"></dropdown-menu>
|
||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Albums</p>
|
||||
<p class="heading">{{ albums_list.sortedAndFiltered.length }} Albums</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="albums_list"></list-albums>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-albums :albums="albums_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -60,17 +77,46 @@ const dataObject = {
|
||||
|
||||
set: function (vm, response) {
|
||||
vm.albums = response.data
|
||||
vm.index_list = [...new Set(vm.albums.items
|
||||
.filter(album => !vm.$store.state.hide_singles || album.track_count > 2)
|
||||
.map(album => album.name_sort.charAt(0).toUpperCase()))]
|
||||
vm.index_list = [
|
||||
...new Set(
|
||||
vm.albums.items
|
||||
.filter(
|
||||
(album) => !vm.$store.state.hide_singles || album.track_count > 2
|
||||
)
|
||||
.map((album) => album.name_sort.charAt(0).toUpperCase())
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'PageAlbums',
|
||||
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListAlbums, DropdownMenu },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
IndexButtonList,
|
||||
ListAlbums,
|
||||
DropdownMenu
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
if (this.albums.items.length > 0) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
albums: { items: [] },
|
||||
sort_options: ['Name', 'Recently added', 'Recently released']
|
||||
@@ -78,7 +124,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
albums_list () {
|
||||
albums_list() {
|
||||
return new Albums(this.albums.items, {
|
||||
hideSingles: this.hide_singles,
|
||||
hideSpotify: this.hide_spotify,
|
||||
@@ -87,33 +133,33 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
spotify_enabled () {
|
||||
spotify_enabled() {
|
||||
return this.$store.state.spotify.webapi_token_valid
|
||||
},
|
||||
|
||||
hide_singles: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.hide_singles
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.HIDE_SINGLES, value)
|
||||
}
|
||||
},
|
||||
|
||||
hide_spotify: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.hide_spotify
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.HIDE_SPOTIFY, value)
|
||||
}
|
||||
},
|
||||
|
||||
sort: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.albums_sort
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.ALBUMS_SORT, value)
|
||||
}
|
||||
}
|
||||
@@ -123,26 +169,8 @@ export default {
|
||||
scrollToTop: function () {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
if (this.albums.items.length > 0) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,30 +1,47 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px;">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options"></dropdown-menu>
|
||||
</div>
|
||||
<template #options>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ artist.name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ artist.name }}
|
||||
</p>
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_artist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_artist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ artist.album_count }} albums | <a class="has-text-link" @click="open_tracks">{{ artist.track_count }} tracks</a></p>
|
||||
<list-albums :albums="albums_list"></list-albums>
|
||||
<modal-dialog-artist :show="show_artist_details_modal" :artist="artist" @close="show_artist_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ artist.album_count }} albums |
|
||||
<a class="has-text-link" @click="open_tracks"
|
||||
>{{ artist.track_count }} tracks</a
|
||||
>
|
||||
</p>
|
||||
<list-albums :albums="albums_list" />
|
||||
<modal-dialog-artist
|
||||
:show="show_artist_details_modal"
|
||||
:artist="artist"
|
||||
@close="show_artist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -54,9 +71,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageArtist',
|
||||
components: { ContentWithHeading, ListAlbums, ModalDialogArtist, DropdownMenu },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListAlbums,
|
||||
ModalDialogArtist,
|
||||
DropdownMenu
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artist: {},
|
||||
albums: { items: [] },
|
||||
@@ -67,7 +102,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
albums_list () {
|
||||
albums_list() {
|
||||
return new Albums(this.albums.items, {
|
||||
sort: this.sort,
|
||||
group: false
|
||||
@@ -75,10 +110,10 @@ export default {
|
||||
},
|
||||
|
||||
sort: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.artist_albums_sort
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.ARTIST_ALBUMS_SORT, value)
|
||||
}
|
||||
}
|
||||
@@ -86,28 +121,19 @@ export default {
|
||||
|
||||
methods: {
|
||||
open_tracks: function () {
|
||||
this.$router.push({ path: '/music/artists/' + this.artist.id + '/tracks' })
|
||||
this.$router.push({
|
||||
path: '/music/artists/' + this.artist.id + '/tracks'
|
||||
})
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_uri(this.albums.items.map(a => a.uri).join(','), true)
|
||||
webapi.player_play_uri(
|
||||
this.albums.items.map((a) => a.uri).join(','),
|
||||
true
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ artist.name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ artist.name }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_artist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_artist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile"><a class="has-text-link" @click="open_artist">{{ artist.album_count }} albums</a> | {{ artist.track_count }} tracks</p>
|
||||
<list-tracks :tracks="tracks.items" :uris="track_uris"></list-tracks>
|
||||
<modal-dialog-artist :show="show_artist_details_modal" :artist="artist" @close="show_artist_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a class="has-text-link" @click="open_artist"
|
||||
>{{ artist.album_count }} albums</a
|
||||
>
|
||||
| {{ artist.track_count }} tracks
|
||||
</p>
|
||||
<list-tracks :tracks="tracks.items" :uris="track_uris" />
|
||||
<modal-dialog-artist
|
||||
:show="show_artist_details_modal"
|
||||
:artist="artist"
|
||||
@close="show_artist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -49,9 +66,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageArtistTracks',
|
||||
components: { ContentWithHeading, ListTracks, IndexButtonList, ModalDialogArtist },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListTracks,
|
||||
IndexButtonList,
|
||||
ModalDialogArtist
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artist: {},
|
||||
tracks: { items: [] },
|
||||
@@ -61,13 +96,18 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.tracks.items
|
||||
.map(track => track.title_sort.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.tracks.items.map((track) =>
|
||||
track.title_sort.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
track_uris () {
|
||||
return this.tracks.items.map(a => a.uri).join(',')
|
||||
track_uris() {
|
||||
return this.tracks.items.map((a) => a.uri).join(',')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -78,24 +118,13 @@ export default {
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_uri(this.tracks.items.map(a => a.uri).join(','), true)
|
||||
webapi.player_play_uri(
|
||||
this.tracks.items.map((a) => a.uri).join(','),
|
||||
true
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,43 +1,62 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="artists_list.indexList"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="artists_list.indexList" />
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px;">Filter</p>
|
||||
<p class="heading" style="margin-bottom: 24px">Filter</p>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input id="switchHideSingles" type="checkbox" name="switchHideSingles" class="switch" v-model="hide_singles">
|
||||
<input
|
||||
id="switchHideSingles"
|
||||
v-model="hide_singles"
|
||||
type="checkbox"
|
||||
name="switchHideSingles"
|
||||
class="switch"
|
||||
/>
|
||||
<label for="switchHideSingles">Hide singles</label>
|
||||
</div>
|
||||
<p class="help">If active, hides artists that only appear on singles or playlists.</p>
|
||||
<p class="help">
|
||||
If active, hides artists that only appear on singles or
|
||||
playlists.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="spotify_enabled">
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<div class="control">
|
||||
<input id="switchHideSpotify" type="checkbox" name="switchHideSpotify" class="switch" v-model="hide_spotify">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
name="switchHideSpotify"
|
||||
class="switch"
|
||||
/>
|
||||
<label for="switchHideSpotify">Hide artists from Spotify</label>
|
||||
</div>
|
||||
<p class="help">If active, hides artists that only appear in your Spotify library.</p>
|
||||
<p class="help">
|
||||
If active, hides artists that only appear in your Spotify
|
||||
library.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<p class="heading" style="margin-bottom: 24px;">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options"></dropdown-menu>
|
||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Artists</p>
|
||||
<p class="heading">{{ artists_list.sortedAndFiltered.length }} Artists</p>
|
||||
<p class="heading">
|
||||
{{ artists_list.sortedAndFiltered.length }} Artists
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-artists :artists="artists_list"></list-artists>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-artists :artists="artists_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -65,9 +84,32 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageArtists',
|
||||
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListArtists, DropdownMenu },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
IndexButtonList,
|
||||
ListArtists,
|
||||
DropdownMenu
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
if (this.artists.items.length > 0) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artists: { items: [] },
|
||||
sort_options: ['Name', 'Recently added']
|
||||
@@ -75,7 +117,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
artists_list () {
|
||||
artists_list() {
|
||||
return new Artists(this.artists.items, {
|
||||
hideSingles: this.hide_singles,
|
||||
hideSpotify: this.hide_spotify,
|
||||
@@ -84,33 +126,33 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
spotify_enabled () {
|
||||
spotify_enabled() {
|
||||
return this.$store.state.spotify.webapi_token_valid
|
||||
},
|
||||
|
||||
hide_singles: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.hide_singles
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.HIDE_SINGLES, value)
|
||||
}
|
||||
},
|
||||
|
||||
hide_spotify: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.hide_spotify
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.HIDE_SPOTIFY, value)
|
||||
}
|
||||
},
|
||||
|
||||
sort: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.artists_sort
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$store.commit(types.ARTISTS_SORT, value)
|
||||
}
|
||||
}
|
||||
@@ -120,26 +162,8 @@ export default {
|
||||
scrollToTop: function () {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
if (this.artists.items.length > 0) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,31 +1,49 @@
|
||||
<template>
|
||||
<content-with-hero>
|
||||
<template v-slot:heading-left>
|
||||
<h1 class="title is-5">{{ album.name }}</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"><a class="has-text-link" @click="open_artist">{{ album.artist }}</a></h2>
|
||||
<template #heading-left>
|
||||
<h1 class="title is-5">
|
||||
{{ album.name }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
|
||||
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a>
|
||||
</h2>
|
||||
|
||||
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-play"></i></span> <span>Play</span>
|
||||
<span class="icon"><i class="mdi mdi-play" /></span>
|
||||
<span>Play</span>
|
||||
</a>
|
||||
<a class="button is-small is-light is-rounded" @click="show_album_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_album_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<p class="image is-square fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="album.artwork_url"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
@click="show_album_details_modal = true" />
|
||||
@click="show_album_details_modal = true"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">{{ album.track_count }} tracks</p>
|
||||
<list-tracks :tracks="tracks" :uris="album.uri"></list-tracks>
|
||||
<modal-dialog-album :show="show_album_details_modal" :album="album" :media_kind="'audiobook'" @close="show_album_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
|
||||
{{ album.track_count }} tracks
|
||||
</p>
|
||||
<list-tracks :tracks="tracks" :uris="album.uri" />
|
||||
<modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="album"
|
||||
:media_kind="'audiobook'"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-hero>
|
||||
</template>
|
||||
@@ -55,7 +73,20 @@ export default {
|
||||
name: 'PageAudiobooksAlbum',
|
||||
components: { ContentWithHero, ListTracks, ModalDialogAlbum, CoverArtwork },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
album: {},
|
||||
tracks: [],
|
||||
@@ -82,22 +113,8 @@ export default {
|
||||
this.selected_track = track
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-audiobooks></tabs-audiobooks>
|
||||
<tabs-audiobooks />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="albums_list.indexList"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="albums_list.indexList" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Audiobooks</p>
|
||||
<p class="heading">{{ albums_list.sortedAndFiltered.length }} Audiobooks</p>
|
||||
<p class="heading">
|
||||
{{ albums_list.sortedAndFiltered.length }} Audiobooks
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="albums_list"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="albums_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -37,16 +39,34 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageAudiobooksAlbums',
|
||||
components: { TabsAudiobooks, ContentWithHeading, IndexButtonList, ListAlbums },
|
||||
components: {
|
||||
TabsAudiobooks,
|
||||
ContentWithHeading,
|
||||
IndexButtonList,
|
||||
ListAlbums
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
albums: { items: [] }
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
albums_list () {
|
||||
albums_list() {
|
||||
return new Albums(this.albums.items, {
|
||||
sort: 'Name',
|
||||
group: true
|
||||
@@ -54,23 +74,8 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ artist.name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ artist.name }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_artist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_artist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-play"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-play" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ artist.album_count }} albums</p>
|
||||
<list-albums :albums="albums.items"></list-albums>
|
||||
<modal-dialog-artist :show="show_artist_details_modal" :artist="artist" @close="show_artist_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ artist.album_count }} albums
|
||||
</p>
|
||||
<list-albums :albums="albums.items" />
|
||||
<modal-dialog-artist
|
||||
:show="show_artist_details_modal"
|
||||
:artist="artist"
|
||||
@close="show_artist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -45,7 +59,20 @@ export default {
|
||||
name: 'PageAudiobooksArtist',
|
||||
components: { ContentWithHeading, ListAlbums, ModalDialogArtist },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artist: {},
|
||||
albums: {},
|
||||
@@ -56,24 +83,13 @@ export default {
|
||||
|
||||
methods: {
|
||||
play: function () {
|
||||
webapi.player_play_uri(this.albums.items.map(a => a.uri).join(','), false)
|
||||
webapi.player_play_uri(
|
||||
this.albums.items.map((a) => a.uri).join(','),
|
||||
false
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-audiobooks></tabs-audiobooks>
|
||||
<tabs-audiobooks />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="artists_list.indexList"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="artists_list.indexList" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Authors</p>
|
||||
<p class="heading">{{ artists_list.sortedAndFiltered.length }} Authors</p>
|
||||
<p class="heading">
|
||||
{{ artists_list.sortedAndFiltered.length }} Authors
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-artists :artists="artists_list"></list-artists>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-artists :artists="artists_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -39,16 +40,34 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageAudiobooksArtists',
|
||||
components: { ContentWithHeading, TabsAudiobooks, IndexButtonList, ListArtists },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsAudiobooks,
|
||||
IndexButtonList,
|
||||
ListArtists
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artists: { items: [] }
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
artists_list () {
|
||||
artists_list() {
|
||||
return new Artists(this.artists.items, {
|
||||
sort: 'Name',
|
||||
group: true
|
||||
@@ -56,23 +75,8 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<!-- Recently added -->
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Recently added</p>
|
||||
<p class="heading">albums</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="recently_added.items"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="recently_added.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_browse('recently_added')">Show more</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_browse('recently_added')"
|
||||
>Show more</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -22,17 +26,21 @@
|
||||
|
||||
<!-- Recently played -->
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Recently played</p>
|
||||
<p class="heading">tracks</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-tracks :tracks="recently_played.items"></list-tracks>
|
||||
<template #content>
|
||||
<list-tracks :tracks="recently_played.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_browse('recently_played')">Show more</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_browse('recently_played')"
|
||||
>Show more</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -50,8 +58,18 @@ import webapi from '@/webapi'
|
||||
const dataObject = {
|
||||
load: function (to) {
|
||||
return Promise.all([
|
||||
webapi.search({ type: 'album', expression: 'time_added after 8 weeks ago and media_kind is music having track_count > 3 order by time_added desc', limit: 3 }),
|
||||
webapi.search({ type: 'track', expression: 'time_played after 8 weeks ago and media_kind is music order by time_played desc', limit: 3 })
|
||||
webapi.search({
|
||||
type: 'album',
|
||||
expression:
|
||||
'time_added after 8 weeks ago and media_kind is music having track_count > 3 order by time_added desc',
|
||||
limit: 3
|
||||
}),
|
||||
webapi.search({
|
||||
type: 'track',
|
||||
expression:
|
||||
'time_played after 8 weeks ago and media_kind is music order by time_played desc',
|
||||
limit: 3
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
@@ -65,7 +83,20 @@ export default {
|
||||
name: 'PageBrowse',
|
||||
components: { ContentWithHeading, TabsMusic, ListAlbums, ListTracks },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
recently_added: { items: [] },
|
||||
recently_played: { items: [] },
|
||||
@@ -79,22 +110,8 @@ export default {
|
||||
open_browse: function (type) {
|
||||
this.$router.push({ path: '/music/browse/' + type })
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Recently added</p>
|
||||
<p class="heading">albums</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="albums_list"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="albums_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -27,7 +27,8 @@ const dataObject = {
|
||||
const limit = store.getters.settings_option_recently_added_limit
|
||||
return webapi.search({
|
||||
type: 'album',
|
||||
expression: 'media_kind is music having track_count > 3 order by time_added desc',
|
||||
expression:
|
||||
'media_kind is music having track_count > 3 order by time_added desc',
|
||||
limit: limit
|
||||
})
|
||||
},
|
||||
@@ -41,14 +42,27 @@ export default {
|
||||
name: 'PageBrowseType',
|
||||
components: { ContentWithHeading, TabsMusic, ListAlbums },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
recently_added: { items: [] }
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
albums_list () {
|
||||
albums_list() {
|
||||
return new Albums(this.recently_added.items, {
|
||||
hideSingles: false,
|
||||
hideSpotify: false,
|
||||
@@ -56,22 +70,8 @@ export default {
|
||||
group: true
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Recently played</p>
|
||||
<p class="heading">tracks</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-tracks :tracks="recently_played.items"></list-tracks>
|
||||
<template #content>
|
||||
<list-tracks :tracks="recently_played.items" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -24,7 +24,8 @@ const dataObject = {
|
||||
load: function (to) {
|
||||
return webapi.search({
|
||||
type: 'track',
|
||||
expression: 'time_played after 8 weeks ago and media_kind is music order by time_played desc',
|
||||
expression:
|
||||
'time_played after 8 weeks ago and media_kind is music order by time_played desc',
|
||||
limit: 50
|
||||
})
|
||||
},
|
||||
@@ -38,26 +39,25 @@ export default {
|
||||
name: 'PageBrowseType',
|
||||
components: { ContentWithHeading, TabsMusic, ListTracks },
|
||||
|
||||
data () {
|
||||
return {
|
||||
recently_played: {}
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
recently_played: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,33 +1,59 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ name }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_composer_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_composer_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ composer_albums.total }} albums | <a class="has-text-link" @click="open_tracks">tracks</a></p>
|
||||
<list-item-albums v-for="album in composer_albums.items" :key="album.id" :album="album" @click="open_album(album)">
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ composer_albums.total }} albums |
|
||||
<a class="has-text-link" @click="open_tracks">tracks</a>
|
||||
</p>
|
||||
<list-item-albums
|
||||
v-for="album in composer_albums.items"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)"
|
||||
>
|
||||
<template slot:actions>
|
||||
<a @click="open_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-albums>
|
||||
<modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
|
||||
<modal-dialog-composer :show="show_composer_details_modal" :composer="{ 'name': name }" @close="show_composer_details_modal = false" />
|
||||
<modal-dialog-album
|
||||
:show="show_details_modal"
|
||||
:album="selected_album"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
<modal-dialog-composer
|
||||
:show="show_composer_details_modal"
|
||||
:composer="{ name: name }"
|
||||
@close="show_composer_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -53,9 +79,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageComposer',
|
||||
components: { ContentWithHeading, ListItemAlbums, ModalDialogAlbum, ModalDialogComposer },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListItemAlbums,
|
||||
ModalDialogAlbum,
|
||||
ModalDialogComposer
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
composer_albums: { items: [] },
|
||||
@@ -67,20 +111,31 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.composer_albums.items
|
||||
.map(album => album.name_sort.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.composer_albums.items.map((album) =>
|
||||
album.name_sort.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open_tracks: function () {
|
||||
this.show_details_modal = false
|
||||
this.$router.push({ name: 'ComposerTracks', params: { composer: this.name } })
|
||||
this.$router.push({
|
||||
name: 'ComposerTracks',
|
||||
params: { composer: this.name }
|
||||
})
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_expression('composer is "' + this.name + '" and media_kind is music', true)
|
||||
webapi.player_play_expression(
|
||||
'composer is "' + this.name + '" and media_kind is music',
|
||||
true
|
||||
)
|
||||
},
|
||||
|
||||
open_album: function (album) {
|
||||
@@ -91,22 +146,8 @@ export default {
|
||||
this.selected_album = album
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,33 +1,59 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ composer }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ composer }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_composer_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_composer_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile"><a class="has-text-link" @click="open_albums">albums</a> | {{ tracks.total }} tracks</p>
|
||||
<list-item-track v-for="(track, index) in rated_tracks" :key="track.id" :track="track" @click="play_track(index)">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a class="has-text-link" @click="open_albums">albums</a> |
|
||||
{{ tracks.total }} tracks
|
||||
</p>
|
||||
<list-item-track
|
||||
v-for="(track, index) in rated_tracks"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
@click="play_track(index)"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click.prevent.stop="open_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-track>
|
||||
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
|
||||
<modal-dialog-composer :show="show_composer_details_modal" :composer="{ 'name': composer }" @close="show_composer_details_modal = false" />
|
||||
<modal-dialog-track
|
||||
:show="show_details_modal"
|
||||
:track="selected_track"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
<modal-dialog-composer
|
||||
:show="show_composer_details_modal"
|
||||
:composer="{ name: composer }"
|
||||
@close="show_composer_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -53,9 +79,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageComposerTracks',
|
||||
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack, ModalDialogComposer },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListItemTrack,
|
||||
ModalDialogTrack,
|
||||
ModalDialogComposer
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tracks: { items: [] },
|
||||
composer: '',
|
||||
@@ -70,28 +114,45 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.tracks.items
|
||||
.map(track => track.title_sort.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.tracks.items.map((track) =>
|
||||
track.title_sort.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
rated_tracks () {
|
||||
return this.tracks.items.filter(track => track.rating >= this.min_rating)
|
||||
rated_tracks() {
|
||||
return this.tracks.items.filter(
|
||||
(track) => track.rating >= this.min_rating
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open_albums: function () {
|
||||
this.show_details_modal = false
|
||||
this.$router.push({ name: 'ComposerAlbums', params: { composer: this.composer } })
|
||||
this.$router.push({
|
||||
name: 'ComposerAlbums',
|
||||
params: { composer: this.composer }
|
||||
})
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_expression('composer is "' + this.composer + '" and media_kind is music', true)
|
||||
webapi.player_play_expression(
|
||||
'composer is "' + this.composer + '" and media_kind is music',
|
||||
true
|
||||
)
|
||||
},
|
||||
|
||||
play_track: function (position) {
|
||||
webapi.player_play_expression('composer is "' + this.composer + '" and media_kind is music', false, position)
|
||||
webapi.player_play_expression(
|
||||
'composer is "' + this.composer + '" and media_kind is music',
|
||||
false,
|
||||
position
|
||||
)
|
||||
},
|
||||
|
||||
show_rating: function (rating) {
|
||||
@@ -105,22 +166,8 @@ export default {
|
||||
this.selected_track = track
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="composers_list.indexList"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="composers_list.indexList" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ heading }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ heading }}
|
||||
</p>
|
||||
<p class="heading">{{ composers.total }} composers</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-composers :composers="composers_list"></list-composers>
|
||||
<template #content>
|
||||
<list-composers :composers="composers_list" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -45,7 +47,20 @@ export default {
|
||||
name: 'PageComposers',
|
||||
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListComposers },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
composers: { items: [] },
|
||||
heading: '',
|
||||
@@ -56,12 +71,17 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.composers.items
|
||||
.map(composer => composer.name.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.composers.items.map((composer) =>
|
||||
composer.name.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
composers_list () {
|
||||
composers_list() {
|
||||
return new Composers(this.composers.items, {
|
||||
sort: 'Name',
|
||||
group: true
|
||||
@@ -71,29 +91,18 @@ export default {
|
||||
|
||||
methods: {
|
||||
open_composer: function (composer) {
|
||||
this.$router.push({ name: 'ComposerAlbums', params: { composer: composer.name } })
|
||||
this.$router.push({
|
||||
name: 'ComposerAlbums',
|
||||
params: { composer: composer.name }
|
||||
})
|
||||
},
|
||||
|
||||
open_dialog: function (composer) {
|
||||
this.selected_composer = composer
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,72 +1,117 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Files</p>
|
||||
<p class="title is-7 has-text-grey">{{ current_directory }}</p>
|
||||
<p class="title is-7 has-text-grey">
|
||||
{{ current_directory }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="open_directory_dialog({ 'path': current_directory })">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="open_directory_dialog({ path: current_directory })"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-play"></i></span> <span>Play</span>
|
||||
<span class="icon"><i class="mdi mdi-play" /></span>
|
||||
<span>Play</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div class="media" v-if="$route.query.directory" @click="open_parent_directory()">
|
||||
<template #content>
|
||||
<div
|
||||
v-if="$route.query.directory"
|
||||
class="media"
|
||||
@click="open_parent_directory()"
|
||||
>
|
||||
<figure class="media-left fd-has-action">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-subdirectory-arrow-left"></i>
|
||||
<i class="mdi mdi-subdirectory-arrow-left" />
|
||||
</span>
|
||||
</figure>
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">..</h1>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<list-item-directory v-for="directory in files.directories" :key="directory.path" :directory="directory" @click="open_directory(directory)">
|
||||
<template v-slot:actions>
|
||||
<a @click="open_directory_dialog(directory)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
</a>
|
||||
</template>
|
||||
<list-item-directory
|
||||
v-for="directory in files.directories"
|
||||
:key="directory.path"
|
||||
:directory="directory"
|
||||
@click="open_directory(directory)"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_directory_dialog(directory)">
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-directory>
|
||||
|
||||
<list-item-playlist v-for="playlist in files.playlists.items" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
|
||||
<template v-slot:icon>
|
||||
<list-item-playlist
|
||||
v-for="playlist in files.playlists.items"
|
||||
:key="playlist.id"
|
||||
:playlist="playlist"
|
||||
@click="open_playlist(playlist)"
|
||||
>
|
||||
<template #icon>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-library-music"></i>
|
||||
<i class="mdi mdi-library-music" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_playlist_dialog(playlist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-playlist>
|
||||
|
||||
<list-item-track v-for="(track, index) in files.tracks.items" :key="track.id" :track="track" @click="play_track(index)">
|
||||
<template v-slot:icon>
|
||||
<list-item-track
|
||||
v-for="(track, index) in files.tracks.items"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
@click="play_track(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-file-outline"></i>
|
||||
<i class="mdi mdi-file-outline" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_track_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-track>
|
||||
|
||||
<modal-dialog-directory :show="show_directory_details_modal" :directory="selected_directory" @close="show_directory_details_modal = false" />
|
||||
<modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
|
||||
<modal-dialog-track :show="show_track_details_modal" :track="selected_track" @close="show_track_details_modal = false" />
|
||||
<modal-dialog-directory
|
||||
:show="show_directory_details_modal"
|
||||
:directory="selected_directory"
|
||||
@close="show_directory_details_modal = false"
|
||||
/>
|
||||
<modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="selected_playlist"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
<modal-dialog-track
|
||||
:show="show_track_details_modal"
|
||||
:track="selected_track"
|
||||
@close="show_track_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -95,7 +140,9 @@ const dataObject = {
|
||||
vm.files = response.data
|
||||
} else {
|
||||
vm.files = {
|
||||
directories: vm.$store.state.config.directories.map(dir => { return { path: dir } }),
|
||||
directories: vm.$store.state.config.directories.map((dir) => {
|
||||
return { path: dir }
|
||||
}),
|
||||
tracks: { items: [] },
|
||||
playlists: { items: [] }
|
||||
}
|
||||
@@ -105,11 +152,36 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageFiles',
|
||||
components: { ContentWithHeading, ListItemDirectory, ListItemPlaylist, ListItemTrack, ModalDialogDirectory, ModalDialogPlaylist, ModalDialogTrack },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListItemDirectory,
|
||||
ListItemPlaylist,
|
||||
ListItemTrack,
|
||||
ModalDialogDirectory,
|
||||
ModalDialogPlaylist,
|
||||
ModalDialogTrack
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
files: { directories: [], tracks: { items: [] }, playlists: { items: [] } },
|
||||
files: {
|
||||
directories: [],
|
||||
tracks: { items: [] },
|
||||
playlists: { items: [] }
|
||||
},
|
||||
|
||||
show_directory_details_modal: false,
|
||||
selected_directory: {},
|
||||
@@ -123,7 +195,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
current_directory () {
|
||||
current_directory() {
|
||||
if (this.$route.query && this.$route.query.directory) {
|
||||
return this.$route.query.directory
|
||||
}
|
||||
@@ -133,16 +205,33 @@ export default {
|
||||
|
||||
methods: {
|
||||
open_parent_directory: function () {
|
||||
const parent = this.current_directory.slice(0, this.current_directory.lastIndexOf('/'))
|
||||
if (parent === '' || this.$store.state.config.directories.includes(this.current_directory)) {
|
||||
const parent = this.current_directory.slice(
|
||||
0,
|
||||
this.current_directory.lastIndexOf('/')
|
||||
)
|
||||
if (
|
||||
parent === '' ||
|
||||
this.$store.state.config.directories.includes(this.current_directory)
|
||||
) {
|
||||
this.$router.push({ path: '/files' })
|
||||
} else {
|
||||
this.$router.push({ path: '/files', query: { directory: this.current_directory.slice(0, this.current_directory.lastIndexOf('/')) } })
|
||||
this.$router.push({
|
||||
path: '/files',
|
||||
query: {
|
||||
directory: this.current_directory.slice(
|
||||
0,
|
||||
this.current_directory.lastIndexOf('/')
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
open_directory: function (directory) {
|
||||
this.$router.push({ path: '/files', query: { directory: directory.path } })
|
||||
this.$router.push({
|
||||
path: '/files',
|
||||
query: { directory: directory.path }
|
||||
})
|
||||
},
|
||||
|
||||
open_directory_dialog: function (directory) {
|
||||
@@ -151,11 +240,18 @@ export default {
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_expression('path starts with "' + this.current_directory + '" order by path asc', false)
|
||||
webapi.player_play_expression(
|
||||
'path starts with "' + this.current_directory + '" order by path asc',
|
||||
false
|
||||
)
|
||||
},
|
||||
|
||||
play_track: function (position) {
|
||||
webapi.player_play_uri(this.files.tracks.items.map(a => a.uri).join(','), false, position)
|
||||
webapi.player_play_uri(
|
||||
this.files.tracks.items.map((a) => a.uri).join(','),
|
||||
false,
|
||||
position
|
||||
)
|
||||
},
|
||||
|
||||
open_track_dialog: function (track) {
|
||||
@@ -171,22 +267,8 @@ export default {
|
||||
this.selected_playlist = playlist
|
||||
this.show_playlist_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ name }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_genre_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_genre_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ genre_albums.total }} albums | <a class="has-text-link" @click="open_tracks">tracks</a></p>
|
||||
<list-albums :albums="genre_albums.items"></list-albums>
|
||||
<modal-dialog-genre :show="show_genre_details_modal" :genre="{ 'name': name }" @close="show_genre_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ genre_albums.total }} albums |
|
||||
<a class="has-text-link" @click="open_tracks">tracks</a>
|
||||
</p>
|
||||
<list-albums :albums="genre_albums.items" />
|
||||
<modal-dialog-genre
|
||||
:show="show_genre_details_modal"
|
||||
:genre="{ name: name }"
|
||||
@close="show_genre_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -46,9 +61,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageGenre',
|
||||
components: { ContentWithHeading, IndexButtonList, ListAlbums, ModalDialogGenre },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
IndexButtonList,
|
||||
ListAlbums,
|
||||
ModalDialogGenre
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
genre_albums: { items: [] },
|
||||
@@ -58,9 +91,14 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.genre_albums.items
|
||||
.map(album => album.name.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.genre_albums.items.map((album) =>
|
||||
album.name.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,29 +109,18 @@ export default {
|
||||
},
|
||||
|
||||
play: function () {
|
||||
webapi.player_play_expression('genre is "' + this.name + '" and media_kind is music', true)
|
||||
webapi.player_play_expression(
|
||||
'genre is "' + this.name + '" and media_kind is music',
|
||||
true
|
||||
)
|
||||
},
|
||||
|
||||
open_dialog: function (album) {
|
||||
this.selected_album = album
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ genre }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ genre }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_genre_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_genre_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile"><a class="has-text-link" @click="open_genre">albums</a> | {{ tracks.total }} tracks</p>
|
||||
<list-tracks :tracks="tracks.items" :expression="expression"></list-tracks>
|
||||
<modal-dialog-genre :show="show_genre_details_modal" :genre="{ 'name': genre }" @close="show_genre_details_modal = false" />
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a class="has-text-link" @click="open_genre">albums</a> |
|
||||
{{ tracks.total }} tracks
|
||||
</p>
|
||||
<list-tracks :tracks="tracks.items" :expression="expression" />
|
||||
<modal-dialog-genre
|
||||
:show="show_genre_details_modal"
|
||||
:genre="{ name: genre }"
|
||||
@close="show_genre_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -46,9 +61,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageGenreTracks',
|
||||
components: { ContentWithHeading, ListTracks, IndexButtonList, ModalDialogGenre },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListTracks,
|
||||
IndexButtonList,
|
||||
ModalDialogGenre
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tracks: { items: [] },
|
||||
genre: '',
|
||||
@@ -58,12 +91,17 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.tracks.items
|
||||
.map(track => track.title_sort.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.tracks.items.map((track) =>
|
||||
track.title_sort.charAt(0).toUpperCase()
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
expression () {
|
||||
expression() {
|
||||
return 'genre is "' + this.genre + '" and media_kind is music'
|
||||
}
|
||||
},
|
||||
@@ -77,22 +115,8 @@ export default {
|
||||
play: function () {
|
||||
webapi.player_play_expression(this.expression, true)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:options>
|
||||
<index-button-list :index="index_list"></index-button-list>
|
||||
<template #options>
|
||||
<index-button-list :index="index_list" />
|
||||
</template>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Genres</p>
|
||||
<p class="heading">{{ genres.total }} genres</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-item-genre v-for="genre in genres.items" :key="genre.name" :genre="genre" @click="open_genre(genre)">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<list-item-genre
|
||||
v-for="genre in genres.items"
|
||||
:key="genre.name"
|
||||
:genre="genre"
|
||||
@click="open_genre(genre)"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click.prevent.stop="open_dialog(genre)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-genre>
|
||||
<modal-dialog-genre :show="show_details_modal" :genre="selected_genre" @close="show_details_modal = false" />
|
||||
<modal-dialog-genre
|
||||
:show="show_details_modal"
|
||||
:genre="selected_genre"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -44,9 +55,28 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageGenres',
|
||||
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemGenre, ModalDialogGenre },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
IndexButtonList,
|
||||
ListItemGenre,
|
||||
ModalDialogGenre
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
genres: { items: [] },
|
||||
|
||||
@@ -56,9 +86,12 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
index_list () {
|
||||
return [...new Set(this.genres.items
|
||||
.map(genre => genre.name.charAt(0).toUpperCase()))]
|
||||
index_list() {
|
||||
return [
|
||||
...new Set(
|
||||
this.genres.items.map((genre) => genre.name.charAt(0).toUpperCase())
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,22 +104,8 @@ export default {
|
||||
this.selected_genre = genre
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -2,23 +2,27 @@
|
||||
<section>
|
||||
<div v-if="now_playing.id > 0" class="fd-is-fullheight">
|
||||
<div class="fd-is-expanded">
|
||||
<cover-artwork @click="open_dialog(now_playing)"
|
||||
<cover-artwork
|
||||
:artwork_url="now_playing.artwork_url"
|
||||
:artist="now_playing.artist"
|
||||
:album="now_playing.album"
|
||||
class="fd-cover-image fd-has-action" />
|
||||
class="fd-cover-image fd-has-action"
|
||||
@click="open_dialog(now_playing)"
|
||||
/>
|
||||
</div>
|
||||
<div class="fd-has-padding-left-right">
|
||||
<div class="container has-text-centered">
|
||||
<p class="control has-text-centered fd-progress-now-playing">
|
||||
<Slider v-model="item_progress_ms"
|
||||
<Slider
|
||||
v-model="item_progress_ms"
|
||||
:min="0"
|
||||
:max="state.item_length_ms"
|
||||
:step="1000"
|
||||
:tooltips="false"
|
||||
:disabled="state.state === 'stop'"
|
||||
:classes="{ target: 'seek-slider' }"
|
||||
@change="seek"
|
||||
:classes="{ target: 'seek-slider'}" />
|
||||
/>
|
||||
<!--range-slider
|
||||
class="seek-slider fd-has-action"
|
||||
min="0"
|
||||
@@ -30,7 +34,10 @@
|
||||
</range-slider-->
|
||||
</p>
|
||||
<p class="content">
|
||||
<span>{{ $filters.duration(item_progress_ms) }} / {{ $filters.duration(now_playing.length_ms) }}</span>
|
||||
<span
|
||||
>{{ $filters.duration(item_progress_ms) }} /
|
||||
{{ $filters.duration(now_playing.length_ms) }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,8 +49,11 @@
|
||||
<h2 class="title is-6">
|
||||
{{ now_playing.artist }}
|
||||
</h2>
|
||||
<h2 class="subtitle is-6 has-text-grey has-text-weight-bold" v-if="composer">
|
||||
{{ composer }}
|
||||
<h2
|
||||
v-if="composer"
|
||||
class="subtitle is-6 has-text-grey has-text-weight-bold"
|
||||
>
|
||||
{{ composer }}
|
||||
</h2>
|
||||
<h3 class="subtitle is-6">
|
||||
{{ now_playing.album }}
|
||||
@@ -52,18 +62,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="fd-is-fullheight">
|
||||
<div class="fd-is-expanded fd-has-padding-left-right" style="flex-direction: column;">
|
||||
<div
|
||||
class="fd-is-expanded fd-has-padding-left-right"
|
||||
style="flex-direction: column"
|
||||
>
|
||||
<div class="content has-text-centered">
|
||||
<h1 class="title is-5">
|
||||
Your play queue is empty
|
||||
</h1>
|
||||
<p>
|
||||
Add some tracks by browsing your library
|
||||
</p>
|
||||
<h1 class="title is-5">Your play queue is empty</h1>
|
||||
<p>Add some tracks by browsing your library</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<modal-dialog-queue-item :show="show_details_modal" :item="selected_item" @close="show_details_modal = false" />
|
||||
<modal-dialog-queue-item
|
||||
:show="show_details_modal"
|
||||
:item="selected_item"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -79,12 +92,12 @@ export default {
|
||||
name: 'PageNowPlaying',
|
||||
components: {
|
||||
ModalDialogQueueItem,
|
||||
// RangeSlider,
|
||||
// RangeSlider,
|
||||
Slider,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
item_progress_ms: 0,
|
||||
interval_id: 0,
|
||||
@@ -94,7 +107,57 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
computed: {
|
||||
state() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
|
||||
now_playing() {
|
||||
return this.$store.getters.now_playing
|
||||
},
|
||||
|
||||
settings_option_show_composer_now_playing() {
|
||||
return this.$store.getters.settings_option_show_composer_now_playing
|
||||
},
|
||||
|
||||
settings_option_show_composer_for_genre() {
|
||||
return this.$store.getters.settings_option_show_composer_for_genre
|
||||
},
|
||||
|
||||
composer() {
|
||||
if (this.settings_option_show_composer_now_playing) {
|
||||
if (
|
||||
!this.settings_option_show_composer_for_genre ||
|
||||
(this.now_playing.genre &&
|
||||
this.settings_option_show_composer_for_genre
|
||||
.toLowerCase()
|
||||
.split(',')
|
||||
.findIndex(
|
||||
(elem) =>
|
||||
this.now_playing.genre.toLowerCase().indexOf(elem.trim()) >= 0
|
||||
) >= 0)
|
||||
) {
|
||||
return this.now_playing.composer
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
state() {
|
||||
if (this.interval_id > 0) {
|
||||
window.clearTimeout(this.interval_id)
|
||||
this.interval_id = 0
|
||||
}
|
||||
this.item_progress_ms = this.state.item_progress_ms
|
||||
if (this.state.state === 'play') {
|
||||
this.interval_id = window.setInterval(this.tick, 1000)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.item_progress_ms = this.state.item_progress_ms
|
||||
webapi.player_status().then(({ data }) => {
|
||||
this.$store.commit(types.UPDATE_PLAYER_STATUS, data)
|
||||
@@ -104,44 +167,13 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
unmounted() {
|
||||
if (this.interval_id > 0) {
|
||||
window.clearTimeout(this.interval_id)
|
||||
this.interval_id = 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
state () {
|
||||
return this.$store.state.player
|
||||
},
|
||||
|
||||
now_playing () {
|
||||
return this.$store.getters.now_playing
|
||||
},
|
||||
|
||||
settings_option_show_composer_now_playing () {
|
||||
return this.$store.getters.settings_option_show_composer_now_playing
|
||||
},
|
||||
|
||||
settings_option_show_composer_for_genre () {
|
||||
return this.$store.getters.settings_option_show_composer_for_genre
|
||||
},
|
||||
|
||||
composer () {
|
||||
if (this.settings_option_show_composer_now_playing) {
|
||||
if (!this.settings_option_show_composer_for_genre ||
|
||||
(this.now_playing.genre &&
|
||||
this.settings_option_show_composer_for_genre.toLowerCase()
|
||||
.split(',')
|
||||
.findIndex(elem => this.now_playing.genre.toLowerCase().indexOf(elem.trim()) >= 0) >= 0)) {
|
||||
return this.now_playing.composer
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
tick: function () {
|
||||
this.item_progress_ms += 1000
|
||||
@@ -157,22 +189,8 @@ export default {
|
||||
this.selected_item = item
|
||||
this.show_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'state' () {
|
||||
if (this.interval_id > 0) {
|
||||
window.clearTimeout(this.interval_id)
|
||||
this.interval_id = 0
|
||||
}
|
||||
this.item_progress_ms = this.state.item_progress_ms
|
||||
if (this.state.state === 'play') {
|
||||
this.interval_id = window.setInterval(this.tick, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<div class="title is-4">{{ playlist.name }}</div>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">
|
||||
{{ playlist.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_playlist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_playlist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">{{ tracks.length }} tracks</p>
|
||||
<list-tracks :tracks="tracks" :uris="uris"></list-tracks>
|
||||
<modal-dialog-playlist :show="show_playlist_details_modal" :playlist="playlist" :uris="uris" @close="show_playlist_details_modal = false" />
|
||||
<list-tracks :tracks="tracks" :uris="uris" />
|
||||
<modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="playlist"
|
||||
:uris="uris"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -45,7 +58,20 @@ export default {
|
||||
name: 'PagePlaylist',
|
||||
components: { ContentWithHeading, ListTracks, ModalDialogPlaylist },
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
playlist: {},
|
||||
tracks: [],
|
||||
@@ -55,9 +81,9 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
uris () {
|
||||
uris() {
|
||||
if (this.playlist.random) {
|
||||
return this.tracks.map(a => a.uri).join(',')
|
||||
return this.tracks.map((a) => a.uri).join(',')
|
||||
}
|
||||
return this.playlist.uri
|
||||
}
|
||||
@@ -67,22 +93,8 @@ export default {
|
||||
play: function () {
|
||||
webapi.player_play_uri(this.uris, true)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ playlist.name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ playlist.name }}
|
||||
</p>
|
||||
<p class="heading">{{ playlists.total }} playlists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-playlists :playlists="playlists.items"></list-playlists>
|
||||
<template #content>
|
||||
<list-playlists :playlists="playlists.items" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -33,27 +35,26 @@ export default {
|
||||
name: 'PagePlaylists',
|
||||
components: { ContentWithHeading, ListPlaylists },
|
||||
|
||||
data () {
|
||||
return {
|
||||
playlist: {},
|
||||
playlists: {}
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
playlist: {},
|
||||
playlists: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,31 +1,46 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<div class="title is-4">{{ album.name }}
|
||||
<template #heading-left>
|
||||
<div class="title is-4">
|
||||
{{ album.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_album_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_album_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-play"></i>
|
||||
<i class="mdi mdi-play" />
|
||||
</span>
|
||||
<span>Play</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ album.track_count }} tracks</p>
|
||||
<list-item-track v-for="track in tracks" :key="track.id" :track="track" @click="play_track(track)">
|
||||
<template v-slot:progress>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ album.track_count }} tracks
|
||||
</p>
|
||||
<list-item-track
|
||||
v-for="track in tracks"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
@click="play_track(track)"
|
||||
>
|
||||
<template #progress>
|
||||
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click.prevent.stop="open_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-track>
|
||||
@@ -33,7 +48,8 @@
|
||||
:show="show_details_modal"
|
||||
:track="selected_track"
|
||||
@close="show_details_modal = false"
|
||||
@play-count-changed="reload_tracks" />
|
||||
@play-count-changed="reload_tracks"
|
||||
/>
|
||||
<modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="album"
|
||||
@@ -41,16 +57,22 @@
|
||||
:new_tracks="new_tracks"
|
||||
@close="show_album_details_modal = false"
|
||||
@play-count-changed="reload_tracks"
|
||||
@remove-podcast="open_remove_podcast_dialog" />
|
||||
@remove-podcast="open_remove_podcast_dialog"
|
||||
/>
|
||||
<modal-dialog
|
||||
:show="show_remove_podcast_modal"
|
||||
title="Remove podcast"
|
||||
delete_action="Remove"
|
||||
@close="show_remove_podcast_modal = false"
|
||||
@delete="remove_podcast">
|
||||
<template v-slot:modal-content>
|
||||
@delete="remove_podcast"
|
||||
>
|
||||
<template #modal-content>
|
||||
<p>Permanently remove this podcast from your library?</p>
|
||||
<p class="is-size-7">(This will also remove the RSS playlist <b>{{ rss_playlist_to_remove.name }}</b>.)</p>
|
||||
<p class="is-size-7">
|
||||
(This will also remove the RSS playlist
|
||||
<b>{{ rss_playlist_to_remove.name }}</b
|
||||
>.)
|
||||
</p>
|
||||
</template>
|
||||
</modal-dialog>
|
||||
</template>
|
||||
@@ -91,7 +113,20 @@ export default {
|
||||
ProgressBar
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
album: {},
|
||||
tracks: [],
|
||||
@@ -107,8 +142,8 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
new_tracks () {
|
||||
return this.tracks.filter(track => track.play_count === 0).length
|
||||
new_tracks() {
|
||||
return this.tracks.filter((track) => track.play_count === 0).length
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,9 +164,12 @@ export default {
|
||||
open_remove_podcast_dialog: function () {
|
||||
this.show_album_details_modal = false
|
||||
webapi.library_track_playlists(this.tracks[0].id).then(({ data }) => {
|
||||
const rssPlaylists = data.items.filter(pl => pl.type === 'rss')
|
||||
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
|
||||
if (rssPlaylists.length !== 1) {
|
||||
this.$store.dispatch('add_notification', { text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.', type: 'danger' })
|
||||
this.$store.dispatch('add_notification', {
|
||||
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.',
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,9 +180,11 @@ export default {
|
||||
|
||||
remove_podcast: function () {
|
||||
this.show_remove_podcast_modal = false
|
||||
webapi.library_playlist_delete(this.rss_playlist_to_remove.id).then(() => {
|
||||
this.$router.replace({ path: '/podcasts' })
|
||||
})
|
||||
webapi
|
||||
.library_playlist_delete(this.rss_playlist_to_remove.id)
|
||||
.then(() => {
|
||||
this.$router.replace({ path: '/podcasts' })
|
||||
})
|
||||
},
|
||||
|
||||
reload_tracks: function () {
|
||||
@@ -152,22 +192,8 @@ export default {
|
||||
this.tracks = data.tracks.items
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,64 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading v-if="new_episodes.items.length > 0">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">New episodes</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small" @click="mark_all_played">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-pencil"></i>
|
||||
</span>
|
||||
<span>Mark All Played</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-item-track v-for="track in new_episodes.items" :key="track.id" :track="track" @click="play_track(track)">
|
||||
<template v-slot:progress>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small" @click="mark_all_played">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-pencil" />
|
||||
</span>
|
||||
<span>Mark All Played</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<list-item-track
|
||||
v-for="track in new_episodes.items"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
@click="play_track(track)"
|
||||
>
|
||||
<template #progress>
|
||||
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_track_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-track>
|
||||
<modal-dialog-track :show="show_track_details_modal" :track="selected_track" @close="show_track_details_modal = false" @play-count-changed="reload_new_episodes" />
|
||||
<modal-dialog-track
|
||||
:show="show_track_details_modal"
|
||||
:track="selected_track"
|
||||
@close="show_track_details_modal = false"
|
||||
@play-count-changed="reload_new_episodes"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Podcasts</p>
|
||||
<p class="heading">{{ albums.total }} podcasts</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a v-if="rss.tracks > 0" class="button is-small" @click="update_rss">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<i class="mdi mdi-refresh" />
|
||||
</span>
|
||||
<span>Update</span>
|
||||
</a>
|
||||
<a class="button is-small" @click="open_add_podcast_dialog">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-rss"></i>
|
||||
<i class="mdi mdi-rss" />
|
||||
</span>
|
||||
<span>Add Podcast</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="albums.items"
|
||||
@play-count-changed="reload_new_episodes()"
|
||||
@podcast-deleted="reload_podcasts()">
|
||||
</list-albums>
|
||||
<template #content>
|
||||
<list-albums
|
||||
:albums="albums.items"
|
||||
@play-count-changed="reload_new_episodes()"
|
||||
@podcast-deleted="reload_podcasts()"
|
||||
/>
|
||||
<modal-dialog-add-rss
|
||||
:show="show_url_modal"
|
||||
@close="show_url_modal = false"
|
||||
@podcast-added="reload_podcasts()" />
|
||||
:show="show_url_modal"
|
||||
@close="show_url_modal = false"
|
||||
@podcast-added="reload_podcasts()"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -99,7 +113,20 @@ export default {
|
||||
ProgressBar
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
albums: { items: [] },
|
||||
new_episodes: { items: [] },
|
||||
@@ -112,7 +139,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
rss () {
|
||||
rss() {
|
||||
return this.$store.state.rss_count
|
||||
}
|
||||
},
|
||||
@@ -128,10 +155,10 @@ export default {
|
||||
},
|
||||
|
||||
mark_all_played: function () {
|
||||
this.new_episodes.items.forEach(ep => {
|
||||
this.new_episodes.items.forEach((ep) => {
|
||||
webapi.library_track_update(ep.id, { play_count: 'increment' })
|
||||
})
|
||||
this.new_episodes.items = { }
|
||||
this.new_episodes.items = {}
|
||||
},
|
||||
|
||||
open_add_podcast_dialog: function (item) {
|
||||
@@ -155,22 +182,8 @@ export default {
|
||||
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, 'rss')
|
||||
this.$store.commit(types.SHOW_UPDATE_DIALOG, true)
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,66 +1,103 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="heading">{{ queue.count }} tracks</p>
|
||||
<p class="title is-4">Queue</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small" :class="{ 'is-info': show_only_next_items }" @click="update_show_next_items">
|
||||
<a
|
||||
class="button is-small"
|
||||
:class="{ 'is-info': show_only_next_items }"
|
||||
@click="update_show_next_items"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-arrow-collapse-down"></i>
|
||||
<i class="mdi mdi-arrow-collapse-down" />
|
||||
</span>
|
||||
<span>Hide previous</span>
|
||||
</a>
|
||||
<a class="button is-small" @click="open_add_stream_dialog">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-web"></i>
|
||||
<i class="mdi mdi-web" />
|
||||
</span>
|
||||
<span>Add Stream</span>
|
||||
</a>
|
||||
<a class="button is-small" :class="{ 'is-info': edit_mode }" @click="edit_mode = !edit_mode">
|
||||
<a
|
||||
class="button is-small"
|
||||
:class="{ 'is-info': edit_mode }"
|
||||
@click="edit_mode = !edit_mode"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-pencil"></i>
|
||||
<i class="mdi mdi-pencil" />
|
||||
</span>
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
<a class="button is-small" @click="queue_clear">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-delete-empty"></i>
|
||||
<i class="mdi mdi-delete-empty" />
|
||||
</span>
|
||||
<span>Clear</span>
|
||||
</a>
|
||||
<a class="button is-small" v-if="is_queue_save_allowed" :disabled="queue_items.length === 0" @click="save_dialog">
|
||||
<a
|
||||
v-if="is_queue_save_allowed"
|
||||
class="button is-small"
|
||||
:disabled="queue_items.length === 0"
|
||||
@click="save_dialog"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-content-save"></i>
|
||||
<i class="mdi mdi-content-save" />
|
||||
</span>
|
||||
<span>Save</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<draggable v-model="queue_items" handle=".handle" item-key="id" @end="move_item">
|
||||
<template #content>
|
||||
<draggable
|
||||
v-model="queue_items"
|
||||
handle=".handle"
|
||||
item-key="id"
|
||||
@end="move_item"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<list-item-queue-item
|
||||
:item="element"
|
||||
:position="index"
|
||||
:current_position="current_position"
|
||||
:show_only_next_items="show_only_next_items"
|
||||
:edit_mode="edit_mode">
|
||||
<template v-slot:actions>
|
||||
<a @click.prevent.stop="open_dialog(element)" v-if="!edit_mode">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
</a>
|
||||
<a @click.prevent.stop="remove(element)" v-if="element.id !== state.item_id && edit_mode">
|
||||
<span class="icon has-text-grey"><i class="mdi mdi-delete mdi-18px"></i></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-queue-item>
|
||||
:edit_mode="edit_mode"
|
||||
>
|
||||
<template #actions>
|
||||
<a v-if="!edit_mode" @click.prevent.stop="open_dialog(element)">
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a
|
||||
v-if="element.id !== state.item_id && edit_mode"
|
||||
@click.prevent.stop="remove(element)"
|
||||
>
|
||||
<span class="icon has-text-grey"
|
||||
><i class="mdi mdi-delete mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-queue-item>
|
||||
</template>
|
||||
</draggable>
|
||||
<modal-dialog-queue-item :show="show_details_modal" :item="selected_item" @close="show_details_modal = false" />
|
||||
<modal-dialog-add-url-stream :show="show_url_modal" @close="show_url_modal = false" />
|
||||
<modal-dialog-playlist-save v-if="is_queue_save_allowed" :show="show_pls_save_modal" @close="show_pls_save_modal = false" />
|
||||
<modal-dialog-queue-item
|
||||
:show="show_details_modal"
|
||||
:item="selected_item"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
<modal-dialog-add-url-stream
|
||||
:show="show_url_modal"
|
||||
@close="show_url_modal = false"
|
||||
/>
|
||||
<modal-dialog-playlist-save
|
||||
v-if="is_queue_save_allowed"
|
||||
:show="show_pls_save_modal"
|
||||
@close="show_pls_save_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -77,9 +114,16 @@ import draggable from 'vuedraggable'
|
||||
|
||||
export default {
|
||||
name: 'PageQueue',
|
||||
components: { ContentWithHeading, ListItemQueueItem, draggable, ModalDialogQueueItem, ModalDialogAddUrlStream, ModalDialogPlaylistSave },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ListItemQueueItem,
|
||||
draggable,
|
||||
ModalDialogQueueItem,
|
||||
ModalDialogAddUrlStream,
|
||||
ModalDialogPlaylistSave
|
||||
},
|
||||
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
edit_mode: false,
|
||||
|
||||
@@ -91,24 +135,33 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
state () {
|
||||
state() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
is_queue_save_allowed () {
|
||||
return this.$store.state.config.allow_modifying_stored_playlists && this.$store.state.config.default_playlist_directory
|
||||
is_queue_save_allowed() {
|
||||
return (
|
||||
this.$store.state.config.allow_modifying_stored_playlists &&
|
||||
this.$store.state.config.default_playlist_directory
|
||||
)
|
||||
},
|
||||
queue () {
|
||||
queue() {
|
||||
return this.$store.state.queue
|
||||
},
|
||||
queue_items: {
|
||||
get () { return this.$store.state.queue.items },
|
||||
set (value) { /* Do nothing? Send move request in @end event */ }
|
||||
get() {
|
||||
return this.$store.state.queue.items
|
||||
},
|
||||
set(value) {
|
||||
/* Do nothing? Send move request in @end event */
|
||||
}
|
||||
},
|
||||
current_position () {
|
||||
current_position() {
|
||||
const nowPlaying = this.$store.getters.now_playing
|
||||
return nowPlaying === undefined || nowPlaying.position === undefined ? -1 : this.$store.getters.now_playing.position
|
||||
return nowPlaying === undefined || nowPlaying.position === undefined
|
||||
? -1
|
||||
: this.$store.getters.now_playing.position
|
||||
},
|
||||
show_only_next_items () {
|
||||
show_only_next_items() {
|
||||
return this.$store.state.show_only_next_items
|
||||
}
|
||||
},
|
||||
@@ -127,7 +180,9 @@ export default {
|
||||
},
|
||||
|
||||
move_item: function (e) {
|
||||
const oldPosition = !this.show_only_next_items ? e.oldIndex : e.oldIndex + this.current_position
|
||||
const oldPosition = !this.show_only_next_items
|
||||
? e.oldIndex
|
||||
: e.oldIndex + this.current_position
|
||||
const item = this.queue_items[oldPosition]
|
||||
const newPosition = item.position + (e.newIndex - e.oldIndex)
|
||||
if (newPosition !== oldPosition) {
|
||||
@@ -153,5 +208,4 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Radio</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ tracks.total }} tracks</p>
|
||||
<list-tracks :tracks="tracks.items"></list-tracks>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ tracks.total }} tracks
|
||||
</p>
|
||||
<list-tracks :tracks="tracks.items" />
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -31,26 +33,25 @@ export default {
|
||||
name: 'PageRadioStreams',
|
||||
components: { ContentWithHeading, ListTracks },
|
||||
|
||||
data () {
|
||||
return {
|
||||
tracks: { items: [] }
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tracks: { items: [] }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -5,91 +5,122 @@
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths">
|
||||
<form v-on:submit.prevent="new_search">
|
||||
<form @submit.prevent="new_search">
|
||||
<div class="field">
|
||||
<p class="control is-expanded has-icons-left">
|
||||
<input class="input is-rounded is-shadowless" type="text" placeholder="Search" v-model="search_query" ref="search_field" autocomplete="off">
|
||||
<input
|
||||
ref="search_field"
|
||||
v-model="search_query"
|
||||
class="input is-rounded is-shadowless"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<i class="mdi mdi-magnify" />
|
||||
</span>
|
||||
</p>
|
||||
<p class="help has-text-centered">Tip: you can search by a smart playlist query language <a href="https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md" target="_blank">expression</a> if you prefix it
|
||||
with <code>query:</code>.
|
||||
<p class="help has-text-centered">
|
||||
Tip: you can search by a smart playlist query language
|
||||
<a
|
||||
href="https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md"
|
||||
target="_blank"
|
||||
>expression</a
|
||||
>
|
||||
if you prefix it with <code>query:</code>.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div class="tags" style="margin-top: 16px;">
|
||||
<a class="tag" v-for="recent_search in recent_searches" :key="recent_search" @click="open_recent_search(recent_search)">{{ recent_search }}</a>
|
||||
<div class="tags" style="margin-top: 16px">
|
||||
<a
|
||||
v-for="recent_search in recent_searches"
|
||||
:key="recent_search"
|
||||
class="tag"
|
||||
@click="open_recent_search(recent_search)"
|
||||
>{{ recent_search }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<tabs-search :query="search_query"></tabs-search>
|
||||
<tabs-search :query="search_query" />
|
||||
|
||||
<!-- Tracks -->
|
||||
<content-with-heading v-if="show_tracks && tracks.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Tracks</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-tracks :tracks="tracks.items"></list-tracks>
|
||||
<template #content>
|
||||
<list-tracks :tracks="tracks.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_tracks_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_tracks">Show all {{ tracks.total.toLocaleString() }} tracks</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_tracks"
|
||||
>Show all {{ tracks.total.toLocaleString() }} tracks</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No tracks found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Artists -->
|
||||
<content-with-heading v-if="show_artists && artists.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Artists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-artists :artists="artists.items"></list-artists>
|
||||
<template #content>
|
||||
<list-artists :artists="artists.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_artists_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_artists">Show all {{ artists.total.toLocaleString() }} artists</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_artists"
|
||||
>Show all {{ artists.total.toLocaleString() }} artists</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_artists && !artists.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No artists found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Albums -->
|
||||
<content-with-heading v-if="show_albums && albums.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Albums</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="albums.items"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="albums.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_albums_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_albums">Show all {{ albums.total.toLocaleString() }} albums</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_albums"
|
||||
>Show all {{ albums.total.toLocaleString() }} albums</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_albums && !albums.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No albums found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
@@ -100,12 +131,16 @@
|
||||
<p class="title is-4">Composers</p>
|
||||
</template>
|
||||
<template slot:content>
|
||||
<list-composers :composers="composers.items"></list-composers>
|
||||
<list-composers :composers="composers.items" />
|
||||
</template>
|
||||
<template slot:footer>
|
||||
<nav v-if="show_all_composers_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_composers">Show all {{ composers.total }} composers</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_composers"
|
||||
>Show all {{ composers.total }} composers</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -118,66 +153,78 @@
|
||||
|
||||
<!-- Playlists -->
|
||||
<content-with-heading v-if="show_playlists && playlists.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Playlists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-playlists :playlists="playlists.items"></list-playlists>
|
||||
<template #content>
|
||||
<list-playlists :playlists="playlists.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_playlists_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_playlists">Show all {{ playlists.total.toLocaleString() }} playlists</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_playlists"
|
||||
>Show all {{ playlists.total.toLocaleString() }} playlists</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_playlists && !playlists.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No playlists found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Podcasts -->
|
||||
<content-with-heading v-if="show_podcasts && podcasts.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Podcasts</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="podcasts.items"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="podcasts.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_podcasts_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_podcasts">Show all {{ podcasts.total.toLocaleString() }} podcasts</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_podcasts"
|
||||
>Show all {{ podcasts.total.toLocaleString() }} podcasts</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_podcasts && !podcasts.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No podcasts found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Audiobooks -->
|
||||
<content-with-heading v-if="show_audiobooks && audiobooks.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Audiobooks</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<list-albums :albums="audiobooks.items"></list-albums>
|
||||
<template #content>
|
||||
<list-albums :albums="audiobooks.items" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_audiobooks_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_audiobooks">Show all {{ audiobooks.total.toLocaleString() }} audiobooks</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_audiobooks"
|
||||
>Show all {{ audiobooks.total.toLocaleString() }} audiobooks</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_audiobooks && !audiobooks.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No audiobooks found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
@@ -198,9 +245,18 @@ import * as types from '@/store/mutation_types'
|
||||
|
||||
export default {
|
||||
name: 'PageSearch',
|
||||
components: { ContentWithHeading, ContentText, TabsSearch, ListTracks, ListArtists, ListAlbums, ListPlaylists, ListComposers },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ContentText,
|
||||
TabsSearch,
|
||||
ListTracks,
|
||||
ListArtists,
|
||||
ListAlbums,
|
||||
ListPlaylists,
|
||||
ListComposers
|
||||
},
|
||||
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
search_query: '',
|
||||
|
||||
@@ -215,64 +271,85 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
recent_searches () {
|
||||
recent_searches() {
|
||||
return this.$store.state.recent_searches
|
||||
},
|
||||
|
||||
show_tracks () {
|
||||
show_tracks() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('track')
|
||||
},
|
||||
show_all_tracks_button () {
|
||||
show_all_tracks_button() {
|
||||
return this.tracks.total > this.tracks.items.length
|
||||
},
|
||||
|
||||
show_artists () {
|
||||
show_artists() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('artist')
|
||||
},
|
||||
show_all_artists_button () {
|
||||
show_all_artists_button() {
|
||||
return this.artists.total > this.artists.items.length
|
||||
},
|
||||
|
||||
show_albums () {
|
||||
show_albums() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('album')
|
||||
},
|
||||
show_all_albums_button () {
|
||||
show_all_albums_button() {
|
||||
return this.albums.total > this.albums.items.length
|
||||
},
|
||||
|
||||
show_composers () {
|
||||
return this.$route.query.type && this.$route.query.type.includes('composer')
|
||||
show_composers() {
|
||||
return (
|
||||
this.$route.query.type && this.$route.query.type.includes('composer')
|
||||
)
|
||||
},
|
||||
show_all_composers_button () {
|
||||
show_all_composers_button() {
|
||||
return this.composers.total > this.composers.items.length
|
||||
},
|
||||
|
||||
show_playlists () {
|
||||
return this.$route.query.type && this.$route.query.type.includes('playlist')
|
||||
show_playlists() {
|
||||
return (
|
||||
this.$route.query.type && this.$route.query.type.includes('playlist')
|
||||
)
|
||||
},
|
||||
show_all_playlists_button () {
|
||||
show_all_playlists_button() {
|
||||
return this.playlists.total > this.playlists.items.length
|
||||
},
|
||||
|
||||
show_audiobooks () {
|
||||
return this.$route.query.type && this.$route.query.type.includes('audiobook')
|
||||
show_audiobooks() {
|
||||
return (
|
||||
this.$route.query.type && this.$route.query.type.includes('audiobook')
|
||||
)
|
||||
},
|
||||
show_all_audiobooks_button () {
|
||||
show_all_audiobooks_button() {
|
||||
return this.audiobooks.total > this.audiobooks.items.length
|
||||
},
|
||||
|
||||
show_podcasts () {
|
||||
return this.$route.query.type && this.$route.query.type.includes('podcast')
|
||||
show_podcasts() {
|
||||
return (
|
||||
this.$route.query.type && this.$route.query.type.includes('podcast')
|
||||
)
|
||||
},
|
||||
show_all_podcasts_button () {
|
||||
show_all_podcasts_button() {
|
||||
return this.podcasts.total > this.podcasts.items.length
|
||||
},
|
||||
|
||||
is_visible_artwork () {
|
||||
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
|
||||
is_visible_artwork() {
|
||||
return this.$store.getters.settings_option(
|
||||
'webinterface',
|
||||
'show_cover_artwork_in_album_lists'
|
||||
).value
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
this.search(to)
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
this.search(this.$route)
|
||||
},
|
||||
|
||||
methods: {
|
||||
search: function (route) {
|
||||
if (!route.query.query || route.query.query === '') {
|
||||
@@ -289,7 +366,12 @@ export default {
|
||||
},
|
||||
|
||||
searchMusic: function (query) {
|
||||
if (query.type.indexOf('track') < 0 && query.type.indexOf('artist') < 0 && query.type.indexOf('album') < 0 && query.type.indexOf('playlist') < 0) {
|
||||
if (
|
||||
query.type.indexOf('track') < 0 &&
|
||||
query.type.indexOf('artist') < 0 &&
|
||||
query.type.indexOf('album') < 0 &&
|
||||
query.type.indexOf('playlist') < 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -313,8 +395,12 @@ export default {
|
||||
this.tracks = data.tracks ? data.tracks : { items: [], total: 0 }
|
||||
this.artists = data.artists ? data.artists : { items: [], total: 0 }
|
||||
this.albums = data.albums ? data.albums : { items: [], total: 0 }
|
||||
this.composers = data.composers ? data.composers : { items: [], total: 0 }
|
||||
this.playlists = data.playlists ? data.playlists : { items: [], total: 0 }
|
||||
this.composers = data.composers
|
||||
? data.composers
|
||||
: { items: [], total: 0 }
|
||||
this.playlists = data.playlists
|
||||
? data.playlists
|
||||
: { items: [], total: 0 }
|
||||
})
|
||||
},
|
||||
|
||||
@@ -331,7 +417,12 @@ export default {
|
||||
if (query.query.startsWith('query:')) {
|
||||
searchParams.expression = query.query.replace(/^query:/, '').trim()
|
||||
} else {
|
||||
searchParams.expression = '((album includes "' + query.query + '" or artist includes "' + query.query + '") and media_kind is audiobook)'
|
||||
searchParams.expression =
|
||||
'((album includes "' +
|
||||
query.query +
|
||||
'" or artist includes "' +
|
||||
query.query +
|
||||
'") and media_kind is audiobook)'
|
||||
}
|
||||
|
||||
if (query.limit) {
|
||||
@@ -357,7 +448,12 @@ export default {
|
||||
if (query.query.startsWith('query:')) {
|
||||
searchParams.expression = query.query.replace(/^query:/, '').trim()
|
||||
} else {
|
||||
searchParams.expression = '((album includes "' + query.query + '" or artist includes "' + query.query + '") and media_kind is podcast)'
|
||||
searchParams.expression =
|
||||
'((album includes "' +
|
||||
query.query +
|
||||
'" or artist includes "' +
|
||||
query.query +
|
||||
'") and media_kind is podcast)'
|
||||
}
|
||||
|
||||
if (query.limit) {
|
||||
@@ -458,7 +554,10 @@ export default {
|
||||
},
|
||||
|
||||
open_composer: function (composer) {
|
||||
this.$router.push({ name: 'ComposerAlbums', params: { composer: composer.name } })
|
||||
this.$router.push({
|
||||
name: 'ComposerAlbums',
|
||||
params: { composer: composer.name }
|
||||
})
|
||||
},
|
||||
|
||||
open_playlist: function (playlist) {
|
||||
@@ -494,19 +593,8 @@ export default {
|
||||
this.selected_playlist = playlist
|
||||
this.show_playlist_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
this.search(this.$route)
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route' (to, from) {
|
||||
this.search(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,28 +1,50 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-settings></tabs-settings>
|
||||
<tabs-settings />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Artwork</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<div class="content">
|
||||
<p>
|
||||
OwnTone supports PNG and JPEG artwork which is either placed as separate image files in the library,
|
||||
embedded in the media files or made available online by radio stations.
|
||||
OwnTone supports PNG and JPEG artwork which is either placed as
|
||||
separate image files in the library, embedded in the media files or
|
||||
made available online by radio stations.
|
||||
</p>
|
||||
<p>
|
||||
In addition to that, you can enable fetching artwork from the
|
||||
following artwork providers:
|
||||
</p>
|
||||
<p>In addition to that, you can enable fetching artwork from the following artwork providers:</p>
|
||||
</div>
|
||||
<settings-checkbox category_name="artwork" option_name="use_artwork_source_spotify" v-if="spotify.libspotify_logged_in">
|
||||
<template v-slot:label> Spotify</template>
|
||||
<settings-checkbox
|
||||
v-if="spotify.libspotify_logged_in"
|
||||
category_name="artwork"
|
||||
option_name="use_artwork_source_spotify"
|
||||
>
|
||||
<template #label> Spotify </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="artwork" option_name="use_artwork_source_discogs">
|
||||
<template v-slot:label> Discogs (<a href="https://www.discogs.com/">https://www.discogs.com/</a>)</template>
|
||||
<settings-checkbox
|
||||
category_name="artwork"
|
||||
option_name="use_artwork_source_discogs"
|
||||
>
|
||||
<template #label>
|
||||
Discogs (<a href="https://www.discogs.com/"
|
||||
>https://www.discogs.com/</a
|
||||
>)
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="artwork" option_name="use_artwork_source_coverartarchive">
|
||||
<template v-slot:label> Cover Art Archive (<a href="https://coverartarchive.org/">https://coverartarchive.org/</a>)</template>
|
||||
<settings-checkbox
|
||||
category_name="artwork"
|
||||
option_name="use_artwork_source_coverartarchive"
|
||||
>
|
||||
<template #label>
|
||||
Cover Art Archive (<a href="https://coverartarchive.org/"
|
||||
>https://coverartarchive.org/</a
|
||||
>)
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
@@ -39,12 +61,11 @@ export default {
|
||||
components: { ContentWithHeading, TabsSettings, SettingsCheckbox },
|
||||
|
||||
computed: {
|
||||
spotify () {
|
||||
spotify() {
|
||||
return this.$store.state.spotify
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-settings></tabs-settings>
|
||||
<tabs-settings />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Spotify</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<div class="notification is-size-7" v-if="!spotify.spotify_installed">
|
||||
<p>OwnTone was either built without support for Spotify or libspotify is not installed.</p>
|
||||
<template #content>
|
||||
<div v-if="!spotify.spotify_installed" class="notification is-size-7">
|
||||
<p>
|
||||
OwnTone was either built without support for Spotify or libspotify
|
||||
is not installed.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="spotify.spotify_installed">
|
||||
<div class="notification is-size-7">
|
||||
<b>You must have a Spotify premium account</b>. <span v-if="use_libspotity">If you normally log into Spotify with your Facebook account you must first go to Spotify's web site where you can get the Spotify username and password that matches your account.</span>
|
||||
<b>You must have a Spotify premium account</b>.
|
||||
<span v-if="use_libspotity"
|
||||
>If you normally log into Spotify with your Facebook account you
|
||||
must first go to Spotify's web site where you can get the Spotify
|
||||
username and password that matches your account.</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-if="use_libspotity">
|
||||
@@ -21,29 +29,53 @@
|
||||
<b>libspotify</b> - Login with your Spotify username and password
|
||||
</p>
|
||||
<p v-if="spotify.libspotify_logged_in" class="fd-has-margin-bottom">
|
||||
Logged in as <b><code>{{ spotify.libspotify_user }}</code></b>
|
||||
Logged in as
|
||||
<b
|
||||
><code>{{ spotify.libspotify_user }}</code></b
|
||||
>
|
||||
</p>
|
||||
<form v-if="spotify.spotify_installed && !spotify.libspotify_logged_in" @submit.prevent="login_libspotify">
|
||||
<form
|
||||
v-if="spotify.spotify_installed && !spotify.libspotify_logged_in"
|
||||
@submit.prevent="login_libspotify"
|
||||
>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" placeholder="Username" v-model="libspotify.user">
|
||||
<p class="help is-danger">{{ libspotify.errors.user }}</p>
|
||||
<input
|
||||
v-model="libspotify.user"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<p class="help is-danger">
|
||||
{{ libspotify.errors.user }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="password" placeholder="Password" v-model="libspotify.password">
|
||||
<p class="help is-danger">{{ libspotify.errors.password }}</p>
|
||||
<input
|
||||
v-model="libspotify.password"
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<p class="help is-danger">
|
||||
{{ libspotify.errors.password }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-info">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p class="help is-danger">{{ libspotify.errors.error }}</p>
|
||||
<p class="help is-danger">
|
||||
{{ libspotify.errors.error }}
|
||||
</p>
|
||||
<p class="help">
|
||||
libspotify enables OwnTone to play Spotify tracks.
|
||||
</p>
|
||||
<p class="help">
|
||||
OwnTone will not store your password, but will still be able to log you in automatically afterwards, because libspotify saves a login token.
|
||||
OwnTone will not store your password, but will still be able to
|
||||
log you in automatically afterwards, because libspotify saves a
|
||||
login token.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -52,22 +84,42 @@
|
||||
<b>Spotify Web API</b> - Grant access to the Spotify Web API
|
||||
</p>
|
||||
<p v-if="spotify.webapi_token_valid">
|
||||
Access granted for <b><code>{{ spotify.webapi_user }}</code></b>
|
||||
Access granted for
|
||||
<b
|
||||
><code>{{ spotify.webapi_user }}</code></b
|
||||
>
|
||||
</p>
|
||||
<p class="help is-danger" v-if="spotify_missing_scope.length > 0">
|
||||
Please reauthorize Web API access to grant OwnTone the following additional access rights:
|
||||
<b><code>{{ spotify_missing_scope.join() }}</code></b>
|
||||
<p v-if="spotify_missing_scope.length > 0" class="help is-danger">
|
||||
Please reauthorize Web API access to grant OwnTone the following
|
||||
additional access rights:
|
||||
<b
|
||||
><code>{{ spotify_missing_scope.join() }}</code></b
|
||||
>
|
||||
</p>
|
||||
<div class="field fd-has-margin-top ">
|
||||
<div class="field fd-has-margin-top">
|
||||
<div class="control">
|
||||
<a class="button" :class="{ 'is-info': !spotify.webapi_token_valid || spotify_missing_scope.length > 0 }" :href="spotify.oauth_uri">Authorize Web API access</a>
|
||||
<a
|
||||
class="button"
|
||||
:class="{
|
||||
'is-info':
|
||||
!spotify.webapi_token_valid ||
|
||||
spotify_missing_scope.length > 0
|
||||
}"
|
||||
:href="spotify.oauth_uri"
|
||||
>Authorize Web API access</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
Access to the Spotify Web API enables scanning of your Spotify library. Required scopes are
|
||||
<code>{{ spotify_required_scope.join() }}</code>.
|
||||
Access to the Spotify Web API enables scanning of your Spotify
|
||||
library. Required scopes are
|
||||
<code>{{ spotify_required_scope.join() }}</code
|
||||
>.
|
||||
</p>
|
||||
<div v-if="spotify.webapi_token_valid" class="field fd-has-margin-top ">
|
||||
<div
|
||||
v-if="spotify.webapi_token_valid"
|
||||
class="field fd-has-margin-top"
|
||||
>
|
||||
<div class="control">
|
||||
<a class="button is-danger" @click="logout_spotify">Logout</a>
|
||||
</div>
|
||||
@@ -78,17 +130,18 @@
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Last.fm</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<div class="notification is-size-7" v-if="!lastfm.enabled">
|
||||
<template #content>
|
||||
<div v-if="!lastfm.enabled" class="notification is-size-7">
|
||||
<p>OwnTone was built without support for Last.fm.</p>
|
||||
</div>
|
||||
<div v-if="lastfm.enabled">
|
||||
<p class="content">
|
||||
<b>Last.fm</b> - Login with your Last.fm username and password to enable scrobbling
|
||||
<b>Last.fm</b> - Login with your Last.fm username and password to
|
||||
enable scrobbling
|
||||
</p>
|
||||
<div v-if="lastfm.scrobbling_enabled">
|
||||
<a class="button" @click="logoutLastfm">Stop scrobbling</a>
|
||||
@@ -97,20 +150,37 @@
|
||||
<form @submit.prevent="login_lastfm">
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" placeholder="Username" v-model="lastfm_login.user">
|
||||
<p class="help is-danger">{{ lastfm_login.errors.user }}</p>
|
||||
<input
|
||||
v-model="lastfm_login.user"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<p class="help is-danger">
|
||||
{{ lastfm_login.errors.user }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="password" placeholder="Password" v-model="lastfm_login.password">
|
||||
<p class="help is-danger">{{ lastfm_login.errors.password }}</p>
|
||||
<input
|
||||
v-model="lastfm_login.password"
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<p class="help is-danger">
|
||||
{{ lastfm_login.errors.password }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-info" type="submit">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger">{{ lastfm_login.errors.error }}</p>
|
||||
<p class="help is-danger">
|
||||
{{ lastfm_login.errors.error }}
|
||||
</p>
|
||||
<p class="help">
|
||||
OwnTone will not store your Last.fm username/password, only the session key. The session key does not expire.
|
||||
OwnTone will not store your Last.fm username/password, only the
|
||||
session key. The session key does not expire.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
@@ -129,44 +199,66 @@ export default {
|
||||
name: 'SettingsPageOnlineServices',
|
||||
components: { ContentWithHeading, TabsSettings },
|
||||
|
||||
data () {
|
||||
filters: {
|
||||
join(array) {
|
||||
return array.join(', ')
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
libspotify: { user: '', password: '', errors: { user: '', password: '', error: '' } },
|
||||
lastfm_login: { user: '', password: '', errors: { user: '', password: '', error: '' } }
|
||||
libspotify: {
|
||||
user: '',
|
||||
password: '',
|
||||
errors: { user: '', password: '', error: '' }
|
||||
},
|
||||
lastfm_login: {
|
||||
user: '',
|
||||
password: '',
|
||||
errors: { user: '', password: '', error: '' }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastfm () {
|
||||
lastfm() {
|
||||
return this.$store.state.lastfm
|
||||
},
|
||||
|
||||
spotify () {
|
||||
spotify() {
|
||||
return this.$store.state.spotify
|
||||
},
|
||||
|
||||
spotify_required_scope () {
|
||||
spotify_required_scope() {
|
||||
if (this.spotify.webapi_required_scope) {
|
||||
return this.spotify.webapi_required_scope.split(' ')
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
spotify_missing_scope () {
|
||||
if (this.spotify.webapi_token_valid && this.spotify.webapi_granted_scope && this.spotify.webapi_required_scope) {
|
||||
return this.spotify.webapi_required_scope.split(' ').filter(scope => this.spotify.webapi_granted_scope.indexOf(scope) < 0)
|
||||
spotify_missing_scope() {
|
||||
if (
|
||||
this.spotify.webapi_token_valid &&
|
||||
this.spotify.webapi_granted_scope &&
|
||||
this.spotify.webapi_required_scope
|
||||
) {
|
||||
return this.spotify.webapi_required_scope
|
||||
.split(' ')
|
||||
.filter(
|
||||
(scope) => this.spotify.webapi_granted_scope.indexOf(scope) < 0
|
||||
)
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
use_libspotify () {
|
||||
use_libspotify() {
|
||||
return this.$store.state.config.use_libspotify
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
login_libspotify () {
|
||||
webapi.spotify_login(this.libspotify).then(response => {
|
||||
login_libspotify() {
|
||||
webapi.spotify_login(this.libspotify).then((response) => {
|
||||
this.libspotify.user = ''
|
||||
this.libspotify.password = ''
|
||||
this.libspotify.errors.user = ''
|
||||
@@ -181,12 +273,12 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
logout_spotify () {
|
||||
logout_spotify() {
|
||||
webapi.spotify_logout()
|
||||
},
|
||||
|
||||
login_lastfm () {
|
||||
webapi.lastfm_login(this.lastfm_login).then(response => {
|
||||
login_lastfm() {
|
||||
webapi.lastfm_login(this.lastfm_login).then((response) => {
|
||||
this.lastfm_login.user = ''
|
||||
this.lastfm_login.password = ''
|
||||
this.lastfm_login.errors.user = ''
|
||||
@@ -201,18 +293,11 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
logoutLastfm () {
|
||||
logoutLastfm() {
|
||||
webapi.lastfm_logout()
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
join (array) {
|
||||
return array.join(', ')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-settings></tabs-settings>
|
||||
<tabs-settings />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Remote Pairing</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<!-- Paring request active -->
|
||||
<div class="notification" v-if="pairing.active">
|
||||
<form v-on:submit.prevent="kickoff_pairing">
|
||||
<div v-if="pairing.active" class="notification">
|
||||
<form @submit.prevent="kickoff_pairing">
|
||||
<label class="label has-text-weight-normal">
|
||||
Remote pairing request from <b>{{ pairing.remote }}</b>
|
||||
</label>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Enter pairing code" v-model="pairing_req.pin">
|
||||
<input
|
||||
v-model="pairing_req.pin"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Enter pairing code"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-info" type="submit">Send</button>
|
||||
@@ -25,34 +30,49 @@
|
||||
</form>
|
||||
</div>
|
||||
<!-- No pairing requests -->
|
||||
<div class="content" v-if="!pairing.active">
|
||||
<div v-if="!pairing.active" class="content">
|
||||
<p>No active pairing request.</p>
|
||||
</div>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Speaker pairing and device verification</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p class="content">
|
||||
If your speaker requires pairing then activate it below and enter the PIN that it displays.
|
||||
If your speaker requires pairing then activate it below and enter the
|
||||
PIN that it displays.
|
||||
</p>
|
||||
|
||||
<div v-for="output in outputs" :key="output.id">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="output.selected" @change="output_toggle(output.id)"> {{ output.name }}
|
||||
<input
|
||||
v-model="output.selected"
|
||||
type="checkbox"
|
||||
@change="output_toggle(output.id)"
|
||||
/>
|
||||
{{ output.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<form @submit.prevent="kickoff_verification(output.id)" v-if="output.needs_auth_key" class="fd-has-margin-bottom">
|
||||
<form
|
||||
v-if="output.needs_auth_key"
|
||||
class="fd-has-margin-bottom"
|
||||
@submit.prevent="kickoff_verification(output.id)"
|
||||
>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Enter verification code" v-model="verification_req.pin">
|
||||
<input
|
||||
v-model="verification_req.pin"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Enter verification code"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-info" type="submit">Verify</button>
|
||||
@@ -74,7 +94,9 @@ export default {
|
||||
name: 'SettingsPageRemotesOutputs',
|
||||
components: { ContentWithHeading, TabsSettings },
|
||||
|
||||
data () {
|
||||
filters: {},
|
||||
|
||||
data() {
|
||||
return {
|
||||
pairing_req: { pin: '' },
|
||||
verification_req: { pin: '' }
|
||||
@@ -82,33 +104,29 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
pairing () {
|
||||
pairing() {
|
||||
return this.$store.state.pairing
|
||||
},
|
||||
|
||||
outputs () {
|
||||
outputs() {
|
||||
return this.$store.state.outputs
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
kickoff_pairing () {
|
||||
kickoff_pairing() {
|
||||
webapi.pairing_kickoff(this.pairing_req)
|
||||
},
|
||||
|
||||
output_toggle (outputId) {
|
||||
output_toggle(outputId) {
|
||||
webapi.output_toggle(outputId)
|
||||
},
|
||||
|
||||
kickoff_verification (outputId) {
|
||||
kickoff_verification(outputId) {
|
||||
webapi.output_update(outputId, this.verification_req)
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,80 +1,112 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-settings></tabs-settings>
|
||||
<tabs-settings />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Navbar items</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<p class="content">
|
||||
Select the top navigation bar menu items
|
||||
</p>
|
||||
<template #content>
|
||||
<p class="content">Select the top navigation bar menu items</p>
|
||||
<div class="notification is-size-7">
|
||||
If you select more items than can be shown on your screen then the burger menu will disappear.
|
||||
If you select more items than can be shown on your screen then the
|
||||
burger menu will disappear.
|
||||
</div>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_playlists">
|
||||
<template v-slot:label> Playlists</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_playlists"
|
||||
>
|
||||
<template #label> Playlists </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_music">
|
||||
<template v-slot:label> Music</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_music"
|
||||
>
|
||||
<template #label> Music </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_podcasts">
|
||||
<template v-slot:label> Podcasts</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_podcasts"
|
||||
>
|
||||
<template #label> Podcasts </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_audiobooks">
|
||||
<template v-slot:label> Audiobooks</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_audiobooks"
|
||||
>
|
||||
<template #label> Audiobooks </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_radio">
|
||||
<template v-slot:label> Radio</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_radio"
|
||||
>
|
||||
<template #label> Radio </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_files">
|
||||
<template v-slot:label> Files</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_files"
|
||||
>
|
||||
<template #label> Files </template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_menu_item_search">
|
||||
<template v-slot:label> Search</template>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_menu_item_search"
|
||||
>
|
||||
<template #label> Search </template>
|
||||
</settings-checkbox>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Album lists</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_cover_artwork_in_album_lists">
|
||||
<template v-slot:label> Show cover artwork in album list</template>
|
||||
<template #content>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_cover_artwork_in_album_lists"
|
||||
>
|
||||
<template #label> Show cover artwork in album list </template>
|
||||
</settings-checkbox>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Now playing page</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<settings-checkbox category_name="webinterface" option_name="show_composer_now_playing">
|
||||
<template v-slot:label> Show composer</template>
|
||||
<template v-slot:info>If enabled the composer of the current playing track is shown on the "now playing page"</template>
|
||||
<template #content>
|
||||
<settings-checkbox
|
||||
category_name="webinterface"
|
||||
option_name="show_composer_now_playing"
|
||||
>
|
||||
<template #label> Show composer </template>
|
||||
<template #info>
|
||||
If enabled the composer of the current playing track is shown on the
|
||||
"now playing page"
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
<settings-textfield category_name="webinterface" option_name="show_composer_for_genre"
|
||||
:disabled="!settings_option_show_composer_now_playing"
|
||||
placeholder="Genres">
|
||||
<template v-slot:label>Show composer only for listed genres</template>
|
||||
<template v-slot:info>
|
||||
<settings-textfield
|
||||
category_name="webinterface"
|
||||
option_name="show_composer_for_genre"
|
||||
:disabled="!settings_option_show_composer_now_playing"
|
||||
placeholder="Genres"
|
||||
>
|
||||
<template #label> Show composer only for listed genres </template>
|
||||
<template #info>
|
||||
<p class="help">
|
||||
Comma separated list of genres the composer should be displayed on the "now playing page".
|
||||
Comma separated list of genres the composer should be displayed on
|
||||
the "now playing page".
|
||||
</p>
|
||||
<p class="help">Leave empty to always show the composer.</p>
|
||||
<p class="help">
|
||||
Leave empty to always show the composer.
|
||||
</p>
|
||||
<p class="help">
|
||||
The genre tag of the current track is matched by checking, if one of the defined genres are included.
|
||||
For example setting to <code>classical, soundtrack</code> will show the composer for tracks with
|
||||
a genre tag of "Contemporary Classical".<br>
|
||||
The genre tag of the current track is matched by checking, if one
|
||||
of the defined genres are included. For example setting to
|
||||
<code>classical, soundtrack</code> will show the composer for
|
||||
tracks with a genre tag of "Contemporary Classical".<br />
|
||||
</p>
|
||||
</template>
|
||||
</settings-textfield>
|
||||
@@ -82,13 +114,18 @@
|
||||
</content-with-heading>
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">Recently added page</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<settings-intfield category_name="webinterface" option_name="recently_added_limit">
|
||||
<template v-slot:label>Limit the number of albums shown on the "Recently Added" page</template>
|
||||
<template #content>
|
||||
<settings-intfield
|
||||
category_name="webinterface"
|
||||
option_name="recently_added_limit"
|
||||
>
|
||||
<template #label>
|
||||
Limit the number of albums shown on the "Recently Added" page
|
||||
</template>
|
||||
</settings-intfield>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
@@ -104,15 +141,20 @@ import SettingsIntfield from '@/components/SettingsIntfield.vue'
|
||||
|
||||
export default {
|
||||
name: 'SettingsPageWebinterface',
|
||||
components: { ContentWithHeading, TabsSettings, SettingsCheckbox, SettingsTextfield, SettingsIntfield },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsSettings,
|
||||
SettingsCheckbox,
|
||||
SettingsTextfield,
|
||||
SettingsIntfield
|
||||
},
|
||||
|
||||
computed: {
|
||||
settings_option_show_composer_now_playing () {
|
||||
settings_option_show_composer_now_playing() {
|
||||
return this.$store.getters.settings_option_show_composer_now_playing
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,40 +1,73 @@
|
||||
<template>
|
||||
<content-with-hero>
|
||||
<template v-slot:heading-left>
|
||||
<h1 class="title is-5">{{ album.name }}</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"><a class="has-text-link" @click="open_artist">{{ album.artists[0].name }}</a></h2>
|
||||
<template #heading-left>
|
||||
<h1 class="title is-5">
|
||||
{{ album.name }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
|
||||
<a class="has-text-link" @click="open_artist">{{
|
||||
album.artists[0].name
|
||||
}}</a>
|
||||
</h2>
|
||||
|
||||
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
<a class="button is-small is-light is-rounded" @click="show_album_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_album_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<p class="image is-square fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="artwork_url"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
@click="show_album_details_modal = true" />
|
||||
@click="show_album_details_modal = true"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-slot:content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">{{ album.tracks.total }} tracks</p>
|
||||
<spotify-list-item-track v-for="(track, index) in album.tracks.items" :key="track.id" :track="track" :position="index" :album="album" :context_uri="album.uri">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
|
||||
{{ album.tracks.total }} tracks
|
||||
</p>
|
||||
<spotify-list-item-track
|
||||
v-for="(track, index) in album.tracks.items"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
:position="index"
|
||||
:album="album"
|
||||
:context_uri="album.uri"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_track_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-track>
|
||||
<spotify-modal-dialog-track :show="show_track_details_modal" :track="selected_track" :album="album" @close="show_track_details_modal = false" />
|
||||
<spotify-modal-dialog-album :show="show_album_details_modal" :album="album" @close="show_album_details_modal = false" />
|
||||
<spotify-modal-dialog-track
|
||||
:show="show_track_details_modal"
|
||||
:track="selected_track"
|
||||
:album="album"
|
||||
@close="show_track_details_modal = false"
|
||||
/>
|
||||
<spotify-modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="album"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-hero>
|
||||
</template>
|
||||
@@ -53,7 +86,9 @@ const dataObject = {
|
||||
load: function (to) {
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
return spotifyApi.getAlbum(to.params.album_id, { market: store.state.spotify.webapi_country })
|
||||
return spotifyApi.getAlbum(to.params.album_id, {
|
||||
market: store.state.spotify.webapi_country
|
||||
})
|
||||
},
|
||||
|
||||
set: function (vm, response) {
|
||||
@@ -63,9 +98,28 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'PageAlbum',
|
||||
components: { ContentWithHero, SpotifyListItemTrack, SpotifyModalDialogTrack, SpotifyModalDialogAlbum, CoverArtwork },
|
||||
components: {
|
||||
ContentWithHero,
|
||||
SpotifyListItemTrack,
|
||||
SpotifyModalDialogTrack,
|
||||
SpotifyModalDialogAlbum,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
album: { artists: [{}], tracks: {} },
|
||||
|
||||
@@ -87,7 +141,9 @@ export default {
|
||||
|
||||
methods: {
|
||||
open_artist: function () {
|
||||
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
|
||||
this.$router.push({
|
||||
path: '/music/spotify/artists/' + this.album.artists[0].id
|
||||
})
|
||||
},
|
||||
|
||||
play: function () {
|
||||
@@ -99,22 +155,8 @@ export default {
|
||||
this.selected_track = track
|
||||
this.show_track_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,43 +1,66 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<p class="title is-4">{{ artist.name }}</p>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">
|
||||
{{ artist.name }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_artist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_artist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">{{ total }} albums</p>
|
||||
<spotify-list-item-album v-for="album in albums"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template v-slot:artwork v-if="is_visible_artwork">
|
||||
<spotify-list-item-album
|
||||
v-for="album in albums"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)"
|
||||
>
|
||||
<template v-if="is_visible_artwork" #artwork>
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="artwork_url(album)"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
:maxwidth="64"
|
||||
:maxheight="64" />
|
||||
:maxheight="64"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click.prevent.stop="open_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-album>
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
|
||||
<spotify-modal-dialog-artist :show="show_artist_details_modal" :artist="artist" @close="show_artist_details_modal = false" />
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next">
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-album
|
||||
:show="show_details_modal"
|
||||
:album="selected_album"
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
<spotify-modal-dialog-artist
|
||||
:show="show_artist_details_modal"
|
||||
:artist="artist"
|
||||
@close="show_artist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -61,7 +84,12 @@ const dataObject = {
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
return Promise.all([
|
||||
spotifyApi.getArtist(to.params.artist_id),
|
||||
spotifyApi.getArtistAlbums(to.params.artist_id, { limit: PAGE_SIZE, offset: 0, include_groups: 'album,single', market: store.state.spotify.webapi_country })
|
||||
spotifyApi.getArtistAlbums(to.params.artist_id, {
|
||||
limit: PAGE_SIZE,
|
||||
offset: 0,
|
||||
include_groups: 'album,single',
|
||||
market: store.state.spotify.webapi_country
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
@@ -77,9 +105,29 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPageArtist',
|
||||
components: { ContentWithHeading, SpotifyListItemAlbum, SpotifyModalDialogAlbum, SpotifyModalDialogArtist, VueEternalLoading, CoverArtwork },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
SpotifyListItemAlbum,
|
||||
SpotifyModalDialogAlbum,
|
||||
SpotifyModalDialogArtist,
|
||||
VueEternalLoading,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
artist: {},
|
||||
albums: [],
|
||||
@@ -94,8 +142,11 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
is_visible_artwork () {
|
||||
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
|
||||
is_visible_artwork() {
|
||||
return this.$store.getters.settings_option(
|
||||
'webinterface',
|
||||
'show_cover_artwork_in_album_lists'
|
||||
).value
|
||||
}
|
||||
},
|
||||
|
||||
@@ -103,10 +154,16 @@ export default {
|
||||
load_next: function ({ loaded }) {
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
|
||||
spotifyApi.getArtistAlbums(this.artist.id, { limit: PAGE_SIZE, offset: this.offset, include_groups: 'album,single' }).then(data => {
|
||||
this.append_albums(data)
|
||||
loaded(data.items.length, PAGE_SIZE)
|
||||
})
|
||||
spotifyApi
|
||||
.getArtistAlbums(this.artist.id, {
|
||||
limit: PAGE_SIZE,
|
||||
offset: this.offset,
|
||||
include_groups: 'album,single'
|
||||
})
|
||||
.then((data) => {
|
||||
this.append_albums(data)
|
||||
loaded(data.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
|
||||
append_albums: function (data) {
|
||||
@@ -135,22 +192,8 @@ export default {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,39 +1,51 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<!-- New Releases -->
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">New Releases</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-album v-for="album in new_releases"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template v-slot:artwork v-if="is_visible_artwork">
|
||||
<template #content>
|
||||
<spotify-list-item-album
|
||||
v-for="album in new_releases"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)"
|
||||
>
|
||||
<template v-if="is_visible_artwork" #artwork>
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="artwork_url(album)"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
:maxwidth="64"
|
||||
:maxheight="64" />
|
||||
:maxheight="64"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_album_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-album>
|
||||
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
|
||||
<spotify-modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="selected_album"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<router-link to="/music/spotify/new-releases" class="button is-light is-small is-rounded">
|
||||
<router-link
|
||||
to="/music/spotify/new-releases"
|
||||
class="button is-light is-small is-rounded"
|
||||
>
|
||||
Show more
|
||||
</router-link>
|
||||
</p>
|
||||
@@ -43,23 +55,36 @@
|
||||
|
||||
<!-- Featured Playlists -->
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Featured Playlists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<spotify-list-item-playlist
|
||||
v-for="playlist in featured_playlists"
|
||||
:key="playlist.id"
|
||||
:playlist="playlist"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_playlist_dialog(playlist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-playlist>
|
||||
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
|
||||
<spotify-modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="selected_playlist"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<router-link to="/music/spotify/featured-playlists" class="button is-light is-small is-rounded">
|
||||
<router-link
|
||||
to="/music/spotify/featured-playlists"
|
||||
class="button is-light is-small is-rounded"
|
||||
>
|
||||
Show more
|
||||
</router-link>
|
||||
</p>
|
||||
@@ -83,31 +108,64 @@ import SpotifyWebApi from 'spotify-web-api-js'
|
||||
|
||||
const dataObject = {
|
||||
load: function (to) {
|
||||
if (store.state.spotify_new_releases.length > 0 && store.state.spotify_featured_playlists.length > 0) {
|
||||
if (
|
||||
store.state.spotify_new_releases.length > 0 &&
|
||||
store.state.spotify_featured_playlists.length > 0
|
||||
) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
return Promise.all([
|
||||
spotifyApi.getNewReleases({ country: store.state.spotify.webapi_country, limit: 50 }),
|
||||
spotifyApi.getFeaturedPlaylists({ country: store.state.spotify.webapi_country, limit: 50 })
|
||||
spotifyApi.getNewReleases({
|
||||
country: store.state.spotify.webapi_country,
|
||||
limit: 50
|
||||
}),
|
||||
spotifyApi.getFeaturedPlaylists({
|
||||
country: store.state.spotify.webapi_country,
|
||||
limit: 50
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
set: function (vm, response) {
|
||||
if (response) {
|
||||
store.commit(types.SPOTIFY_NEW_RELEASES, response[0].albums.items)
|
||||
store.commit(types.SPOTIFY_FEATURED_PLAYLISTS, response[1].playlists.items)
|
||||
store.commit(
|
||||
types.SPOTIFY_FEATURED_PLAYLISTS,
|
||||
response[1].playlists.items
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPageBrowse',
|
||||
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, CoverArtwork },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
SpotifyListItemAlbum,
|
||||
SpotifyListItemPlaylist,
|
||||
SpotifyModalDialogAlbum,
|
||||
SpotifyModalDialogPlaylist,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show_album_details_modal: false,
|
||||
selected_album: {},
|
||||
@@ -118,21 +176,23 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
new_releases () {
|
||||
new_releases() {
|
||||
return this.$store.state.spotify_new_releases.slice(0, 3)
|
||||
},
|
||||
|
||||
featured_playlists () {
|
||||
featured_playlists() {
|
||||
return this.$store.state.spotify_featured_playlists.slice(0, 3)
|
||||
},
|
||||
|
||||
is_visible_artwork () {
|
||||
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
|
||||
is_visible_artwork() {
|
||||
return this.$store.getters.settings_option(
|
||||
'webinterface',
|
||||
'show_cover_artwork_in_album_lists'
|
||||
).value
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
open_album: function (album) {
|
||||
this.$router.push({ path: '/music/spotify/albums/' + album.id })
|
||||
},
|
||||
@@ -153,22 +213,8 @@ export default {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Featured Playlists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<spotify-list-item-playlist
|
||||
v-for="playlist in featured_playlists"
|
||||
:key="playlist.id"
|
||||
:playlist="playlist"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_playlist_dialog(playlist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-playlist>
|
||||
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
|
||||
<spotify-modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="selected_playlist"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -37,7 +47,10 @@ const dataObject = {
|
||||
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
spotifyApi.getFeaturedPlaylists({ country: store.state.spotify.webapi_country, limit: 50 })
|
||||
spotifyApi.getFeaturedPlaylists({
|
||||
country: store.state.spotify.webapi_country,
|
||||
limit: 50
|
||||
})
|
||||
},
|
||||
|
||||
set: function (vm, response) {
|
||||
@@ -49,9 +62,27 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPageBrowseFeaturedPlaylists',
|
||||
components: { ContentWithHeading, TabsMusic, SpotifyListItemPlaylist, SpotifyModalDialogPlaylist },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
SpotifyListItemPlaylist,
|
||||
SpotifyModalDialogPlaylist
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show_playlist_details_modal: false,
|
||||
selected_playlist: {}
|
||||
@@ -59,7 +90,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
featured_playlists () {
|
||||
featured_playlists() {
|
||||
return this.$store.state.spotify_featured_playlists
|
||||
}
|
||||
},
|
||||
@@ -69,22 +100,8 @@ export default {
|
||||
this.selected_playlist = playlist
|
||||
this.show_playlist_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,33 +1,42 @@
|
||||
<template>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music></tabs-music>
|
||||
<tabs-music />
|
||||
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">New Releases</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-album v-for="album in new_releases"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template v-slot:artwork v-if="is_visible_artwork">
|
||||
<template #content>
|
||||
<spotify-list-item-album
|
||||
v-for="album in new_releases"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)"
|
||||
>
|
||||
<template v-if="is_visible_artwork" #artwork>
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="artwork_url(album)"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
:maxwidth="64"
|
||||
:maxheight="64" />
|
||||
:maxheight="64"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_album_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-album>
|
||||
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
|
||||
<spotify-modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="selected_album"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</div>
|
||||
@@ -51,7 +60,10 @@ const dataObject = {
|
||||
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
return spotifyApi.getNewReleases({ country: store.state.spotify.webapi_country, limit: 50 })
|
||||
return spotifyApi.getNewReleases({
|
||||
country: store.state.spotify.webapi_country,
|
||||
limit: 50
|
||||
})
|
||||
},
|
||||
|
||||
set: function (vm, response) {
|
||||
@@ -63,9 +75,28 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPageBrowseNewReleases',
|
||||
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyModalDialogAlbum, CoverArtwork },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
TabsMusic,
|
||||
SpotifyListItemAlbum,
|
||||
SpotifyModalDialogAlbum,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show_album_details_modal: false,
|
||||
selected_album: {}
|
||||
@@ -73,17 +104,19 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
new_releases () {
|
||||
new_releases() {
|
||||
return this.$store.state.spotify_new_releases
|
||||
},
|
||||
|
||||
is_visible_artwork () {
|
||||
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
|
||||
is_visible_artwork() {
|
||||
return this.$store.getters.settings_option(
|
||||
'webinterface',
|
||||
'show_cover_artwork_in_album_lists'
|
||||
).value
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
open_album: function (album) {
|
||||
this.$router.push({ path: '/music/spotify/albums/' + album.id })
|
||||
},
|
||||
@@ -99,22 +132,8 @@ export default {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
<template>
|
||||
<content-with-heading>
|
||||
<template v-slot:heading-left>
|
||||
<div class="title is-4">{{ playlist.name }}</div>
|
||||
<template #heading-left>
|
||||
<div class="title is-4">
|
||||
{{ playlist.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:heading-right>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-small is-light is-rounded" @click="show_playlist_details_modal = true">
|
||||
<span class="icon"><i class="mdi mdi-dots-horizontal mdi-18px"></i></span>
|
||||
<a
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_playlist_details_modal = true"
|
||||
>
|
||||
<span class="icon"
|
||||
><i class="mdi mdi-dots-horizontal mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
|
||||
<span class="icon"><i class="mdi mdi-shuffle" /></span>
|
||||
<span>Shuffle</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<p class="heading has-text-centered-mobile">{{ playlist.tracks.total }} tracks</p>
|
||||
<spotify-list-item-track v-for="(item, index) in tracks" :key="item.track.id" :track="item.track" :album="item.track.album" :position="index" :context_uri="playlist.uri">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
{{ playlist.tracks.total }} tracks
|
||||
</p>
|
||||
<spotify-list-item-track
|
||||
v-for="(item, index) in tracks"
|
||||
:key="item.track.id"
|
||||
:track="item.track"
|
||||
:album="item.track.album"
|
||||
:position="index"
|
||||
:context_uri="playlist.uri"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_track_dialog(item.track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-track>
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-track :show="show_track_details_modal" :track="selected_track" :album="selected_track.album" @close="show_track_details_modal = false" />
|
||||
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="playlist" @close="show_playlist_details_modal = false" />
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next">
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-track
|
||||
:show="show_track_details_modal"
|
||||
:track="selected_track"
|
||||
:album="selected_track.album"
|
||||
@close="show_track_details_modal = false"
|
||||
/>
|
||||
<spotify-modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="playlist"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
</template>
|
||||
@@ -47,7 +77,10 @@ const dataObject = {
|
||||
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
|
||||
return Promise.all([
|
||||
spotifyApi.getPlaylist(to.params.playlist_id),
|
||||
spotifyApi.getPlaylistTracks(to.params.playlist_id, { limit: PAGE_SIZE, offset: 0 })
|
||||
spotifyApi.getPlaylistTracks(to.params.playlist_id, {
|
||||
limit: PAGE_SIZE,
|
||||
offset: 0
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
@@ -62,9 +95,28 @@ const dataObject = {
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPagePlaylist',
|
||||
components: { ContentWithHeading, SpotifyListItemTrack, SpotifyModalDialogTrack, SpotifyModalDialogPlaylist, VueEternalLoading },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
SpotifyListItemTrack,
|
||||
SpotifyModalDialogTrack,
|
||||
SpotifyModalDialogPlaylist,
|
||||
VueEternalLoading
|
||||
},
|
||||
|
||||
data () {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next((vm) => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
playlist: { tracks: {} },
|
||||
tracks: [],
|
||||
@@ -82,10 +134,15 @@ export default {
|
||||
load_next: function ({ loaded }) {
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
|
||||
spotifyApi.getPlaylistTracks(this.playlist.id, { limit: PAGE_SIZE, offset: this.offset }).then(data => {
|
||||
this.append_tracks(data)
|
||||
loaded(data.items.length, PAGE_SIZE)
|
||||
})
|
||||
spotifyApi
|
||||
.getPlaylistTracks(this.playlist.id, {
|
||||
limit: PAGE_SIZE,
|
||||
offset: this.offset
|
||||
})
|
||||
.then((data) => {
|
||||
this.append_tracks(data)
|
||||
loaded(data.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
|
||||
append_tracks: function (data) {
|
||||
@@ -103,22 +160,8 @@ export default {
|
||||
this.selected_track = track
|
||||
this.show_track_details_modal = true
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
dataObject.load(to).then((response) => {
|
||||
next(vm => dataObject.set(vm, response))
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
const vm = this
|
||||
dataObject.load(to).then((response) => {
|
||||
dataObject.set(vm, response)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -5,155 +5,247 @@
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths">
|
||||
<form v-on:submit.prevent="new_search">
|
||||
<form @submit.prevent="new_search">
|
||||
<div class="field">
|
||||
<p class="control is-expanded has-icons-left">
|
||||
<input class="input is-rounded is-shadowless" type="text" placeholder="Search" v-model="search_query" ref="search_field" autocomplete="off">
|
||||
<input
|
||||
ref="search_field"
|
||||
v-model="search_query"
|
||||
class="input is-rounded is-shadowless"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<i class="mdi mdi-magnify" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div class="tags" style="margin-top: 16px;">
|
||||
<a class="tag" v-for="recent_search in recent_searches" :key="recent_search" @click="open_recent_search(recent_search)">{{ recent_search }}</a>
|
||||
<div class="tags" style="margin-top: 16px">
|
||||
<a
|
||||
v-for="recent_search in recent_searches"
|
||||
:key="recent_search"
|
||||
class="tag"
|
||||
@click="open_recent_search(recent_search)"
|
||||
>{{ recent_search }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<tabs-search :query="search_query"></tabs-search>
|
||||
<tabs-search :query="search_query" />
|
||||
|
||||
<!-- Tracks -->
|
||||
<content-with-heading v-if="show_tracks && tracks.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Tracks</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-track v-for="track in tracks.items" :key="track.id" :track="track" :album="track.album" :position="0" :context_uri="track.uri">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<spotify-list-item-track
|
||||
v-for="track in tracks.items"
|
||||
:key="track.id"
|
||||
:track="track"
|
||||
:album="track.album"
|
||||
:position="0"
|
||||
:context_uri="track.uri"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_track_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-track>
|
||||
<VueEternalLoading v-if="query.type === 'track'" :load="search_tracks_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-track :show="show_track_details_modal" :track="selected_track" :album="selected_track.album" @close="show_track_details_modal = false" />
|
||||
<VueEternalLoading
|
||||
v-if="query.type === 'track'"
|
||||
:load="search_tracks_next"
|
||||
>
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-track
|
||||
:show="show_track_details_modal"
|
||||
:track="selected_track"
|
||||
:album="selected_track.album"
|
||||
@close="show_track_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_tracks_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_tracks">Show all {{ tracks.total.toLocaleString() }} tracks</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_tracks"
|
||||
>Show all {{ tracks.total.toLocaleString() }} tracks</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No tracks found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Artists -->
|
||||
<content-with-heading v-if="show_artists && artists.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Artists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<spotify-list-item-artist
|
||||
v-for="artist in artists.items"
|
||||
:key="artist.id"
|
||||
:artist="artist"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_artist_dialog(artist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-artist>
|
||||
<VueEternalLoading v-if="query.type === 'artist'" :load="search_artists_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-artist :show="show_artist_details_modal" :artist="selected_artist" @close="show_artist_details_modal = false" />
|
||||
<VueEternalLoading
|
||||
v-if="query.type === 'artist'"
|
||||
:load="search_artists_next"
|
||||
>
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-artist
|
||||
:show="show_artist_details_modal"
|
||||
:artist="selected_artist"
|
||||
@close="show_artist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_artists_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_artists">Show all {{ artists.total.toLocaleString() }} artists</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_artists"
|
||||
>Show all {{ artists.total.toLocaleString() }} artists</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_artists && !artists.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No artists found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Albums -->
|
||||
<content-with-heading v-if="show_albums && albums.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Albums</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-album v-for="album in albums.items"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template v-slot:artwork v-if="is_visible_artwork">
|
||||
<template #content>
|
||||
<spotify-list-item-album
|
||||
v-for="album in albums.items"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)"
|
||||
>
|
||||
<template v-if="is_visible_artwork" #artwork>
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="artwork_url(album)"
|
||||
:artist="album.artist"
|
||||
:album="album.name"
|
||||
:maxwidth="64"
|
||||
:maxheight="64" />
|
||||
:maxheight="64"
|
||||
/>
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<a @click="open_album_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-album>
|
||||
<VueEternalLoading v-if="query.type === 'album'" :load="search_albums_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
|
||||
<VueEternalLoading
|
||||
v-if="query.type === 'album'"
|
||||
:load="search_albums_next"
|
||||
>
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-album
|
||||
:show="show_album_details_modal"
|
||||
:album="selected_album"
|
||||
@close="show_album_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_albums_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_albums">Show all {{ albums.total.toLocaleString() }} albums</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_albums"
|
||||
>Show all {{ albums.total.toLocaleString() }} albums</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_albums && !albums.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No albums found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
|
||||
<!-- Playlists -->
|
||||
<content-with-heading v-if="show_playlists && playlists.total">
|
||||
<template v-slot:heading-left>
|
||||
<template #heading-left>
|
||||
<p class="title is-4">Playlists</p>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<spotify-list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist">
|
||||
<template v-slot:actions>
|
||||
<template #content>
|
||||
<spotify-list-item-playlist
|
||||
v-for="playlist in playlists.items"
|
||||
:key="playlist.id"
|
||||
:playlist="playlist"
|
||||
>
|
||||
<template #actions>
|
||||
<a @click="open_playlist_dialog(playlist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
<span class="icon has-text-dark"
|
||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||
/></span>
|
||||
</a>
|
||||
</template>
|
||||
</spotify-list-item-playlist>
|
||||
<VueEternalLoading v-if="query.type === 'playlist'" :load="search_playlists_next"><template #no-more>.</template></VueEternalLoading>
|
||||
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
|
||||
<VueEternalLoading
|
||||
v-if="query.type === 'playlist'"
|
||||
:load="search_playlists_next"
|
||||
>
|
||||
<template #no-more> . </template>
|
||||
</VueEternalLoading>
|
||||
<spotify-modal-dialog-playlist
|
||||
:show="show_playlist_details_modal"
|
||||
:playlist="selected_playlist"
|
||||
@close="show_playlist_details_modal = false"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<template #footer>
|
||||
<nav v-if="show_all_playlists_button" class="level">
|
||||
<p class="level-item">
|
||||
<a class="button is-light is-small is-rounded" v-on:click="open_search_playlists">Show all {{ playlists.total.toLocaleString() }} playlists</a>
|
||||
<a
|
||||
class="button is-light is-small is-rounded"
|
||||
@click="open_search_playlists"
|
||||
>Show all {{ playlists.total.toLocaleString() }} playlists</a
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-text v-if="show_playlists && !playlists.total">
|
||||
<template v-slot:content>
|
||||
<template #content>
|
||||
<p><i>No playlists found</i></p>
|
||||
</template>
|
||||
</content-text>
|
||||
@@ -182,9 +274,23 @@ const PAGE_SIZE = 50
|
||||
|
||||
export default {
|
||||
name: 'SpotifyPageSearch',
|
||||
components: { ContentWithHeading, ContentText, TabsSearch, SpotifyListItemTrack, SpotifyListItemArtist, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogTrack, SpotifyModalDialogArtist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, VueEternalLoading, CoverArtwork },
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ContentText,
|
||||
TabsSearch,
|
||||
SpotifyListItemTrack,
|
||||
SpotifyListItemArtist,
|
||||
SpotifyListItemAlbum,
|
||||
SpotifyListItemPlaylist,
|
||||
SpotifyModalDialogTrack,
|
||||
SpotifyModalDialogArtist,
|
||||
SpotifyModalDialogAlbum,
|
||||
SpotifyModalDialogPlaylist,
|
||||
VueEternalLoading,
|
||||
CoverArtwork
|
||||
},
|
||||
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
search_query: '',
|
||||
tracks: { items: [], total: 0 },
|
||||
@@ -212,43 +318,62 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
recent_searches () {
|
||||
return this.$store.state.recent_searches.filter(search => !search.startsWith('query:'))
|
||||
recent_searches() {
|
||||
return this.$store.state.recent_searches.filter(
|
||||
(search) => !search.startsWith('query:')
|
||||
)
|
||||
},
|
||||
|
||||
show_tracks () {
|
||||
show_tracks() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('track')
|
||||
},
|
||||
show_all_tracks_button () {
|
||||
show_all_tracks_button() {
|
||||
return this.tracks.total > this.tracks.items.length
|
||||
},
|
||||
|
||||
show_artists () {
|
||||
show_artists() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('artist')
|
||||
},
|
||||
show_all_artists_button () {
|
||||
show_all_artists_button() {
|
||||
return this.artists.total > this.artists.items.length
|
||||
},
|
||||
|
||||
show_albums () {
|
||||
show_albums() {
|
||||
return this.$route.query.type && this.$route.query.type.includes('album')
|
||||
},
|
||||
show_all_albums_button () {
|
||||
show_all_albums_button() {
|
||||
return this.albums.total > this.albums.items.length
|
||||
},
|
||||
|
||||
show_playlists () {
|
||||
return this.$route.query.type && this.$route.query.type.includes('playlist')
|
||||
show_playlists() {
|
||||
return (
|
||||
this.$route.query.type && this.$route.query.type.includes('playlist')
|
||||
)
|
||||
},
|
||||
show_all_playlists_button () {
|
||||
show_all_playlists_button() {
|
||||
return this.playlists.total > this.playlists.items.length
|
||||
},
|
||||
|
||||
is_visible_artwork () {
|
||||
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
|
||||
is_visible_artwork() {
|
||||
return this.$store.getters.settings_option(
|
||||
'webinterface',
|
||||
'show_cover_artwork_in_album_lists'
|
||||
).value
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
this.query = to.query
|
||||
this.search()
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
this.query = this.$route.query
|
||||
this.search()
|
||||
},
|
||||
|
||||
methods: {
|
||||
reset: function () {
|
||||
this.tracks = { items: [], total: 0 }
|
||||
@@ -261,7 +386,11 @@ export default {
|
||||
this.reset()
|
||||
|
||||
// If no search query present reset and focus search field
|
||||
if (!this.query.query || this.query.query === '' || this.query.query.startsWith('query:')) {
|
||||
if (
|
||||
!this.query.query ||
|
||||
this.query.query === '' ||
|
||||
this.query.query.startsWith('query:')
|
||||
) {
|
||||
this.search_query = ''
|
||||
this.$refs.search_field.focus()
|
||||
return
|
||||
@@ -283,56 +412,60 @@ export default {
|
||||
const spotifyApi = new SpotifyWebApi()
|
||||
spotifyApi.setAccessToken(data.webapi_token)
|
||||
|
||||
const types = this.query.type.split(',').filter(type => this.validSearchTypes.includes(type))
|
||||
const types = this.query.type
|
||||
.split(',')
|
||||
.filter((type) => this.validSearchTypes.includes(type))
|
||||
return spotifyApi.search(this.query.query, types, this.search_param)
|
||||
})
|
||||
},
|
||||
|
||||
search_all: function () {
|
||||
this.spotify_search().then(data => {
|
||||
this.spotify_search().then((data) => {
|
||||
this.tracks = data.tracks ? data.tracks : { items: [], total: 0 }
|
||||
this.artists = data.artists ? data.artists : { items: [], total: 0 }
|
||||
this.albums = data.albums ? data.albums : { items: [], total: 0 }
|
||||
this.playlists = data.playlists ? data.playlists : { items: [], total: 0 }
|
||||
this.playlists = data.playlists
|
||||
? data.playlists
|
||||
: { items: [], total: 0 }
|
||||
})
|
||||
},
|
||||
|
||||
search_tracks_next: function ({ loaded }) {
|
||||
this.spotify_search().then(data => {
|
||||
this.spotify_search().then((data) => {
|
||||
this.tracks.items = this.tracks.items.concat(data.tracks.items)
|
||||
this.tracks.total = data.tracks.total
|
||||
this.search_param.offset += data.tracks.limit
|
||||
|
||||
|
||||
loaded(data.tracks.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
|
||||
search_artists_next: function ({ loaded }) {
|
||||
this.spotify_search().then(data => {
|
||||
this.spotify_search().then((data) => {
|
||||
this.artists.items = this.artists.items.concat(data.artists.items)
|
||||
this.artists.total = data.artists.total
|
||||
this.search_param.offset += data.artists.limit
|
||||
|
||||
|
||||
loaded(data.artists.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
|
||||
search_albums_next: function ({ loaded }) {
|
||||
this.spotify_search().then(data => {
|
||||
this.spotify_search().then((data) => {
|
||||
this.albums.items = this.albums.items.concat(data.albums.items)
|
||||
this.albums.total = data.albums.total
|
||||
this.search_param.offset += data.albums.limit
|
||||
|
||||
|
||||
loaded(data.albums.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
|
||||
search_playlists_next: function ({ loaded }) {
|
||||
this.spotify_search().then(data => {
|
||||
this.spotify_search().then((data) => {
|
||||
this.playlists.items = this.playlists.items.concat(data.playlists.items)
|
||||
this.playlists.total = data.playlists.total
|
||||
this.search_param.offset += data.playlists.limit
|
||||
|
||||
|
||||
loaded(data.playlists.items.length, PAGE_SIZE)
|
||||
})
|
||||
},
|
||||
@@ -429,21 +562,8 @@ export default {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
this.query = this.$route.query
|
||||
this.search()
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route' (to, from) {
|
||||
this.query = to.query
|
||||
this.search()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
Reference in New Issue
Block a user