[web] Format web sources with prettier and run fix linting errors

This commit is contained in:
chme
2022-02-19 06:39:14 +01:00
parent d7f1c13585
commit c78f861f45
116 changed files with 5274 additions and 2887 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 &quot;now playing page&quot;</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
&quot;now playing page&quot;
</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 &quot;now playing page&quot;.
Comma separated list of genres the composer should be displayed on
the &quot;now playing page&quot;.
</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 &quot;Contemporary Classical&quot;.<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 &quot;Contemporary Classical&quot;.<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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>