[web] Migration to Vue 3 and Vite

This commit is contained in:
chme
2022-02-19 06:18:01 +01:00
parent 92279ef33d
commit de097fcf94
104 changed files with 2904 additions and 36569 deletions

View File

@@ -34,27 +34,27 @@
<tbody>
<tr>
<th>Artists</th>
<td class="has-text-right">{{ library.artists | number }}</td>
<td class="has-text-right">{{ $filters.number(library.artists) }}</td>
</tr>
<tr>
<th>Albums</th>
<td class="has-text-right">{{ library.albums | number }}</td>
<td class="has-text-right">{{ $filters.number(library.albums) }}</td>
</tr>
<tr>
<th>Tracks</th>
<td class="has-text-right">{{ library.songs | number }}</td>
<td class="has-text-right">{{ $filters.number(library.songs) }}</td>
</tr>
<tr>
<th>Total playtime</th>
<td class="has-text-right">{{ library.db_playtime * 1000 | duration('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">{{ library.updated_at | timeFromNow }} <span class="has-text-grey">({{ library.updated_at | time('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">{{ library.started_at | timeFromNow(true) }} <span class="has-text-grey">({{ library.started_at | time('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,7 +68,7 @@
<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">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>
@@ -107,12 +107,6 @@ export default {
showUpdateDialog () {
this.$store.commit(types.SHOW_UPDATE_DIALOG, true)
}
},
filters: {
join: function (array) {
return array.join(', ')
}
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<content-with-hero>
<template slot="heading-left">
<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>
@@ -13,7 +13,7 @@
</a>
</div>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<p class="image is-square fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="album.artwork_url"
@@ -22,7 +22,7 @@
@click="show_album_details_modal = true" />
</p>
</template>
<template slot="content">
<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" />
@@ -31,14 +31,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHero from '@/templates/ContentWithHero'
import ListTracks from '@/components/ListTracks'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHero from '@/templates/ContentWithHero.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi'
const albumData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_album(to.params.album_id),
@@ -54,7 +53,6 @@ const albumData = {
export default {
name: 'PageAlbum',
mixins: [LoadDataBeforeEnterMixin(albumData)],
components: { ContentWithHero, ListTracks, ModalDialogAlbum, CoverArtwork },
data () {
@@ -75,6 +73,19 @@ 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>

View File

@@ -1,9 +1,9 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="albums_list.indexList"></index-button-list>
<div class="columns">
@@ -30,13 +30,13 @@
</div>
</div>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Albums</p>
<p class="heading">{{ albums_list.sortedAndFiltered.length }} Albums</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="albums_list"></list-albums>
</template>
</content-with-heading>
@@ -44,17 +44,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListAlbums from '@/components/ListAlbums'
import DropdownMenu from '@/components/DropdownMenu'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import Albums from '@/lib/Albums'
const albumsData = {
const dataObject = {
load: function (to) {
return webapi.library_albums('music')
},
@@ -69,7 +68,6 @@ const albumsData = {
export default {
name: 'PageAlbums',
mixins: [LoadDataBeforeEnterMixin(albumsData)],
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListAlbums, DropdownMenu },
data () {
@@ -125,6 +123,23 @@ 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>

View File

@@ -1,6 +1,6 @@
<template>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<div class="columns">
<div class="column">
<p class="heading" style="margin-bottom: 24px;">Sort by</p>
@@ -8,10 +8,10 @@
</div>
</div>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ artist.name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -21,7 +21,7 @@
</a>
</div>
</template>
<template slot="content">
<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" />
@@ -30,16 +30,15 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListAlbums from '@/components/ListAlbums'
import ModalDialogArtist from '@/components/ModalDialogArtist'
import DropdownMenu from '@/components/DropdownMenu'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import Albums from '@/lib/Albums'
const artistData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_artist(to.params.artist_id),
@@ -55,7 +54,6 @@ const artistData = {
export default {
name: 'PageArtist',
mixins: [LoadDataBeforeEnterMixin(artistData)],
components: { ContentWithHeading, ListAlbums, ModalDialogArtist, DropdownMenu },
data () {
@@ -94,6 +92,19 @@ export default {
play: function () {
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>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ artist.name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -17,7 +17,7 @@
</a>
</div>
</template>
<template slot="content">
<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" />
@@ -27,14 +27,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import IndexButtonList from '@/components/IndexButtonList'
import ListTracks from '@/components/ListTracks'
import ModalDialogArtist from '@/components/ModalDialogArtist'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import webapi from '@/webapi'
const tracksData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_artist(to.params.artist_id),
@@ -50,7 +49,6 @@ const tracksData = {
export default {
name: 'PageArtistTracks',
mixins: [LoadDataBeforeEnterMixin(tracksData)],
components: { ContentWithHeading, ListTracks, IndexButtonList, ModalDialogArtist },
data () {
@@ -82,6 +80,19 @@ export default {
play: function () {
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>

View File

@@ -1,9 +1,9 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="artists_list.indexList"></index-button-list>
<div class="columns">
@@ -30,13 +30,13 @@
</div>
</div>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Artists</p>
<p class="heading">{{ artists_list.sortedAndFiltered.length }} Artists</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
</template>
<template slot="content">
<template v-slot:content>
<list-artists :artists="artists_list"></list-artists>
</template>
</content-with-heading>
@@ -44,17 +44,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListArtists from '@/components/ListArtists'
import DropdownMenu from '@/components/DropdownMenu'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import Artists from '@/lib/Artists'
const artistsData = {
const dataObject = {
load: function (to) {
return webapi.library_artists('music')
},
@@ -66,7 +65,6 @@ const artistsData = {
export default {
name: 'PageArtists',
mixins: [LoadDataBeforeEnterMixin(artistsData)],
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListArtists, DropdownMenu },
data () {
@@ -122,6 +120,23 @@ 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>

View File

@@ -1,6 +1,6 @@
<template>
<content-with-hero>
<template slot="heading-left">
<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>
@@ -13,7 +13,7 @@
</a>
</div>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<p class="image is-square fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="album.artwork_url"
@@ -22,7 +22,7 @@
@click="show_album_details_modal = true" />
</p>
</template>
<template slot="content">
<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" />
@@ -31,14 +31,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHero from '@/templates/ContentWithHero'
import ListTracks from '@/components/ListTracks'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHero from '@/templates/ContentWithHero.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi'
const albumData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_album(to.params.album_id),
@@ -54,7 +53,6 @@ const albumData = {
export default {
name: 'PageAudiobooksAlbum',
mixins: [LoadDataBeforeEnterMixin(albumData)],
components: { ContentWithHero, ListTracks, ModalDialogAlbum, CoverArtwork },
data () {
@@ -84,6 +82,19 @@ 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>

View File

@@ -1,16 +1,16 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-audiobooks></tabs-audiobooks>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="albums_list.indexList"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Audiobooks</p>
<p class="heading">{{ albums_list.sortedAndFiltered.length }} Audiobooks</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="albums_list"></list-albums>
</template>
</content-with-heading>
@@ -18,15 +18,14 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import TabsAudiobooks from '@/components/TabsAudiobooks'
import IndexButtonList from '@/components/IndexButtonList'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListAlbums from '@/components/ListAlbums'
import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import webapi from '@/webapi'
import Albums from '@/lib/Albums'
const albumsData = {
const dataObject = {
load: function (to) {
return webapi.library_albums('audiobook')
},
@@ -38,7 +37,6 @@ const albumsData = {
export default {
name: 'PageAudiobooksAlbums',
mixins: [LoadDataBeforeEnterMixin(albumsData)],
components: { TabsAudiobooks, ContentWithHeading, IndexButtonList, ListAlbums },
data () {
@@ -57,6 +55,19 @@ 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()
})
}
}
</script>

View File

@@ -1,9 +1,9 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ artist.name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -13,7 +13,7 @@
</a>
</div>
</template>
<template slot="content">
<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" />
@@ -22,13 +22,12 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListAlbums from '@/components/ListAlbums'
import ModalDialogArtist from '@/components/ModalDialogArtist'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import webapi from '@/webapi'
const artistData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_artist(to.params.artist_id),
@@ -44,7 +43,6 @@ const artistData = {
export default {
name: 'PageAudiobooksArtist',
mixins: [LoadDataBeforeEnterMixin(artistData)],
components: { ContentWithHeading, ListAlbums, ModalDialogArtist },
data () {
@@ -60,6 +58,19 @@ export default {
play: function () {
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>

View File

@@ -1,18 +1,18 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-audiobooks></tabs-audiobooks>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="artists_list.indexList"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Authors</p>
<p class="heading">{{ artists_list.sortedAndFiltered.length }} Authors</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
</template>
<template slot="content">
<template v-slot:content>
<list-artists :artists="artists_list"></list-artists>
</template>
</content-with-heading>
@@ -20,15 +20,14 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsAudiobooks from '@/components/TabsAudiobooks'
import IndexButtonList from '@/components/IndexButtonList'
import ListArtists from '@/components/ListArtists'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue'
import webapi from '@/webapi'
import Artists from '@/lib/Artists'
const artistsData = {
const dataObject = {
load: function (to) {
return webapi.library_artists('audiobook')
},
@@ -40,7 +39,6 @@ const artistsData = {
export default {
name: 'PageAudiobooksArtists',
mixins: [LoadDataBeforeEnterMixin(artistsData)],
components: { ContentWithHeading, TabsAudiobooks, IndexButtonList, ListArtists },
data () {
@@ -59,6 +57,19 @@ 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()
})
}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<!-- Recently added -->
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Recently added</p>
<p class="heading">albums</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="recently_added.items"></list-albums>
</template>
<template slot="footer">
<template v-slot: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>
@@ -22,14 +22,14 @@
<!-- Recently played -->
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Recently played</p>
<p class="heading">tracks</p>
</template>
<template slot="content">
<template v-slot:content>
<list-tracks :tracks="recently_played.items"></list-tracks>
</template>
<template slot="footer">
<template v-slot: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>
@@ -41,14 +41,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListAlbums from '@/components/ListAlbums'
import ListTracks from '@/components/ListTracks'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ListTracks from '@/components/ListTracks.vue'
import webapi from '@/webapi'
const browseData = {
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 }),
@@ -64,7 +63,6 @@ const browseData = {
export default {
name: 'PageBrowse',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, ListAlbums, ListTracks },
data () {
@@ -81,6 +79,19 @@ 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>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Recently added</p>
<p class="heading">albums</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="albums_list"></list-albums>
</template>
</content-with-heading>
@@ -15,15 +15,14 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListAlbums from '@/components/ListAlbums'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import webapi from '@/webapi'
import store from '@/store'
import Albums from '@/lib/Albums'
const browseData = {
const dataObject = {
load: function (to) {
const limit = store.getters.settings_option_recently_added_limit
return webapi.search({
@@ -40,7 +39,6 @@ const browseData = {
export default {
name: 'PageBrowseType',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, ListAlbums },
data () {
@@ -58,6 +56,19 @@ 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>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Recently played</p>
<p class="heading">tracks</p>
</template>
<template slot="content">
<template v-slot:content>
<list-tracks :tracks="recently_played.items"></list-tracks>
</template>
</content-with-heading>
@@ -15,13 +15,12 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListTracks from '@/components/ListTracks'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import ListTracks from '@/components/ListTracks.vue'
import webapi from '@/webapi'
const browseData = {
const dataObject = {
load: function (to) {
return webapi.search({
type: 'track',
@@ -37,13 +36,25 @@ const browseData = {
export default {
name: 'PageBrowseType',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, ListTracks },
data () {
return {
recently_played: {}
}
},
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>

View File

@@ -1,10 +1,13 @@
<template>
<div>
<content-with-heading>
<template slot="heading-left">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template v-slot:heading-left>
<p class="title is-4">{{ name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -14,10 +17,10 @@
</a>
</div>
</template>
<template slot="content">
<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 slot="actions">
<template slot:actions>
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -31,14 +34,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemAlbums from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import ModalDialogComposer from '@/components/ModalDialogComposer'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemAlbums from '@/components/ListItemAlbum.vue'
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
import webapi from '@/webapi'
const composerData = {
const dataObject = {
load: function (to) {
return webapi.library_composer(to.params.composer)
},
@@ -51,7 +53,6 @@ const composerData = {
export default {
name: 'PageComposer',
mixins: [LoadDataBeforeEnterMixin(composerData)],
components: { ContentWithHeading, ListItemAlbums, ModalDialogAlbum, ModalDialogComposer },
data () {
@@ -90,6 +91,19 @@ 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>

View File

@@ -1,12 +1,15 @@
<template>
<div>
<content-with-heading>
<template slot="heading-left">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template v-slot:heading-left>
<p class="title is-4">{{ composer }}</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<div class="buttons is-centered">
<a class="button is-small is-light is-rounded" @click="show_composer_details_modal = true">
<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>
<a class="button is-small is-dark is-rounded" @click="play">
@@ -14,11 +17,11 @@
</a>
</div>
</template>
<template slot="content">
<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 slot="actions">
<a @click="open_dialog(track)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
@@ -31,14 +34,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogComposer from '@/components/ModalDialogComposer'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemTrack from '@/components/ListItemTrack.vue'
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
import webapi from '@/webapi'
const tracksData = {
const dataObject = {
load: function (to) {
return webapi.library_composer_tracks(to.params.composer)
},
@@ -51,7 +53,6 @@ const tracksData = {
export default {
name: 'PageComposerTracks',
mixins: [LoadDataBeforeEnterMixin(tracksData)],
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack, ModalDialogComposer },
data () {
@@ -104,6 +105,19 @@ 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>

View File

@@ -3,14 +3,14 @@
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="composers_list.indexList"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ heading }}</p>
<p class="heading">{{ composers.total }} composers</p>
</template>
<template slot="content">
<template v-slot:content>
<list-composers :composers="composers_list"></list-composers>
</template>
</content-with-heading>
@@ -18,15 +18,14 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListComposers from '@/components/ListComposers'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListComposers from '@/components/ListComposers.vue'
import webapi from '@/webapi'
import Composers from '@/lib/Composers'
const composersData = {
const dataObject = {
load: function (to) {
return webapi.library_composers()
},
@@ -44,7 +43,6 @@ const composersData = {
export default {
name: 'PageComposers',
mixins: [LoadDataBeforeEnterMixin(composersData)],
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListComposers },
data () {
@@ -80,6 +78,19 @@ export default {
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>

View File

@@ -1,11 +1,11 @@
<template>
<div>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Files</p>
<p class="title is-7 has-text-grey">{{ current_directory }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -15,7 +15,7 @@
</a>
</div>
</template>
<template slot="content">
<template v-slot:content>
<div class="media" v-if="$route.query.directory" @click="open_parent_directory()">
<figure class="media-left fd-has-action">
<span class="icon">
@@ -31,7 +31,7 @@
</div>
<list-item-directory v-for="directory in files.directories" :key="directory.path" :directory="directory" @click="open_directory(directory)">
<template slot="actions">
<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>
@@ -39,12 +39,12 @@
</list-item-directory>
<list-item-playlist v-for="playlist in files.playlists.items" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
<template slot="icon">
<template v-slot:icon>
<span class="icon">
<i class="mdi mdi-library-music"></i>
</span>
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -52,12 +52,12 @@
</list-item-playlist>
<list-item-track v-for="(track, index) in files.tracks.items" :key="track.id" :track="track" @click="play_track(index)">
<template slot="icon">
<template v-slot:icon>
<span class="icon">
<i class="mdi mdi-file-outline"></i>
</span>
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_track_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -73,17 +73,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemDirectory from '@/components/ListItemDirectory'
import ListItemPlaylist from '@/components/ListItemPlaylist'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogDirectory from '@/components/ModalDialogDirectory'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemDirectory from '@/components/ListItemDirectory.vue'
import ListItemPlaylist from '@/components/ListItemPlaylist.vue'
import ListItemTrack from '@/components/ListItemTrack.vue'
import ModalDialogDirectory from '@/components/ModalDialogDirectory.vue'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
import webapi from '@/webapi'
const filesData = {
const dataObject = {
load: function (to) {
if (to.query.directory) {
return webapi.library_files(to.query.directory)
@@ -106,7 +105,6 @@ const filesData = {
export default {
name: 'PageFiles',
mixins: [LoadDataBeforeEnterMixin(filesData)],
components: { ContentWithHeading, ListItemDirectory, ListItemPlaylist, ListItemTrack, ModalDialogDirectory, ModalDialogPlaylist, ModalDialogTrack },
data () {
@@ -173,6 +171,19 @@ 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>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -17,7 +17,7 @@
</a>
</div>
</template>
<template slot="content">
<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" />
@@ -27,14 +27,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import IndexButtonList from '@/components/IndexButtonList'
import ListAlbums from '@/components/ListAlbums'
import ModalDialogGenre from '@/components/ModalDialogGenre'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
import webapi from '@/webapi'
const genreData = {
const dataObject = {
load: function (to) {
return webapi.library_genre(to.params.genre)
},
@@ -47,7 +46,6 @@ const genreData = {
export default {
name: 'PageGenre',
mixins: [LoadDataBeforeEnterMixin(genreData)],
components: { ContentWithHeading, IndexButtonList, ListAlbums, ModalDialogGenre },
data () {
@@ -80,6 +78,19 @@ 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>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ genre }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -17,7 +17,7 @@
</a>
</div>
</template>
<template slot="content">
<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" />
@@ -27,14 +27,13 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import IndexButtonList from '@/components/IndexButtonList'
import ListTracks from '@/components/ListTracks'
import ModalDialogGenre from '@/components/ModalDialogGenre'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
import webapi from '@/webapi'
const tracksData = {
const dataObject = {
load: function (to) {
return webapi.library_genre_tracks(to.params.genre)
},
@@ -47,7 +46,6 @@ const tracksData = {
export default {
name: 'PageGenreTracks',
mixins: [LoadDataBeforeEnterMixin(tracksData)],
components: { ContentWithHeading, ListTracks, IndexButtonList, ModalDialogGenre },
data () {
@@ -79,6 +77,19 @@ 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>

View File

@@ -1,19 +1,19 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<template v-slot:options>
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Genres</p>
<p class="heading">{{ genres.total }} genres</p>
</template>
<template slot="content">
<template v-slot:content>
<list-item-genre v-for="genre in genres.items" :key="genre.name" :genre="genre" @click="open_genre(genre)">
<template slot="actions">
<a @click="open_dialog(genre)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(genre)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
@@ -25,15 +25,14 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemGenre from '@/components/ListItemGenre'
import ModalDialogGenre from '@/components/ModalDialogGenre'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListItemGenre from '@/components/ListItemGenre.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
import webapi from '@/webapi'
const genresData = {
const dataObject = {
load: function (to) {
return webapi.library_genres()
},
@@ -45,7 +44,6 @@ const genresData = {
export default {
name: 'PageGenres',
mixins: [LoadDataBeforeEnterMixin(genresData)],
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemGenre, ModalDialogGenre },
data () {
@@ -73,6 +71,19 @@ 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>

View File

@@ -11,7 +11,15 @@
<div class="fd-has-padding-left-right">
<div class="container has-text-centered">
<p class="control has-text-centered fd-progress-now-playing">
<range-slider
<Slider v-model="item_progress_ms"
:min="0"
:max="state.item_length_ms"
:step="1000"
:tooltips="false"
:disabled="state.state === 'stop'"
@change="seek"
:classes="{ target: 'seek-slider'}" />
<!--range-slider
class="seek-slider fd-has-action"
min="0"
:max="state.item_length_ms"
@@ -19,10 +27,10 @@
:disabled="state.state === 'stop'"
step="1000"
@change="seek" >
</range-slider>
</range-slider-->
</p>
<p class="content">
<span>{{ item_progress_ms | duration }} / {{ now_playing.length_ms | duration }}</span>
<span>{{ $filters.duration(item_progress_ms) }} / {{ $filters.duration(now_playing.length_ms) }}</span>
</p>
</div>
</div>
@@ -60,15 +68,21 @@
</template>
<script>
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem'
import RangeSlider from 'vue-range-slider'
import CoverArtwork from '@/components/CoverArtwork'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider'
import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
export default {
name: 'PageNowPlaying',
components: { ModalDialogQueueItem, RangeSlider, CoverArtwork },
components: {
ModalDialogQueueItem,
// RangeSlider,
Slider,
CoverArtwork
},
data () {
return {

View File

@@ -1,9 +1,9 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">{{ playlist.name }}</div>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -13,7 +13,7 @@
</a>
</div>
</template>
<template slot="content">
<template v-slot: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" />
@@ -22,13 +22,12 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListTracks from '@/components/ListTracks'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
import webapi from '@/webapi'
const playlistData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_playlist(to.params.playlist_id),
@@ -44,7 +43,6 @@ const playlistData = {
export default {
name: 'PagePlaylist',
mixins: [LoadDataBeforeEnterMixin(playlistData)],
components: { ContentWithHeading, ListTracks, ModalDialogPlaylist },
data () {
@@ -69,6 +67,19 @@ 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>

View File

@@ -1,22 +1,21 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ playlist.name }}</p>
<p class="heading">{{ playlists.total }} playlists</p>
</template>
<template slot="content">
<template v-slot:content>
<list-playlists :playlists="playlists.items"></list-playlists>
</template>
</content-with-heading>
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListPlaylists from '@/components/ListPlaylists'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListPlaylists from '@/components/ListPlaylists.vue'
import webapi from '@/webapi'
const playlistsData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_playlist(to.params.playlist_id),
@@ -32,7 +31,6 @@ const playlistsData = {
export default {
name: 'PagePlaylists',
mixins: [LoadDataBeforeEnterMixin(playlistsData)],
components: { ContentWithHeading, ListPlaylists },
data () {
@@ -40,6 +38,19 @@ export default {
playlist: {},
playlists: {}
}
},
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>

View File

@@ -1,10 +1,10 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">{{ album.name }}
</div>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -17,21 +17,14 @@
</a>
</div>
</template>
<template slot="content">
<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 slot="progress">
<range-slider
class="track-progress"
min="0"
:max="track.length_ms"
step="1"
:disabled="true"
:value="track.seek_ms" >
</range-slider>
<template v-slot:progress>
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
</template>
<template slot="actions">
<a @click="open_dialog(track)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
@@ -55,7 +48,7 @@
delete_action="Remove"
@close="show_remove_podcast_modal = false"
@delete="remove_podcast">
<template slot="modal-content">
<template v-slot: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>
</template>
@@ -65,16 +58,15 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import ModalDialog from '@/components/ModalDialog'
import RangeSlider from 'vue-range-slider'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemTrack from '@/components/ListItemTrack.vue'
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
import ModalDialog from '@/components/ModalDialog.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import webapi from '@/webapi'
const albumData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_album(to.params.album_id),
@@ -90,8 +82,14 @@ const albumData = {
export default {
name: 'PagePodcast',
mixins: [LoadDataBeforeEnterMixin(albumData)],
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack, RangeSlider, ModalDialogAlbum, ModalDialog },
components: {
ContentWithHeading,
ListItemTrack,
ModalDialogTrack,
ModalDialogAlbum,
ModalDialog,
ProgressBar
},
data () {
return {
@@ -154,6 +152,19 @@ 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>

View File

@@ -1,10 +1,10 @@
<template>
<div>
<content-with-heading v-if="new_episodes.items.length > 0">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">New episodes</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<div class="buttons is-centered">
<a class="button is-small" @click="mark_all_played">
<span class="icon">
@@ -14,19 +14,12 @@
</a>
</div>
</template>
<template slot="content">
<template v-slot:content>
<list-item-track v-for="track in new_episodes.items" :key="track.id" :track="track" @click="play_track(track)">
<template slot="progress">
<range-slider
class="track-progress"
min="0"
:max="track.length_ms"
step="1"
:disabled="true"
:value="track.seek_ms" >
</range-slider>
<template v-slot:progress>
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_track_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -37,11 +30,11 @@
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Podcasts</p>
<p class="heading">{{ albums.total }} podcasts</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<div class="buttons is-centered">
<a v-if="rss.tracks > 0" class="button is-small" @click="update_rss">
<span class="icon">
@@ -57,7 +50,7 @@
</a>
</div>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="albums.items"
@play-count-changed="reload_new_episodes()"
@podcast-deleted="reload_podcasts()">
@@ -72,17 +65,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ListAlbums from '@/components/ListAlbums'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogAddRss from '@/components/ModalDialogAddRss'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemTrack from '@/components/ListItemTrack.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import * as types from '@/store/mutation_types'
import RangeSlider from 'vue-range-slider'
import webapi from '@/webapi'
const albumsData = {
const dataObject = {
load: function (to) {
return Promise.all([
webapi.library_albums('podcast'),
@@ -98,8 +90,14 @@ const albumsData = {
export default {
name: 'PagePodcasts',
mixins: [LoadDataBeforeEnterMixin(albumsData)],
components: { ContentWithHeading, ListItemTrack, ListAlbums, ModalDialogTrack, ModalDialogAddRss, RangeSlider },
components: {
ContentWithHeading,
ListItemTrack,
ListAlbums,
ModalDialogTrack,
ModalDialogAddRss,
ProgressBar
},
data () {
return {
@@ -157,6 +155,19 @@ 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>

View File

@@ -1,10 +1,10 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="heading">{{ queue.count }} tracks</p>
<p class="title is-4">Queue</p>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<div class="buttons is-centered">
<a class="button is-small" :class="{ 'is-info': show_only_next_items }" @click="update_show_next_items">
<span class="icon">
@@ -38,22 +38,25 @@
</a>
</div>
</template>
<template slot="content">
<draggable v-model="queue_items" handle=".handle" @end="move_item">
<list-item-queue-item v-for="(item, index) in queue_items"
:key="item.id" :item="item" :position="index"
:current_position="current_position"
:show_only_next_items="show_only_next_items"
:edit_mode="edit_mode">
<template slot="actions">
<a @click="open_dialog(item)" v-if="!edit_mode">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<a @click="remove(item)" v-if="item.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>
<template v-slot: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>
</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" />
@@ -63,11 +66,11 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemQueueItem from '@/components/ListItemQueueItem'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem'
import ModalDialogAddUrlStream from '@/components/ModalDialogAddUrlStream'
import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemQueueItem from '@/components/ListItemQueueItem.vue'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
import ModalDialogAddUrlStream from '@/components/ModalDialogAddUrlStream.vue'
import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import draggable from 'vuedraggable'

View File

@@ -1,10 +1,10 @@
<template>
<div>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Radio</p>
</template>
<template slot="content">
<template v-slot:content>
<p class="heading has-text-centered-mobile">{{ tracks.total }} tracks</p>
<list-tracks :tracks="tracks.items"></list-tracks>
</template>
@@ -13,12 +13,11 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListTracks from '@/components/ListTracks'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListTracks from '@/components/ListTracks.vue'
import webapi from '@/webapi'
const streamsData = {
const dataObject = {
load: function (to) {
return webapi.library_radio_streams()
},
@@ -30,13 +29,25 @@ const streamsData = {
export default {
name: 'PageRadioStreams',
mixins: [LoadDataBeforeEnterMixin(streamsData)],
components: { ContentWithHeading, ListTracks },
data () {
return {
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>

View File

@@ -30,13 +30,13 @@
<!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Tracks</p>
</template>
<template slot="content">
<template v-slot:content>
<list-tracks :tracks="tracks.items"></list-tracks>
</template>
<template slot="footer">
<template v-slot: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>
@@ -45,20 +45,20 @@
</template>
</content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template slot="content">
<template v-slot:content>
<p><i>No tracks found</i></p>
</template>
</content-text>
<!-- Artists -->
<content-with-heading v-if="show_artists && artists.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Artists</p>
</template>
<template slot="content">
<template v-slot:content>
<list-artists :artists="artists.items"></list-artists>
</template>
<template slot="footer">
<template v-slot: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>
@@ -67,20 +67,20 @@
</template>
</content-with-heading>
<content-text v-if="show_artists && !artists.total">
<template slot="content">
<template v-slot:content>
<p><i>No artists found</i></p>
</template>
</content-text>
<!-- Albums -->
<content-with-heading v-if="show_albums && albums.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Albums</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="albums.items"></list-albums>
</template>
<template slot="footer">
<template v-slot: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>
@@ -89,20 +89,20 @@
</template>
</content-with-heading>
<content-text v-if="show_albums && !albums.total">
<template slot="content">
<template v-slot:content>
<p><i>No albums found</i></p>
</template>
</content-text>
<!-- Composers -->
<content-with-heading v-if="show_composers && composers.total">
<template slot="heading-left">
<template slot:heading-left>
<p class="title is-4">Composers</p>
</template>
<template slot="content">
<template slot:content>
<list-composers :composers="composers.items"></list-composers>
</template>
<template slot="footer">
<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>
@@ -111,20 +111,20 @@
</template>
</content-with-heading>
<content-text v-if="show_composers && !composers.total">
<template slot="content">
<template slot:content>
<p><i>No composers found</i></p>
</template>
</content-text>
<!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Playlists</p>
</template>
<template slot="content">
<template v-slot:content>
<list-playlists :playlists="playlists.items"></list-playlists>
</template>
<template slot="footer">
<template v-slot: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>
@@ -133,20 +133,20 @@
</template>
</content-with-heading>
<content-text v-if="show_playlists && !playlists.total">
<template slot="content">
<template v-slot:content>
<p><i>No playlists found</i></p>
</template>
</content-text>
<!-- Podcasts -->
<content-with-heading v-if="show_podcasts && podcasts.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Podcasts</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="podcasts.items"></list-albums>
</template>
<template slot="footer">
<template v-slot: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>
@@ -155,20 +155,20 @@
</template>
</content-with-heading>
<content-text v-if="show_podcasts && !podcasts.total">
<template slot="content">
<template v-slot:content>
<p><i>No podcasts found</i></p>
</template>
</content-text>
<!-- Audiobooks -->
<content-with-heading v-if="show_audiobooks && audiobooks.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Audiobooks</p>
</template>
<template slot="content">
<template v-slot:content>
<list-albums :albums="audiobooks.items"></list-albums>
</template>
<template slot="footer">
<template v-slot: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>
@@ -177,7 +177,7 @@
</template>
</content-with-heading>
<content-text v-if="show_audiobooks && !audiobooks.total">
<template slot="content">
<template v-slot:content>
<p><i>No audiobooks found</i></p>
</template>
</content-text>
@@ -185,14 +185,14 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import ContentText from '@/templates/ContentText'
import TabsSearch from '@/components/TabsSearch'
import ListTracks from '@/components/ListTracks'
import ListArtists from '@/components/ListArtists'
import ListAlbums from '@/components/ListAlbums'
import ListComposers from '@/components/ListComposers'
import ListPlaylists from '@/components/ListPlaylists'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ContentText from '@/templates/ContentText.vue'
import TabsSearch from '@/components/TabsSearch.vue'
import ListTracks from '@/components/ListTracks.vue'
import ListArtists from '@/components/ListArtists.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ListComposers from '@/components/ListComposers.vue'
import ListPlaylists from '@/components/ListPlaylists.vue'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-settings></tabs-settings>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Artwork</div>
</template>
<template slot="content">
<template v-slot:content>
<div class="content">
<p>
OwnTone supports PNG and JPEG artwork which is either placed as separate image files in the library,
@@ -16,13 +16,13 @@
<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 slot="label"> Spotify</template>
<template v-slot:label> Spotify</template>
</settings-checkbox>
<settings-checkbox category_name="artwork" option_name="use_artwork_source_discogs">
<template slot="label"> Discogs (<a href="https://www.discogs.com/">https://www.discogs.com/</a>)</template>
<template v-slot: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 slot="label"> Cover Art Archive (<a href="https://coverartarchive.org/">https://coverartarchive.org/</a>)</template>
<template v-slot:label> Cover Art Archive (<a href="https://coverartarchive.org/">https://coverartarchive.org/</a>)</template>
</settings-checkbox>
</template>
</content-with-heading>
@@ -30,9 +30,9 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsSettings from '@/components/TabsSettings'
import SettingsCheckbox from '@/components/SettingsCheckbox'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue'
import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
export default {
name: 'SettingsPageArtwork',

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-settings></tabs-settings>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Spotify</div>
</template>
<template slot="content">
<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>
</div>
@@ -56,7 +56,7 @@
</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>
<b><code>{{ spotify_missing_scope.join() }}</code></b>
</p>
<div class="field fd-has-margin-top ">
<div class="control">
@@ -65,7 +65,7 @@
</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>.
<code>{{ spotify_required_scope.join() }}</code>.
</p>
<div v-if="spotify.webapi_token_valid" class="field fd-has-margin-top ">
<div class="control">
@@ -78,11 +78,11 @@
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Last.fm</div>
</template>
<template slot="content">
<template v-slot:content>
<div class="notification is-size-7" v-if="!lastfm.enabled">
<p>OwnTone was built without support for Last.fm.</p>
</div>
@@ -121,8 +121,8 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsSettings from '@/components/TabsSettings'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue'
import webapi from '@/webapi'
export default {

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-settings></tabs-settings>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Remote Pairing</div>
</template>
<template slot="content">
<template v-slot:content>
<!-- Paring request active -->
<div class="notification" v-if="pairing.active">
<form v-on:submit.prevent="kickoff_pairing">
@@ -32,11 +32,11 @@
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Speaker pairing and device verification</div>
</template>
<template slot="content">
<template v-slot:content>
<p class="content">
If your speaker requires pairing then activate it below and enter the PIN that it displays.
</p>
@@ -66,8 +66,8 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsSettings from '@/components/TabsSettings'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue'
import webapi from '@/webapi'
export default {

View File

@@ -1,13 +1,13 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-settings></tabs-settings>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Navbar items</div>
</template>
<template slot="content">
<template v-slot:content>
<p class="content">
Select the top navigation bar menu items
</p>
@@ -15,56 +15,56 @@
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 slot="label"> Playlists</template>
<template v-slot:label> Playlists</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_music">
<template slot="label"> Music</template>
<template v-slot:label> Music</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_podcasts">
<template slot="label"> Podcasts</template>
<template v-slot:label> Podcasts</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_audiobooks">
<template slot="label"> Audiobooks</template>
<template v-slot:label> Audiobooks</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_radio">
<template slot="label"> Radio</template>
<template v-slot:label> Radio</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_files">
<template slot="label"> Files</template>
<template v-slot:label> Files</template>
</settings-checkbox>
<settings-checkbox category_name="webinterface" option_name="show_menu_item_search">
<template slot="label"> Search</template>
<template v-slot:label> Search</template>
</settings-checkbox>
</template>
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Album lists</div>
</template>
<template slot="content">
<template v-slot:content>
<settings-checkbox category_name="webinterface" option_name="show_cover_artwork_in_album_lists">
<template slot="label"> Show cover artwork in album list</template>
<template v-slot:label> Show cover artwork in album list</template>
</settings-checkbox>
</template>
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Now playing page</div>
</template>
<template slot="content">
<template v-slot:content>
<settings-checkbox category_name="webinterface" option_name="show_composer_now_playing">
<template slot="label"> Show composer</template>
<template slot="info">If enabled the composer of the current playing track is shown on the &quot;now playing page&quot;</template>
<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>
</settings-checkbox>
<settings-textfield category_name="webinterface" option_name="show_composer_for_genre"
:disabled="!settings_option_show_composer_now_playing"
placeholder="Genres">
<template slot="label">Show composer only for listed genres</template>
<template slot="info">
<template v-slot:label>Show composer only for listed genres</template>
<template v-slot:info>
<p class="help">
Comma separated list of genres the composer should be displayed on the &quot;now playing page&quot;.
</p>
@@ -82,13 +82,13 @@
</content-with-heading>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">Recently added page</div>
</template>
<template slot="content">
<template v-slot:content>
<settings-intfield category_name="webinterface" option_name="recently_added_limit">
<template slot="label">Limit the number of albums shown on the "Recently Added" page</template>
<template v-slot:label>Limit the number of albums shown on the "Recently Added" page</template>
</settings-intfield>
</template>
</content-with-heading>
@@ -96,11 +96,11 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsSettings from '@/components/TabsSettings'
import SettingsCheckbox from '@/components/SettingsCheckbox'
import SettingsTextfield from '@/components/SettingsTextfield'
import SettingsIntfield from '@/components/SettingsIntfield'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue'
import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
import SettingsTextfield from '@/components/SettingsTextfield.vue'
import SettingsIntfield from '@/components/SettingsIntfield.vue'
export default {
name: 'SettingsPageWebinterface',

View File

@@ -1,6 +1,6 @@
<template>
<content-with-hero>
<template slot="heading-left">
<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>
@@ -14,7 +14,7 @@
</div>
</template>
<template slot="heading-right">
<template v-slot:heading-right>
<p class="image is-square fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="artwork_url"
@@ -24,10 +24,10 @@
</p>
</template>
<template slot="content">
<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 slot="actions">
<template v-slot:actions>
<a @click="open_track_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -40,17 +40,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHero from '@/templates/ContentWithHero'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHero from '@/templates/ContentWithHero.vue'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack.vue'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack.vue'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import store from '@/store'
import webapi from '@/webapi'
import SpotifyWebApi from 'spotify-web-api-js'
const albumData = {
const dataObject = {
load: function (to) {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
@@ -64,7 +63,6 @@ const albumData = {
export default {
name: 'PageAlbum',
mixins: [LoadDataBeforeEnterMixin(albumData)],
components: { ContentWithHero, SpotifyListItemTrack, SpotifyModalDialogTrack, SpotifyModalDialogAlbum, CoverArtwork },
data () {
@@ -101,6 +99,19 @@ 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>

View File

@@ -1,9 +1,9 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">{{ artist.name }}</p>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -13,13 +13,13 @@
</a>
</div>
</template>
<template slot="content">
<template v-slot: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 slot="artwork" v-if="is_visible_artwork">
<template v-slot:artwork v-if="is_visible_artwork">
<p class="image is-64x64 fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="artwork_url(album)"
@@ -29,13 +29,13 @@
:maxheight="64" />
</p>
</template>
<template slot="actions">
<a @click="open_dialog(album)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-album>
<infinite-loading v-if="offset < total" @infinite="load_next"><span slot="no-more">.</span></infinite-loading>
<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>
@@ -43,24 +43,25 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import SpotifyModalDialogArtist from '@/components/SpotifyModalDialogArtist'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum.vue'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
import SpotifyModalDialogArtist from '@/components/SpotifyModalDialogArtist.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import store from '@/store'
import webapi from '@/webapi'
import SpotifyWebApi from 'spotify-web-api-js'
import InfiniteLoading from 'vue-infinite-loading'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
const artistData = {
const PAGE_SIZE = 50
const dataObject = {
load: function (to) {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
return Promise.all([
spotifyApi.getArtist(to.params.artist_id),
spotifyApi.getArtistAlbums(to.params.artist_id, { limit: 50, 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 })
])
},
@@ -76,8 +77,7 @@ const artistData = {
export default {
name: 'SpotifyPageArtist',
mixins: [LoadDataBeforeEnterMixin(artistData)],
components: { ContentWithHeading, SpotifyListItemAlbum, SpotifyModalDialogAlbum, SpotifyModalDialogArtist, InfiniteLoading, CoverArtwork },
components: { ContentWithHeading, SpotifyListItemAlbum, SpotifyModalDialogAlbum, SpotifyModalDialogArtist, VueEternalLoading, CoverArtwork },
data () {
return {
@@ -100,25 +100,19 @@ export default {
},
methods: {
load_next: function ($state) {
load_next: function ({ loaded }) {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi.getArtistAlbums(this.artist.id, { limit: 50, offset: this.offset, include_groups: 'album,single' }).then(data => {
this.append_albums(data, $state)
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, $state) {
append_albums: function (data) {
this.albums = this.albums.concat(data.items)
this.total = data.total
this.offset += data.limit
if ($state) {
$state.loaded()
if (this.offset >= this.total) {
$state.complete()
}
}
},
play: function () {
@@ -141,6 +135,19 @@ 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>

View File

@@ -1,18 +1,18 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<!-- New Releases -->
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">New Releases</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-album v-for="album in new_releases"
:key="album.id"
:album="album"
@click="open_album(album)">
<template slot="artwork" v-if="is_visible_artwork">
<template v-slot:artwork v-if="is_visible_artwork">
<p class="image is-64x64 fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="artwork_url(album)"
@@ -22,7 +22,7 @@
:maxheight="64" />
</p>
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_album_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -30,7 +30,7 @@
</spotify-list-item-album>
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
<template slot="footer">
<template v-slot:footer>
<nav class="level">
<p class="level-item">
<router-link to="/music/spotify/new-releases" class="button is-light is-small is-rounded">
@@ -43,12 +43,12 @@
<!-- Featured Playlists -->
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Featured Playlists</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
<template slot="actions">
<template v-slot:actions>
<a @click="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -56,7 +56,7 @@
</spotify-list-item-playlist>
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
</template>
<template slot="footer">
<template v-slot:footer>
<nav class="level">
<p class="level-item">
<router-link to="/music/spotify/featured-playlists" class="button is-light is-small is-rounded">
@@ -70,19 +70,18 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum.vue'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist.vue'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
const browseData = {
const dataObject = {
load: function (to) {
if (store.state.spotify_new_releases.length > 0 && store.state.spotify_featured_playlists.length > 0) {
return Promise.resolve()
@@ -106,7 +105,6 @@ const browseData = {
export default {
name: 'SpotifyPageBrowse',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, CoverArtwork },
data () {
@@ -155,6 +153,19 @@ 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>

View File

@@ -1,14 +1,14 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Featured Playlists</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
<template slot="actions">
<template v-slot:actions>
<a @click="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -21,16 +21,15 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist.vue'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist.vue'
import store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
const browseData = {
const dataObject = {
load: function (to) {
if (store.state.spotify_featured_playlists.length > 0) {
return Promise.resolve()
@@ -50,7 +49,6 @@ const browseData = {
export default {
name: 'SpotifyPageBrowseFeaturedPlaylists',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, SpotifyListItemPlaylist, SpotifyModalDialogPlaylist },
data () {
@@ -71,6 +69,19 @@ 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>

View File

@@ -1,17 +1,17 @@
<template>
<div>
<div class="fd-page-with-tabs">
<tabs-music></tabs-music>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">New Releases</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-album v-for="album in new_releases"
:key="album.id"
:album="album"
@click="open_album(album)">
<template slot="artwork" v-if="is_visible_artwork">
<template v-slot:artwork v-if="is_visible_artwork">
<p class="image is-64x64 fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="artwork_url(album)"
@@ -21,7 +21,7 @@
:maxheight="64" />
</p>
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_album_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
@@ -34,17 +34,16 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum.vue'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
const browseData = {
const dataObject = {
load: function (to) {
if (store.state.spotify_new_releases.length > 0) {
return Promise.resolve()
@@ -64,7 +63,6 @@ const browseData = {
export default {
name: 'SpotifyPageBrowseNewReleases',
mixins: [LoadDataBeforeEnterMixin(browseData)],
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyModalDialogAlbum, CoverArtwork },
data () {
@@ -101,6 +99,19 @@ 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>

View File

@@ -1,9 +1,9 @@
<template>
<content-with-heading>
<template slot="heading-left">
<template v-slot:heading-left>
<div class="title is-4">{{ playlist.name }}</div>
</template>
<template slot="heading-right">
<template v-slot: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>
@@ -13,16 +13,16 @@
</a>
</div>
</template>
<template slot="content">
<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 slot="actions">
<template v-slot:actions>
<a @click="open_track_dialog(item.track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-track>
<infinite-loading v-if="offset < total" @infinite="load_next"><span slot="no-more">.</span></infinite-loading>
<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>
@@ -30,23 +30,24 @@
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack.vue'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack.vue'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist.vue'
import store from '@/store'
import webapi from '@/webapi'
import SpotifyWebApi from 'spotify-web-api-js'
import InfiniteLoading from 'vue-infinite-loading'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
const playlistData = {
const PAGE_SIZE = 50
const dataObject = {
load: function (to) {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token)
return Promise.all([
spotifyApi.getPlaylist(to.params.playlist_id),
spotifyApi.getPlaylistTracks(to.params.playlist_id, { limit: 50, offset: 0 })
spotifyApi.getPlaylistTracks(to.params.playlist_id, { limit: PAGE_SIZE, offset: 0 })
])
},
@@ -61,8 +62,7 @@ const playlistData = {
export default {
name: 'SpotifyPagePlaylist',
mixins: [LoadDataBeforeEnterMixin(playlistData)],
components: { ContentWithHeading, SpotifyListItemTrack, SpotifyModalDialogTrack, SpotifyModalDialogPlaylist, InfiniteLoading },
components: { ContentWithHeading, SpotifyListItemTrack, SpotifyModalDialogTrack, SpotifyModalDialogPlaylist, VueEternalLoading },
data () {
return {
@@ -79,25 +79,19 @@ export default {
},
methods: {
load_next: function ($state) {
load_next: function ({ loaded }) {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi.getPlaylistTracks(this.playlist.id, { limit: 50, offset: this.offset }).then(data => {
this.append_tracks(data, $state)
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, $state) {
append_tracks: function (data) {
this.tracks = this.tracks.concat(data.items)
this.total = data.total
this.offset += data.limit
if ($state) {
$state.loaded()
if (this.offset >= this.total) {
$state.complete()
}
}
},
play: function () {
@@ -109,6 +103,19 @@ 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>

View File

@@ -27,21 +27,21 @@
<!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Tracks</p>
</template>
<template slot="content">
<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 slot="actions">
<template v-slot:actions>
<a @click="open_track_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-track>
<infinite-loading v-if="query.type === 'track'" @infinite="search_tracks_next"><span slot="no-more">.</span></infinite-loading>
<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 slot="footer">
<template v-slot: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>
@@ -50,28 +50,28 @@
</template>
</content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template slot="content">
<template v-slot:content>
<p><i>No tracks found</i></p>
</template>
</content-text>
<!-- Artists -->
<content-with-heading v-if="show_artists && artists.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Artists</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist">
<template slot="actions">
<template v-slot:actions>
<a @click="open_artist_dialog(artist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-artist>
<infinite-loading v-if="query.type === 'artist'" @infinite="search_artists_next"><span slot="no-more">.</span></infinite-loading>
<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 slot="footer">
<template v-slot: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>
@@ -80,22 +80,22 @@
</template>
</content-with-heading>
<content-text v-if="show_artists && !artists.total">
<template slot="content">
<template v-slot:content>
<p><i>No artists found</i></p>
</template>
</content-text>
<!-- Albums -->
<content-with-heading v-if="show_albums && albums.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Albums</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-album v-for="album in albums.items"
:key="album.id"
:album="album"
@click="open_album(album)">
<template slot="artwork" v-if="is_visible_artwork">
<template v-slot:artwork v-if="is_visible_artwork">
<p class="image is-64x64 fd-has-shadow fd-has-action">
<cover-artwork
:artwork_url="artwork_url(album)"
@@ -105,16 +105,16 @@
:maxheight="64" />
</p>
</template>
<template slot="actions">
<template v-slot:actions>
<a @click="open_album_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-album>
<infinite-loading v-if="query.type === 'album'" @infinite="search_albums_next"><span slot="no-more">.</span></infinite-loading>
<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 slot="footer">
<template v-slot: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>
@@ -123,28 +123,28 @@
</template>
</content-with-heading>
<content-text v-if="show_albums && !albums.total">
<template slot="content">
<template v-slot:content>
<p><i>No albums found</i></p>
</template>
</content-text>
<!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total">
<template slot="heading-left">
<template v-slot:heading-left>
<p class="title is-4">Playlists</p>
</template>
<template slot="content">
<template v-slot:content>
<spotify-list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist">
<template slot="actions">
<template v-slot:actions>
<a @click="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-playlist>
<infinite-loading v-if="query.type === 'playlist'" @infinite="search_playlists_next"><span slot="no-more">.</span></infinite-loading>
<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 slot="footer">
<template v-slot: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>
@@ -153,7 +153,7 @@
</template>
</content-with-heading>
<content-text v-if="show_playlists && !playlists.total">
<template slot="content">
<template v-slot:content>
<p><i>No playlists found</i></p>
</template>
</content-text>
@@ -161,26 +161,28 @@
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import ContentText from '@/templates/ContentText'
import TabsSearch from '@/components/TabsSearch'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack'
import SpotifyListItemArtist from '@/components/SpotifyListItemArtist'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack'
import SpotifyModalDialogArtist from '@/components/SpotifyModalDialogArtist'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist'
import CoverArtwork from '@/components/CoverArtwork'
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ContentText from '@/templates/ContentText.vue'
import TabsSearch from '@/components/TabsSearch.vue'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack.vue'
import SpotifyListItemArtist from '@/components/SpotifyListItemArtist.vue'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum.vue'
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist.vue'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack.vue'
import SpotifyModalDialogArtist from '@/components/SpotifyModalDialogArtist.vue'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import SpotifyWebApi from 'spotify-web-api-js'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import InfiniteLoading from 'vue-infinite-loading'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
const PAGE_SIZE = 50
export default {
name: 'SpotifyPageSearch',
components: { ContentWithHeading, ContentText, TabsSearch, SpotifyListItemTrack, SpotifyListItemArtist, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogTrack, SpotifyModalDialogArtist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, InfiniteLoading, CoverArtwork },
components: { ContentWithHeading, ContentText, TabsSearch, SpotifyListItemTrack, SpotifyListItemArtist, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogTrack, SpotifyModalDialogArtist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, VueEternalLoading, CoverArtwork },
data () {
return {
@@ -266,7 +268,7 @@ export default {
}
this.search_query = this.query.query
this.search_param.limit = this.query.limit ? this.query.limit : 50
this.search_param.limit = this.query.limit ? this.query.limit : PAGE_SIZE
this.search_param.offset = this.query.offset ? this.query.offset : 0
this.$store.commit(types.ADD_RECENT_SEARCH, this.query.query)
@@ -295,55 +297,43 @@ export default {
})
},
search_tracks_next: function ($state) {
search_tracks_next: function ({ loaded }) {
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
$state.loaded()
if (this.search_param.offset >= this.tracks.total) {
$state.complete()
}
loaded(data.tracks.items.length, PAGE_SIZE)
})
},
search_artists_next: function ($state) {
search_artists_next: function ({ loaded }) {
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
$state.loaded()
if (this.search_param.offset >= this.artists.total) {
$state.complete()
}
loaded(data.artists.items.length, PAGE_SIZE)
})
},
search_albums_next: function ($state) {
search_albums_next: function ({ loaded }) {
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
$state.loaded()
if (this.search_param.offset >= this.albums.total) {
$state.complete()
}
loaded(data.albums.items.length, PAGE_SIZE)
})
},
search_playlists_next: function ($state) {
search_playlists_next: function ({ loaded }) {
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
$state.loaded()
if (this.search_param.offset >= this.playlists.total) {
$state.complete()
}
loaded(data.playlists.items.length, PAGE_SIZE)
})
},

View File

@@ -1,17 +0,0 @@
export const LoadDataBeforeEnterMixin = function (dataObject) {
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()
})
}
}
}