Merge pull request #634 from chme/webinterface

Update player webinterface (v0.3.0)
This commit is contained in:
chme 2018-12-24 07:22:37 +01:00 committed by GitHub
commit 4961dd9141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2715 additions and 1836 deletions

View File

@ -1,2 +1,2 @@
.fd-notifications{position:fixed;bottom:60px;z-index:20000;width:100%}.fd-notifications .notification{margin-bottom:10px;margin-left:24px;margin-right:24px;-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.slider{min-width:250px;width:100%}.range-slider-fill{background-color:#363636}a.navbar-item{outline:0;line-height:1.5;padding:.5rem 1rem}.fd-expanded{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1}.fd-margin-left-auto{margin-left:auto}.fd-has-action{cursor:pointer}.fd-is-movable{cursor:move}.fd-has-margin-top{margin-top:24px}.fd-has-margin-bottom{margin-bottom:24px}.fd-has-padding-left-right{padding-left:24px;padding-right:24px}.fd-is-text-clipped{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fd-is-fullheight{height:calc(100vh - 6.5rem)}.fd-is-fullheight-body{-ms-flex-negative:1;flex-shrink:1;overflow:hidden;height:100%}.fd-image-fullheight{height:100%;width:auto}.fd-tabs-section{padding-bottom:0}.fd-progress-bar{top:52px!important}.fd-has-shadow{-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.sortable-chosen .media-right{visibility:hidden}.sortable-ghost h1,.sortable-ghost h2{color:#ff3860!important}.media:first-of-type{padding-top:17px;margin-top:16px}.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .4s;transition:opacity .4s}.fade-enter,.fade-leave-to{opacity:0}.seek-slider{min-width:250px;max-width:500px;width:100%!important}.seek-slider .range-slider-fill{background-color:#00d1b2;-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.seek-slider .range-slider-knob{width:10px;height:10px;background-color:#00d1b2;border-color:#00d1b2}.title:not(.is-spaced)+.subtitle,.title:not(.is-spaced)+.subtitle+.subtitle{margin-top:-1.3rem!important}.fd-modal-card{overflow:visible}.fd-modal-card .card-content{max-height:calc(100vh - 200px);overflow:auto}
.fd-notifications{position:fixed;bottom:60px;z-index:20000;width:100%}.fd-notifications .notification{margin-bottom:10px;margin-left:24px;margin-right:24px;-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.slider{min-width:250px;width:100%}.range-slider-fill{background-color:#363636}a.navbar-item{outline:0;line-height:1.5;padding:.5rem 1rem}.fd-expanded{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1}.fd-margin-left-auto{margin-left:auto}.fd-has-action{cursor:pointer}.fd-is-movable{cursor:move}.fd-has-margin-top{margin-top:24px}.fd-has-margin-bottom{margin-bottom:24px}.fd-remove-padding-bottom{padding-bottom:0}.fd-has-padding-left-right{padding-left:24px;padding-right:24px}.fd-is-square .button{height:27px;width:27px}.fd-is-text-clipped{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fd-is-fullheight{height:calc(100vh - 6.5rem)}.fd-is-fullheight-body{-ms-flex-negative:1;flex-shrink:1;overflow:hidden;height:100%}.fd-image-fullheight{height:100%;width:auto}.fd-tabs-section{padding-bottom:3px;padding-top:3px;background:#fff;top:3.25rem;z-index:20;position:fixed;width:100%}section.fd-tabs-section+section.fd-content{margin-top:24px}.fd-progress-bar{top:52px!important}.fd-has-shadow{-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.sortable-chosen .media-right{visibility:hidden}.sortable-ghost h1,.sortable-ghost h2{color:#ff3860!important}.media:first-of-type{padding-top:17px;margin-top:16px}.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .4s;transition:opacity .4s}.fade-enter,.fade-leave-to{opacity:0}.seek-slider{min-width:250px;max-width:500px;width:100%!important}.seek-slider .range-slider-fill{background-color:#00d1b2;-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19);box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19)}.seek-slider .range-slider-knob{width:10px;height:10px;background-color:#00d1b2;border-color:#00d1b2}.title:not(.is-spaced)+.subtitle,.title:not(.is-spaced)+.subtitle+.subtitle{margin-top:-1.3rem!important}.fd-modal-card{overflow:visible}.fd-modal-card .card-content{max-height:calc(100vh - 200px);overflow:auto}.fd-modal-card .card{margin-left:16px;margin-right:16px}
/*# sourceMappingURL=app.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1490
web-src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "forked-daapd-web",
"version": "0.2.0",
"version": "0.3.0",
"description": "forked-daapd web interface",
"author": "chme <christian.meffert@googlemail.com>",
"license": "GPL-2.0",
@ -15,24 +15,24 @@
"axios": "^0.18.0",
"bulma": "^0.7.1",
"mdi": "^2.1.99",
"moment": "^2.22.1",
"moment": "^2.23.0",
"moment-duration-format": "^2.2.2",
"npm": "^6.4.1",
"npm": "^6.5.0",
"reconnectingwebsocket": "^1.0.0",
"spotify-web-api-js": "^1.1.1",
"vue": "^2.5.17",
"vue-infinite-loading": "^2.4.0",
"vue": "^2.5.21",
"vue-infinite-loading": "^2.4.3",
"vue-progressbar": "^0.7.4",
"vue-range-slider": "^0.6.0",
"vue-router": "^3.0.1",
"vuedraggable": "^2.16.0",
"vue-router": "^3.0.2",
"vuedraggable": "^2.17.0",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.1.1",
"@vue/cli-plugin-eslint": "^3.1.4",
"@vue/cli-service": "^3.1.2",
"@vue/cli-plugin-babel": "^3.2.0",
"@vue/cli-plugin-eslint": "^3.2.1",
"@vue/cli-service": "^3.2.0",
"@vue/eslint-config-standard": "^3.0.5",
"vue-template-compiler": "^2.5.17"
"vue-template-compiler": "^2.5.21"
}
}

View File

@ -165,13 +165,11 @@ export default {
this.$store.commit(types.UPDATE_SPOTIFY, data)
if (this.token_timer_id > 0) {
console.log('clear old timer: ' + this.token_timer_id)
window.clearTimeout(this.token_timer_id)
this.token_timer_id = 0
}
if (data.webapi_token_expires_in > 0 && data.webapi_token) {
this.token_timer_id = window.setTimeout(this.update_spotify, 1000 * data.webapi_token_expires_in)
console.log('new timer: ' + this.token_timer_id + ', expires in ' + data.webapi_token_expires_in + ' seconds')
}
})
}

View File

@ -0,0 +1,38 @@
<template>
<section>
<nav class="buttons is-centered fd-is-square" style="margin-bottom: 48px;" v-if="filtered_index.length > 1">
<a v-for="char in filtered_index" :key="char" class="button is-small" @click="nav(char)">{{ char }}</a>
</nav>
<nav class="buttons is-centered" style="margin-bottom: 6px;" v-if="filtered_index.length > 1">
<a class="button is-small is-white" @click="scroll_to_top"><span class="icon is-small"><i class="mdi mdi-chevron-up"></i></span></a>
</nav>
</section>
</template>
<script>
export default {
name: 'IndexButtonList',
props: ['index'],
computed: {
filtered_index () {
const specialChars = '!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
return this.index.filter(c => !specialChars.includes(c))
}
},
methods: {
nav: function (id) {
this.$router.push({ path: this.$router.currentRoute.path + '#index_' + id })
},
scroll_to_top: function () {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
}
</script>
<style>
</style>

View File

@ -1,105 +1,19 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_album">
<h1 class="title is-6">{{ album.name }}</h1>
<h2 class="subtitle is-7 has-text-grey"><b>{{ album.artist }}</b></h2>
<template functional>
<div class="media" :id="'index_' + props.album.name_sort.charAt(0).toUpperCase()">
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
<h1 class="title is-6">{{ props.album.name }}</h1>
<h2 class="subtitle is-7 has-text-grey"><b>{{ props.album.artist }}</b></h2>
</div>
<div class="media-right">
<a @click="show_details_modal = true">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<div class="content is-small">
<p v-if="album.artist && media_kind !== 'audiobook'">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artist }}</a>
</p>
<p v-if="album.artist && media_kind === 'audiobook'">
<span class="heading">Album artist</span>
<span class="title is-6">{{ album.artist }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ album.track_count }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'ListItemAlbum',
components: { ModalDialog },
props: ['album', 'media_kind'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.album.uri, false)
},
queue_add: function () {
this.show_details_modal = false
webapi.queue_add(this.album.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
},
queue_add_next: function () {
this.show_details_modal = false
webapi.queue_add_next(this.album.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
},
open_album: function () {
this.show_details_modal = false
if (this.media_kind === 'podcast') {
this.$router.push({ path: '/podcasts/' + this.album.id })
} else if (this.media_kind === 'audiobook') {
this.$router.push({ path: '/audiobooks/' + this.album.id })
} else {
this.$router.push({ path: '/music/albums/' + this.album.id })
}
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/artists/' + this.album.artist_id })
}
}
props: ['album', 'media_kind']
}
</script>

View File

@ -1,89 +1,18 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_artist">
<h1 class="title is-6">{{ artist.name }}</h1>
<template functional>
<div class="media" :id="'index_' + props.artist.name_sort.charAt(0).toUpperCase()">
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
<h1 class="title is-6">{{ props.artist.name }}</h1>
</div>
<div class="media-right">
<a @click="show_details_modal = true">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ artist.album_count }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ artist.track_count }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'PartArtist',
components: { ModalDialog },
props: ['artist'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.artist.uri, false)
},
queue_add: function () {
this.show_details_modal = false
webapi.queue_add(this.artist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Artist tracks appended to queue', type: 'info', timeout: 2000 })
)
},
queue_add_next: function () {
this.show_details_modal = false
webapi.queue_add_next(this.artist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/artists/' + this.artist.id })
}
}
name: 'ListItemArtist',
props: ['artist']
}
</script>

View File

@ -1,97 +1,18 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_genre">
<h1 class="title is-6">{{ genre.name }}</h1>
<template functional>
<div class="media" :id="'index_' + props.genre.name.charAt(0).toUpperCase()">
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
<h1 class="title is-6">{{ props.genre.name }}</h1>
</div>
<div class="media-right">
<a @click="show_details_modal = true">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_genre">{{ genre.name }}</a>
</p>
<!--
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ genre.album_count }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ genre.track_count }}</span>
</p>
</div>
-->
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-12px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-12px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-12px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'PartGenre',
components: { ModalDialog },
props: [ 'genre' ],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.player_play_uri(data.albums.items.map(a => a.uri).join(','), false)
)
},
queue_add: function () {
this.show_details_modal = false
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.queue_add(data.albums.items.map(a => a.uri).join(',')).then(() =>
this.$store.dispatch('add_notification', { text: 'Genre albums appended to queue', type: 'info', timeout: 1500 })
)
)
},
queue_add_next: function () {
this.show_details_modal = false
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.queue_add_next(data.albums.items.map(a => a.uri).join(',')).then(() =>
this.$store.dispatch('add_notification', { text: 'Genre albums playing next', type: 'info', timeout: 1500 })
)
)
},
open_genre: function () {
this.show_details_modal = false
this.$router.push({ name: 'Genre', params: { genre: this.genre.name } })
}
}
name: 'ListItemGenre',
props: [ 'genre' ]
}
</script>

View File

@ -1,85 +1,18 @@
<template>
<template functional>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_playlist">
<h1 class="title is-6">{{ playlist.name }}</h1>
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
<h1 class="title is-6">{{ props.playlist.name }}</h1>
</div>
<div class="media-right">
<a @click="show_details_modal = true">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.path }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'PartPlaylist',
components: { ModalDialog },
props: ['playlist'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.playlist.uri, false)
},
queue_add: function () {
this.show_details_modal = false
webapi.queue_add(this.playlist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Playlist appended to queue', type: 'info', timeout: 2000 })
)
},
queue_add_next: function () {
this.show_details_modal = false
webapi.queue_add_next(this.playlist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
},
open_playlist: function () {
this.show_details_modal = false
this.$router.push({ path: '/playlists/' + this.playlist.id })
}
}
name: 'ListItemPlaylist',
props: ['playlist']
}
</script>

View File

@ -1,6 +1,5 @@
<template>
<div class="media" v-if="is_next || !show_only_next_items">
<!---->
<div class="media-left" v-if="edit_mode">
<span class="icon has-text-grey fd-is-movable handle"><i class="mdi mdi-drag-horizontal mdi-18px"></i></span>
</div>
@ -11,84 +10,18 @@
<h2 class="subtitle is-7" :class="{ 'has-text-primary': item.id === state.item_id, 'has-text-grey-light': !is_next, 'has-text-grey': is_next && item.id !== state.item_id }">{{ item.album }}</h2>
</div>
<div class="media-right">
<a v-on:click="remove" 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>
<a @click="show_details_modal = true" v-if="!edit_mode">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog v-if="!edit_mode" :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ item.title }}
</p>
<p class="subtitle">
{{ item.artist }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<span class="title is-6">{{ item.album }}</span>
</p>
<p v-if="item.album_artist">
<span class="heading">Album artist</span>
<span class="title is-6">{{ item.album_artist }}</span>
</p>
<p v-if="item.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ item.year }}</span>
</p>
<p>
<span class="heading">Genre</span>
<span class="title is-6">{{ item.genre }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ item.track_number }} / {{ item.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ item.length_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ item.path }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="remove">
<span class="icon"><i class="mdi mdi-delete mdi-18px"></i></span> <span>Remove</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'PartQueueItem',
components: { ModalDialog },
name: 'ListItemQueueItem',
props: ['item', 'position', 'current_position', 'show_only_next_items', 'edit_mode'],
data () {
return {
show_details_modal: false
}
},
computed: {
state () {
return this.$store.state.player
@ -100,13 +33,7 @@ export default {
},
methods: {
remove: function () {
this.show_details_modal = false
webapi.queue_remove(this.item.id)
},
play: function () {
this.show_details_modal = false
webapi.player_play({ 'item_id': this.item.id })
}
}

View File

@ -1,142 +1,20 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="play">
<h1 class="title is-6">{{ track.title }}</h1>
<h2 class="subtitle is-7 has-text-grey"><b>{{ track.artist }}</b></h2>
<h2 class="subtitle is-7 has-text-grey">{{ track.album }}</h2>
<template functional>
<div class="media" :id="'index_' + props.track.title_sort.charAt(0).toUpperCase()">
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
<h1 class="title is-6">{{ props.track.title }}</h1>
<h2 class="subtitle is-7 has-text-grey"><b>{{ props.track.artist }}</b></h2>
<h2 class="subtitle is-7 has-text-grey">{{ props.track.album }}</h2>
</div>
<div class="media-right">
<a @click="show_details_modal = true">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<modal-dialog :show="show_details_modal" @close="show_details_modal = false">
<template slot="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.title }}
</p>
<p class="subtitle">
{{ track.artist }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ track.album }}</a>
</p>
<p v-if="track.album_artist && track.media_kind !== 'audiobook'">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ track.album_artist }}</a>
</p>
<p v-if="track.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{ track.date_released | time('L')}}</span>
</p>
<p v-else-if="track.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ track.year }}</span>
</p>
<p>
<span class="heading">Genre</span>
<span class="title is-6">{{ track.genre }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ track.length_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.path }}</span>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ track.media_kind }} - {{ track.data_kind }}</span>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{ track.time_added | time('L LT')}}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play_track">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</template>
</modal-dialog>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import webapi from '@/webapi'
export default {
name: 'PartTrack',
components: { ModalDialog },
props: ['track', 'position', 'context_uri'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.context_uri, false, this.position)
},
play_track: function () {
this.show_details_modal = false
webapi.player_play_uri(this.track.uri, false)
},
queue_add: function () {
this.show_details_modal = false
webapi.queue_add(this.track.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Track appended to queue', type: 'info', timeout: 2000 })
)
},
queue_add_next: function () {
this.show_details_modal = false
webapi.queue_add_next(this.track.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
},
open_album: function () {
this.show_details_modal = false
if (this.track.media_kind === 'podcast') {
this.$router.push({ path: '/podcasts/' + this.track.album_id })
} else if (this.track.media_kind === 'audiobook') {
this.$router.push({ path: '/audiobooks/' + this.track.album_id })
} else {
this.$router.push({ path: '/music/albums/' + this.track.album_id })
}
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/artists/' + this.track.album_artist_id })
}
}
name: 'ListItemTrack',
props: ['track']
}
</script>

View File

@ -0,0 +1,113 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<figure class="image is-square fd-has-margin-bottom" v-show="artwork_visible">
<img :src="artwork_url" @load="artwork_loaded" @error="artwork_error" class="fd-has-shadow">
</figure>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<div class="content is-small">
<p v-if="album.artist && media_kind !== 'audiobook'">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artist }}</a>
</p>
<p v-if="album.artist && media_kind === 'audiobook'">
<span class="heading">Album artist</span>
<span class="title is-6">{{ album.artist }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ album.track_count }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogAlbum',
props: [ 'show', 'album', 'media_kind' ],
data () {
return {
artwork_visible: false
}
},
computed: {
artwork_url: function () {
if (this.album.artwork_url && this.album.artwork_url.startsWith('/')) {
return this.album.artwork_url + '?maxwidth=600&maxheight=600'
}
return this.album.artwork_url
}
},
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.album.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.album.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.album.uri)
},
open_album: function () {
if (this.media_kind === 'podcast') {
this.$router.push({ path: '/podcasts/' + this.album.id })
} else if (this.media_kind === 'audiobook') {
this.$router.push({ path: '/audiobooks/' + this.album.id })
} else {
this.$router.push({ path: '/music/albums/' + this.album.id })
}
},
open_artist: function () {
this.$router.push({ path: '/music/artists/' + this.album.artist_id })
},
artwork_loaded: function () {
this.artwork_visible = true
},
artwork_error: function () {
this.artwork_visible = false
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,74 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ artist.album_count }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ artist.track_count }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogArtist',
props: [ 'show', 'artist' ],
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.artist.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.artist.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.artist.uri)
},
open_artist: function () {
this.$emit('close')
this.$router.push({ path: '/music/artists/' + this.artist.id })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,70 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_genre">{{ genre.name }}</a>
</p>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogGenre',
props: [ 'show', 'genre' ],
methods: {
play: function () {
this.$emit('close')
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.player_play_uri(data.albums.items.map(a => a.uri).join(','), false)
)
},
queue_add: function () {
this.$emit('close')
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.queue_add(data.albums.items.map(a => a.uri).join(','))
)
},
queue_add_next: function () {
this.$emit('close')
webapi.library_genre(this.genre.name).then(({ data }) =>
webapi.queue_add_next(data.albums.items.map(a => a.uri).join(','))
)
},
open_genre: function () {
this.$emit('close')
this.$router.push({ name: 'Genre', params: { genre: this.genre.name } })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,70 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.path }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogPlaylist',
props: [ 'show', 'playlist' ],
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.playlist.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.playlist.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.playlist.uri)
},
open_playlist: function () {
this.$emit('close')
this.$router.push({ path: '/playlists/' + this.playlist.id })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,88 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ item.title }}
</p>
<p class="subtitle">
{{ item.artist }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<span class="title is-6">{{ item.album }}</span>
</p>
<p v-if="item.album_artist">
<span class="heading">Album artist</span>
<span class="title is-6">{{ item.album_artist }}</span>
</p>
<p v-if="item.composer">
<span class="heading">Composer</span>
<span class="title is-6">{{ item.composer }}</span>
</p>
<p v-if="item.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ item.year }}</span>
</p>
<p>
<span class="heading">Genre</span>
<span class="title is-6">{{ item.genre }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ item.track_number }} / {{ item.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ item.length_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ item.path }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="remove">
<span class="icon"><i class="mdi mdi-delete"></i></span> <span class="is-size-7">Remove</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogQueueItem',
props: [ 'show', 'item' ],
methods: {
remove: function () {
this.$emit('close')
webapi.queue_remove(this.item.id)
},
play: function () {
this.$emit('close')
webapi.player_play({ 'item_id': this.item.id })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,134 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.title }}
</p>
<p class="subtitle">
{{ track.artist }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ track.album }}</a>
</p>
<p v-if="track.album_artist && track.media_kind !== 'audiobook'">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ track.album_artist }}</a>
</p>
<p v-if="track.composer">
<span class="heading">Composer</span>
<span class="title is-6">{{ track.composer }}</span>
</p>
<p v-if="track.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{ track.date_released | time('L')}}</span>
</p>
<p v-else-if="track.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ track.year }}</span>
</p>
<p>
<span class="heading">Genre</span>
<span class="title is-6">{{ track.genre }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ track.length_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.path }}</span>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ track.media_kind }} - {{ track.data_kind }}</span>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{ track.time_added | time('L LT') }}</span>
</p>
<p>
<span class="heading">Rating</span>
<span class="title is-6">{{ Math.floor(track.rating / 10) }} / 10</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play_track">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'ModalDialogTrack',
props: ['show', 'track'],
data () {
return {
}
},
methods: {
play_track: function () {
this.$emit('close')
webapi.player_play_uri(this.track.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.track.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.track.uri)
},
open_album: function () {
this.$emit('close')
if (this.track.media_kind === 'podcast') {
this.$router.push({ path: '/podcasts/' + this.track.album_id })
} else if (this.track.media_kind === 'audiobook') {
this.$router.push({ path: '/audiobooks/' + this.track.album_id })
} else {
this.$router.push({ path: '/music/albums/' + this.track.album_id })
}
},
open_artist: function () {
this.$emit('close')
this.$router.push({ path: '/music/artists/' + this.track.album_artist_id })
}
}
}
</script>
<style>
</style>

View File

@ -5,103 +5,20 @@
<h2 class="subtitle is-7 has-text-grey"><b>{{ album.artists[0].name }}</b></h2>
</div>
<div class="media-right">
<a @click="show_details">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<transition name="fade">
<div class="modal is-active" v-if="show_details_modal">
<div class="modal-background" @click="hide_details"></div>
<div class="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ album.release_date }}</span>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ album.album_type }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="hide_details"></button>
</div>
</transition>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyListItemAlbum',
props: ['album'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.album.uri, false)
},
queue_add: function () {
webapi.queue_add(this.album.uri).then(
// this.$store.commit(types.ADD_NOTIFICATION, { text: 'Album tracks appended to queue', timeout: 0 })
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 3000 })
)
this.show_details_modal = false
},
queue_add_next: function () {
webapi.queue_add_next(this.album.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Album tracks appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
show_details: function () {
this.show_details_modal = true
},
hide_details: function () {
this.show_details_modal = false
},
open_album: function () {
this.$router.push({ path: '/music/spotify/albums/' + this.album.id })
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
}
}
}

View File

@ -4,93 +4,18 @@
<h1 class="title is-6">{{ artist.name }}</h1>
</div>
<div class="media-right">
<a @click="show_details">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<transition name="fade">
<div class="modal is-active" v-if="show_details_modal">
<div class="modal-background" @click="hide_details"></div>
<div class="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Popularity / Followers</span>
<span class="title is-6">{{ artist.popularity }} / {{ artist.followers.total }}</span>
</p>
<p>
<span class="heading">Genres</span>
<span class="title is-6">{{ artist.genres.join(', ') }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="hide_details"></button>
</div>
</transition>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyListItemArtist',
props: ['artist'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.artist.uri, false)
},
queue_add: function () {
webapi.queue_add(this.artist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Artist tracks appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
queue_add_next: function () {
webapi.queue_add_next(this.artist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Artist tracks appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
show_details: function () {
this.show_details_modal = true
},
hide_details: function () {
this.show_details_modal = false
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/spotify/artists/' + this.artist.id })
}
}

View File

@ -5,97 +5,18 @@
<h2 class="subtitle is-7">{{ playlist.owner.display_name }}</h2>
</div>
<div class="media-right">
<a @click="show_details">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<transition name="fade">
<div class="modal is-active" v-if="show_details_modal">
<div class="modal-background" @click="hide_details"></div>
<div class="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Owner</span>
<span class="title is-6">{{ playlist.owner.display_name }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ playlist.tracks.total }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.uri }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="hide_details"></button>
</div>
</transition>
<slot name="actions"></slot>
</div>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyListItemPlaylist',
props: ['playlist'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.playlist.uri, false)
},
queue_add: function () {
webapi.queue_add(this.playlist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Playlist appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
queue_add_next: function () {
webapi.queue_add_next(this.playlist.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Playlist tracks appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
show_details: function () {
this.show_details_modal = true
},
hide_details: function () {
this.show_details_modal = false
},
open_playlist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/spotify/playlists/' + this.playlist.id })
}
}

View File

@ -5,64 +5,7 @@
<h2 class="subtitle is-7 has-text-grey"><b>{{ track.artists[0].name }}</b></h2>
</div>
<div class="media-right">
<a @click="show_details">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
<transition name="fade">
<div class="modal is-active" v-if="show_details_modal">
<div class="modal-background" @click="hide_details"></div>
<div class="modal-content">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.name }}
</p>
<p class="subtitle">
{{ track.artists[0].name }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ album.release_date }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ track.duration_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.uri }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus mdi-18px"></i></span> <span>Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play mdi-18px"></i></span> <span>Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play mdi-18px"></i></span> <span>Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="hide_details"></button>
</div>
</transition>
<slot name="actions"></slot>
</div>
</div>
</template>
@ -75,48 +18,9 @@ export default {
props: ['track', 'position', 'album', 'context_uri'],
data () {
return {
show_details_modal: false
}
},
methods: {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.context_uri, false, this.position)
},
queue_add: function () {
webapi.queue_add(this.track.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Track appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
queue_add_next: function () {
webapi.queue_add_next(this.track.uri).then(() =>
this.$store.dispatch('add_notification', { text: 'Track appended to queue', type: 'info', timeout: 2000 })
)
this.show_details_modal = false
},
show_details: function () {
this.show_details_modal = true
},
hide_details: function () {
this.show_details_modal = false
},
open_album: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/spotify/albums/' + this.album.id })
},
open_artist: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
}
}
}

View File

@ -0,0 +1,107 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<figure class="image is-square fd-has-margin-bottom" v-show="artwork_visible">
<img :src="artwork_url" @load="artwork_loaded" @error="artwork_error" class="fd-has-shadow">
</figure>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ album.release_date }}</span>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ album.album_type }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyModalDialogAlbum',
props: [ 'show', 'album' ],
data () {
return {
artwork_visible: false
}
},
computed: {
artwork_url: function () {
if (this.album.images && this.album.images.length > 0) {
return this.album.images[0].url
}
return ''
}
},
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.album.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.album.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.album.uri)
},
open_album: function () {
this.$router.push({ path: '/music/spotify/albums/' + this.album.id })
},
open_artist: function () {
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
},
artwork_loaded: function () {
this.artwork_visible = true
},
artwork_error: function () {
this.artwork_visible = false
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,73 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Popularity / Followers</span>
<span class="title is-6">{{ artist.popularity }} / {{ artist.followers.total }}</span>
</p>
<p>
<span class="heading">Genres</span>
<span class="title is-6">{{ artist.genres.join(', ') }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyModalDialogArtist',
props: [ 'show', 'artist' ],
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.artist.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.artist.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.artist.uri)
},
open_artist: function () {
this.$router.push({ path: '/music/spotify/artists/' + this.artist.id })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,77 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Owner</span>
<span class="title is-6">{{ playlist.owner.display_name }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ playlist.tracks.total }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.uri }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyModalDialogPlaylist',
props: [ 'show', 'playlist' ],
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.playlist.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.playlist.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.playlist.uri)
},
open_playlist: function () {
this.$router.push({ path: '/music/spotify/playlists/' + this.playlist.id })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,96 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.name }}
</p>
<p class="subtitle">
{{ track.artists[0].name }}
</p>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ album.name }}</a>
</p>
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ album.release_date }}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ track.duration_ms | duration }}</span>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.uri }}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>
</transition>
</div>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'SpotifyModalDialogTrack',
props: [ 'show', 'track', 'album' ],
methods: {
play: function () {
this.$emit('close')
webapi.player_play_uri(this.track.uri, false)
},
queue_add: function () {
this.$emit('close')
webapi.queue_add(this.track.uri)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_add_next(this.track.uri)
},
open_album: function () {
this.$router.push({ path: '/music/spotify/albums/' + this.album.id })
},
open_artist: function () {
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
}
}
}
</script>
<style>
</style>

View File

@ -1,5 +1,5 @@
<template>
<section class="section fd-tabs-section" v-if="spotify_enabled">
<section class="section fd-remove-padding-bottom" v-if="spotify_enabled">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">

View File

@ -38,11 +38,20 @@ a.navbar-item {
margin-bottom: 24px;
}
.fd-remove-padding-bottom {
padding-bottom: 0;
}
.fd-has-padding-left-right {
padding-left: 24px;
padding-right: 24px;
}
.fd-is-square .button {
height: 27px;
width: 27px;
}
.fd-is-text-clipped {
white-space: nowrap;
overflow: hidden;
@ -65,7 +74,17 @@ a.navbar-item {
}
.fd-tabs-section {
padding-bottom: 0;
padding-bottom: 3px;
padding-top: 3px;
background: white;
top: 3.25rem;
z-index: 20;
position: fixed;
width: 100%;
}
section.fd-tabs-section + section.fd-content {
margin-top: 24px;
}
.fd-progress-bar {
@ -131,3 +150,7 @@ a.navbar-item {
max-height: calc(100vh - 200px);
overflow: auto;
}
.fd-modal-card .card {
margin-left: 16px;
margin-right: 16px;
}

View File

@ -18,7 +18,14 @@
</template>
<template slot="content">
<p class="heading has-text-centered-mobile">{{ album.track_count }} tracks</p>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" :position="index" :context_uri="album.uri"></list-item-track>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" @click="play_track(index)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -27,6 +34,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const albumData = {
@ -46,12 +54,15 @@ const albumData = {
export default {
name: 'PageAlbum',
mixins: [ LoadDataBeforeEnterMixin(albumData) ],
components: { ContentWithHeading, ListItemTrack },
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack },
data () {
return {
album: {},
tracks: []
tracks: [],
show_details_modal: false,
selected_track: {}
}
},
@ -63,6 +74,15 @@ export default {
play: function () {
webapi.player_play_uri(this.album.uri, true)
},
play_track: function (position) {
webapi.player_play_uri(this.album.uri, false, position)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}

View File

@ -3,6 +3,9 @@
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">Albums</p>
<p class="heading">{{ albums.total }} albums</p>
@ -16,7 +19,18 @@
</a>
</template>
<template slot="content">
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" v-if="!hide_singles || album.track_count > 2"></list-item-album>
<list-item-album v-for="album in albums.items"
:key="album.id"
:album="album"
@click="open_album(album)"
v-if="!hide_singles || album.track_count > 2">
<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>
</template>
</list-item-album>
<modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -26,7 +40,9 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemAlbum from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
@ -37,17 +53,24 @@ const albumsData = {
set: function (vm, response) {
vm.albums = response.data
vm.index_list = [...new Set(vm.albums.items
.filter(album => !vm.$store.state.hide_singles || album.track_count > 2)
.map(album => album.name_sort.charAt(0).toUpperCase()))]
}
}
export default {
name: 'PageAlbums',
mixins: [ LoadDataBeforeEnterMixin(albumsData) ],
components: { ContentWithHeading, TabsMusic, ListItemAlbum },
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemAlbum, ModalDialogAlbum },
data () {
return {
albums: {}
albums: { items: [] },
index_list: [],
show_details_modal: false,
selected_album: {}
}
},
@ -60,6 +83,23 @@ export default {
methods: {
update_hide_singles: function (e) {
this.$store.commit(types.HIDE_SINGLES, !this.hide_singles)
},
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
},
watch: {
'hide_singles' () {
this.index_list = [...new Set(this.albums.items
.filter(album => !this.$store.state.hide_singles || album.track_count > 2)
.map(album => album.name_sort.charAt(0).toUpperCase()))]
}
}
}

View File

@ -10,7 +10,14 @@
</template>
<template 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-item-album v-for="album in albums.items" :key="album.id" :album="album"></list-item-album>
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" @click="open_album(album)">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-album>
<modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -19,6 +26,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemAlbum from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const artistData = {
@ -38,12 +46,15 @@ const artistData = {
export default {
name: 'PageArtist',
mixins: [ LoadDataBeforeEnterMixin(artistData) ],
components: { ContentWithHeading, ListItemAlbum },
components: { ContentWithHeading, ListItemAlbum, ModalDialogAlbum },
data () {
return {
artist: {},
albums: {}
albums: {},
show_details_modal: false,
selected_album: {}
}
},
@ -54,6 +65,15 @@ export default {
play: function () {
webapi.player_play_uri(this.albums.items.map(a => a.uri).join(','), true)
},
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -1,6 +1,9 @@
<template>
<div>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">{{ artist.name }}</p>
</template>
@ -11,7 +14,14 @@
</template>
<template 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-item-track v-for="(track, index) in tracks.items" :key="track.id" :track="track" :position="index" :context_uri="track.uri"></list-item-track>
<list-item-track v-for="(track, index) in tracks.items" :key="track.id" :track="track" @click="play_track(index)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -20,7 +30,9 @@
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const tracksData = {
@ -38,14 +50,24 @@ const tracksData = {
}
export default {
name: 'PageTracks',
name: 'PageArtistTracks',
mixins: [ LoadDataBeforeEnterMixin(tracksData) ],
components: { ContentWithHeading, ListItemTrack },
components: { ContentWithHeading, ListItemTrack, IndexButtonList, ModalDialogTrack },
data () {
return {
artist: {},
tracks: {}
tracks: { items: [] },
show_details_modal: false,
selected_track: {}
}
},
computed: {
index_list () {
return [...new Set(this.tracks.items
.map(track => track.title_sort.charAt(0).toUpperCase()))]
}
},
@ -57,6 +79,15 @@ export default {
play: function () {
webapi.player_play_uri(this.tracks.items.map(a => a.uri).join(','), true)
},
play_track: function (position) {
webapi.player_play_uri(this.tracks.items.map(a => a.uri).join(','), false, position)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}

View File

@ -3,6 +3,9 @@
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">Artists</p>
<p class="heading">{{ artists.total }} artists</p>
@ -16,7 +19,18 @@
</a>
</template>
<template slot="content">
<list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist" v-if="!hide_singles || artist.track_count > (artist.album_count * 2)"></list-item-artist>
<list-item-artist v-for="artist in artists.items"
:key="artist.id"
:artist="artist"
@click="open_artist(artist)"
v-if="!hide_singles || artist.track_count > (artist.album_count * 2)">
<template slot="actions">
<a @click="open_dialog(artist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-artist>
<modal-dialog-artist :show="show_details_modal" :artist="selected_artist" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -26,7 +40,9 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemArtist from '@/components/ListItemArtist'
import ModalDialogArtist from '@/components/ModalDialogArtist'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
@ -43,23 +59,41 @@ const artistsData = {
export default {
name: 'PageArtists',
mixins: [ LoadDataBeforeEnterMixin(artistsData) ],
components: { ContentWithHeading, TabsMusic, ListItemArtist },
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemArtist, ModalDialogArtist },
data () {
return {
artists: {}
artists: { items: [] },
show_details_modal: false,
selected_artist: {}
}
},
computed: {
hide_singles () {
return this.$store.state.hide_singles
},
index_list () {
return [...new Set(this.artists.items
.filter(artist => !this.$store.state.hide_singles || artist.track_count > (artist.album_count * 2))
.map(artist => artist.name_sort.charAt(0).toUpperCase()))]
}
},
methods: {
update_hide_singles: function (e) {
this.$store.commit(types.HIDE_SINGLES, !this.hide_singles)
},
open_artist: function (artist) {
this.$router.push({ path: '/music/artists/' + artist.id })
},
open_dialog: function (artist) {
this.selected_artist = artist
this.show_details_modal = true
}
}
}

View File

@ -14,7 +14,14 @@
</template>
<template slot="content">
<p class="heading has-text-centered-mobile">{{ album.track_count }} tracks</p>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" :position="index" :context_uri="album.uri"></list-item-track>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" @click="play_track(index)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -23,6 +30,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const albumData = {
@ -42,18 +50,30 @@ const albumData = {
export default {
name: 'PageAudiobook',
mixins: [ LoadDataBeforeEnterMixin(albumData) ],
components: { ContentWithHeading, ListItemTrack },
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack },
data () {
return {
album: {},
tracks: []
tracks: [],
show_details_modal: false,
selected_track: {}
}
},
methods: {
play: function () {
webapi.player_play_uri(this.album.uri, false)
},
play_track: function (position) {
webapi.player_play_uri(this.album.uri, false, position)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}

View File

@ -6,7 +6,14 @@
<p class="heading">{{ albums.total }} audiobooks</p>
</template>
<template slot="content">
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" :media_kind="'audiobook'"></list-item-album>
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" :media_kind="'audiobook'" @click="open_album(album)">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-album>
<modal-dialog-album :show="show_details_modal" :album="selected_album" :media_kind="'audiobook'" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -16,6 +23,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemAlbum from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const albumsData = {
@ -31,11 +39,25 @@ const albumsData = {
export default {
name: 'PageAudiobooks',
mixins: [ LoadDataBeforeEnterMixin(albumsData) ],
components: { ContentWithHeading, ListItemAlbum },
components: { ContentWithHeading, ListItemAlbum, ModalDialogAlbum },
data () {
return {
albums: {}
albums: {},
show_details_modal: false,
selected_album: {}
}
},
methods: {
open_album: function (album) {
this.$router.push({ path: '/audiobooks/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -9,7 +9,14 @@
<p class="heading">albums</p>
</template>
<template slot="content">
<list-item-album v-for="album in recently_added.items" :key="album.id" :album="album"></list-item-album>
<list-item-album v-for="album in recently_added.items" :key="album.id" :album="album" @click="open_album(album)">
<template 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>
</list-item-album>
<modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
<template slot="footer">
<nav class="level">
@ -27,7 +34,14 @@
<p class="heading">tracks</p>
</template>
<template slot="content">
<list-item-track v-for="track in recently_played.items" :key="track.id" :track="track" :position="0" :context_uri="track.uri"></list-item-track>
<list-item-track v-for="track in recently_played.items" :key="track.id" :track="track" @click="play_track(track)">
<template 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>
</list-item-track>
<modal-dialog-track :show="show_track_details_modal" :track="selected_track" @close="show_track_details_modal = false" />
</template>
<template slot="footer">
<nav class="level">
@ -46,6 +60,8 @@ import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListItemAlbum from '@/components/ListItemAlbum'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const browseData = {
@ -65,18 +81,42 @@ const browseData = {
export default {
name: 'PageBrowse',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, ListItemAlbum, ListItemTrack },
components: { ContentWithHeading, TabsMusic, ListItemAlbum, ListItemTrack, ModalDialogTrack, ModalDialogAlbum },
data () {
return {
recently_added: {},
recently_played: {}
recently_played: {},
show_track_details_modal: false,
selected_track: {},
show_album_details_modal: false,
selected_album: {}
}
},
methods: {
open_browse: function (type) {
this.$router.push({ path: '/music/browse/' + type })
},
open_track_dialog: function (track) {
this.selected_track = track
this.show_track_details_modal = true
},
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_album_dialog: function (album) {
this.selected_album = album
this.show_album_details_modal = true
},
play_track: function (track) {
webapi.player_play_uri(track.uri, false)
}
}
}

View File

@ -8,7 +8,14 @@
<p class="heading">albums</p>
</template>
<template slot="content">
<list-item-album v-for="album in recently_added.items" :key="album.id" :album="album"></list-item-album>
<list-item-album v-for="album in recently_added.items" :key="album.id" :album="album" @click="open_album(album)">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-album>
<modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -19,6 +26,7 @@ import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListItemAlbum from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const browseData = {
@ -38,11 +46,25 @@ const browseData = {
export default {
name: 'PageBrowseType',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, ListItemAlbum },
components: { ContentWithHeading, TabsMusic, ListItemAlbum, ModalDialogAlbum },
data () {
return {
recently_added: {}
recently_added: {},
show_details_modal: false,
selected_album: {}
}
},
methods: {
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -8,7 +8,14 @@
<p class="heading">tracks</p>
</template>
<template slot="content">
<list-item-track v-for="track in recently_played.items" :key="track.id" :track="track" :position="0" :context_uri="track.uri"></list-item-track>
<list-item-track v-for="track in recently_played.items" :key="track.id" :track="track" @click="play_track(track)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -19,6 +26,7 @@ import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const browseData = {
@ -38,11 +46,25 @@ const browseData = {
export default {
name: 'PageBrowseType',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, ListItemTrack },
components: { ContentWithHeading, TabsMusic, ListItemTrack, ModalDialogTrack },
data () {
return {
recently_played: {}
recently_played: {},
show_details_modal: false,
selected_track: {}
}
},
methods: {
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
},
play_track: function (track) {
webapi.player_play_uri(track.uri, false)
}
}
}

View File

@ -1,11 +1,11 @@
<template>
<div>
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">{{ name }}</p>
<p class="heading">{{ genreAlbums.total }} albums</p>
</template>
<template slot="heading-right">
<a class="button is-small is-dark is-rounded" @click="play">
@ -13,7 +13,15 @@
</a>
</template>
<template slot="content">
<list-item-albums v-for="album in genreAlbums.items" :key="album.id" :album="album" :links="links"></list-item-albums>
<p class="heading has-text-centered-mobile">{{ genre_albums.total }} albums | <a class="has-text-link" @click="open_tracks">tracks</a></p>
<list-item-albums v-for="album in genre_albums.items" :key="album.id" :album="album" @click="open_album(album)">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-albums>
<modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -23,7 +31,9 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemAlbums from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const genreData = {
@ -33,40 +43,49 @@ const genreData = {
set: function (vm, response) {
vm.name = vm.$route.params.genre
vm.genreAlbums = response.data.albums
var li = 0
var v = null
var i
for (i = 0; i < vm.genreAlbums.items.length; i++) {
var n = vm.genreAlbums.items[i].name_sort.charAt(0).toUpperCase()
if (n !== v) {
var obj = {}
obj.n = n
obj.a = 'idx_nav_' + li
vm.links.push(obj)
li++
v = n
}
}
vm.genre_albums = response.data.albums
}
}
export default {
name: 'PageGenre',
mixins: [ LoadDataBeforeEnterMixin(genreData) ],
components: { ContentWithHeading, TabsMusic, ListItemAlbums },
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemAlbums, ModalDialogAlbum },
data () {
return {
name: '',
genreAlbums: {},
links: []
genre_albums: { items: [] },
show_details_modal: false,
selected_album: {}
}
},
computed: {
index_list () {
return [...new Set(this.genre_albums.items
.map(album => album.name.charAt(0).toUpperCase()))]
}
},
methods: {
open_tracks: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/genres/' + this.name + '/tracks' })
},
play: function () {
webapi.player_play_uri(this.genreAlbums.items.map(a => a.uri).join(','), true)
webapi.player_play_uri(this.genre_albums.items.map(a => a.uri).join(','), true)
},
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -0,0 +1,94 @@
<template>
<div>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">{{ genre }}</p>
</template>
<template slot="heading-right">
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><i class="mdi mdi-shuffle"></i></span> <span>Shuffle</span>
</a>
</template>
<template slot="content">
<p class="heading has-text-centered-mobile"><a class="has-text-link" @click="open_genre">albums</a> | {{ tracks.total }} tracks</p>
<list-item-track v-for="(track, index) in tracks.items" :key="track.id" :track="track" @click="play_track(index)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
</template>
<script>
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import IndexButtonList from '@/components/IndexButtonList'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const tracksData = {
load: function (to) {
return webapi.library_genre_tracks(to.params.genre)
},
set: function (vm, response) {
vm.genre = vm.$route.params.genre
vm.tracks = response.data.tracks
}
}
export default {
name: 'PageGenreTracks',
mixins: [ LoadDataBeforeEnterMixin(tracksData) ],
components: { ContentWithHeading, ListItemTrack, IndexButtonList, ModalDialogTrack },
data () {
return {
tracks: { items: [] },
genre: '',
show_details_modal: false,
selected_track: {}
}
},
computed: {
index_list () {
return [...new Set(this.tracks.items
.map(track => track.title_sort.charAt(0).toUpperCase()))]
}
},
methods: {
open_genre: function () {
this.show_details_modal = false
this.$router.push({ path: '/music/genres/' + this.genre })
},
play: function () {
webapi.player_play_uri(this.tracks.items.map(a => a.uri).join(','), true)
},
play_track: function (position) {
webapi.player_play_uri(this.tracks.items.map(a => a.uri).join(','), false, position)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}
</script>
<style>
</style>

View File

@ -3,12 +3,22 @@
<tabs-music></tabs-music>
<content-with-heading>
<template slot="options">
<index-button-list :index="index_list"></index-button-list>
</template>
<template slot="heading-left">
<p class="title is-4">Genres</p>
<p class="heading">{{ genres.total }} genres</p>
</template>
<template slot="content">
<list-item-genre v-for="genre in genres.items" :key="genre.name" :genre="genre"></list-item-genre>
<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)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-genre>
<modal-dialog-genre :show="show_details_modal" :genre="selected_genre" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -18,7 +28,9 @@
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 webapi from '@/webapi'
const genresData = {
@ -34,15 +46,33 @@ const genresData = {
export default {
name: 'PageGenres',
mixins: [ LoadDataBeforeEnterMixin(genresData) ],
components: { ContentWithHeading, TabsMusic, ListItemGenre },
components: { ContentWithHeading, TabsMusic, IndexButtonList, ListItemGenre, ModalDialogGenre },
data () {
return {
genres: {}
genres: { items: [] },
show_details_modal: false,
selected_genre: {}
}
},
computed: {
index_list () {
return [...new Set(this.genres.items
.map(genre => genre.name.charAt(0).toUpperCase()))]
}
},
methods: {
open_genre: function (genre) {
this.$router.push({ name: 'Genre', params: { genre: genre.name } })
},
open_dialog: function (genre) {
this.selected_genre = genre
this.show_details_modal = true
}
}
}
</script>

View File

@ -14,7 +14,15 @@
</div>
</div>
<div class="hero-body fd-is-fullheight-body has-text-centered" v-show="artwork_visible">
<img :src="artwork_url" class="fd-has-shadow fd-image-fullheight" @load="artwork_loaded" @error="artwork_error">
<img :src="artwork_url" class="fd-has-shadow fd-image-fullheight fd-has-action"
@load="artwork_loaded"
@error="artwork_error"
@click="open_dialog(now_playing)">
</div>
<div class="hero-body fd-is-fullheight-body has-text-centered" v-show="!artwork_visible">
<a @click="open_dialog(now_playing)" class="button is-white is-medium">
<span class="icon has-text-grey-light"><i class="mdi mdi-information-outline"></i></span>
</a>
</div>
<div class="hero-foot fd-has-padding-left-right">
<div class="container has-text-centered fd-has-margin-bottom">
@ -41,11 +49,13 @@
<player-button-consume class="button is-medium is-light"></player-button-consume>
</div>
</div>
<modal-dialog-queue-item :show="show_details_modal" :item="selected_item" @close="show_details_modal = false" />
</div>
</section>
</template>
<script>
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem'
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause'
import PlayerButtonNext from '@/components/PlayerButtonNext'
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious'
@ -58,13 +68,16 @@ import * as types from '@/store/mutation_types'
export default {
name: 'PageNowPlaying',
components: { PlayerButtonPlayPause, PlayerButtonNext, PlayerButtonPrevious, PlayerButtonShuffle, PlayerButtonConsume, PlayerButtonRepeat, RangeSlider },
components: { ModalDialogQueueItem, PlayerButtonPlayPause, PlayerButtonNext, PlayerButtonPrevious, PlayerButtonShuffle, PlayerButtonConsume, PlayerButtonRepeat, RangeSlider },
data () {
return {
item_progress_ms: 0,
interval_id: 0,
artwork_visible: false
artwork_visible: false,
show_details_modal: false,
selected_item: {}
}
},
@ -118,6 +131,11 @@ export default {
artwork_error: function () {
this.artwork_visible = false
},
open_dialog: function (item) {
this.selected_item = item
this.show_details_modal = true
}
},

View File

@ -10,7 +10,14 @@
</template>
<template slot="content">
<p class="heading has-text-centered-mobile">{{ tracks.length }} tracks</p>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" :position="index" :context_uri="playlist.uri"></list-item-track>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" @click="play_track(index)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -19,6 +26,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const playlistData = {
@ -38,18 +46,30 @@ const playlistData = {
export default {
name: 'PagePlaylist',
mixins: [ LoadDataBeforeEnterMixin(playlistData) ],
components: { ContentWithHeading, ListItemTrack },
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack },
data () {
return {
playlist: {},
tracks: []
tracks: [],
show_details_modal: false,
selected_track: {}
}
},
methods: {
play: function () {
webapi.player_play_uri(this.playlist.uri, true)
},
play_track: function (position) {
webapi.player_play_uri(this.playlist.uri, false, position)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}

View File

@ -5,7 +5,14 @@
<p class="heading">{{ playlists.total }} playlists</p>
</template>
<template slot="content">
<list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist"></list-item-playlist>
<list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
<template slot="actions">
<a @click="open_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-playlist>
<modal-dialog-playlist :show="show_details_modal" :playlist="selected_playlist" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -15,6 +22,7 @@ import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import TabsMusic from '@/components/TabsMusic'
import ListItemPlaylist from '@/components/ListItemPlaylist'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist'
import webapi from '@/webapi'
const playlistsData = {
@ -30,11 +38,25 @@ const playlistsData = {
export default {
name: 'PagePlaylists',
mixins: [ LoadDataBeforeEnterMixin(playlistsData) ],
components: { ContentWithHeading, TabsMusic, ListItemPlaylist },
components: { ContentWithHeading, TabsMusic, ListItemPlaylist, ModalDialogPlaylist },
data () {
return {
playlists: {}
playlists: {},
show_details_modal: false,
selected_playlist: {}
}
},
methods: {
open_playlist: function (playlist) {
this.$router.push({ path: '/playlists/' + playlist.id })
},
open_dialog: function (playlist) {
this.selected_playlist = playlist
this.show_details_modal = true
}
}
}

View File

@ -13,7 +13,14 @@
</template>
<template slot="content">
<p class="heading has-text-centered-mobile">{{ album.track_count }} tracks</p>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" :position="index" :context_uri="album.uri"></list-item-track>
<list-item-track v-for="track in tracks" :key="track.id" :track="track" @click="play_track(track)">
<template slot="actions">
<a @click="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-track>
<modal-dialog-track :show="show_details_modal" :track="selected_track" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -22,6 +29,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemTrack from '@/components/ListItemTrack'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import webapi from '@/webapi'
const albumData = {
@ -41,18 +49,30 @@ const albumData = {
export default {
name: 'PagePodcast',
mixins: [ LoadDataBeforeEnterMixin(albumData) ],
components: { ContentWithHeading, ListItemTrack },
components: { ContentWithHeading, ListItemTrack, ModalDialogTrack },
data () {
return {
album: {},
tracks: []
tracks: [],
show_details_modal: false,
selected_track: {}
}
},
methods: {
play: function () {
webapi.player_play_uri(this.album.uri, false)
},
play_track: function (track) {
webapi.player_play_uri(track.uri, false)
},
open_dialog: function (track) {
this.selected_track = track
this.show_details_modal = true
}
}
}

View File

@ -6,7 +6,14 @@
<p class="heading">{{ albums.total }} podcasts</p>
</template>
<template slot="content">
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" :media_kind="'podcast'"></list-item-album>
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" :media_kind="'podcast'" @click="open_album(album)">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</list-item-album>
<modal-dialog-album :show="show_details_modal" :album="selected_album" :media_kind="'podcast'" @close="show_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -16,6 +23,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemAlbum from '@/components/ListItemAlbum'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import webapi from '@/webapi'
const albumsData = {
@ -31,11 +39,25 @@ const albumsData = {
export default {
name: 'PagePodcasts',
mixins: [ LoadDataBeforeEnterMixin(albumsData) ],
components: { ContentWithHeading, ListItemAlbum },
components: { ContentWithHeading, ListItemAlbum, ModalDialogAlbum },
data () {
return {
albums: {}
albums: {},
show_details_modal: false,
selected_album: {}
}
},
methods: {
open_album: function (album) {
this.$router.push({ path: '/podcasts/' + album.id })
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -40,8 +40,18 @@
:key="item.id" :item="item" :position="index"
:current_position="current_position"
:show_only_next_items="show_only_next_items"
:edit_mode="edit_mode"></list-item-queue-item>
: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>
</draggable>
<modal-dialog-queue-item :show="show_details_modal" :item="selected_item" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -49,17 +59,21 @@
<script>
import ContentWithHeading from '@/templates/ContentWithHeading'
import ListItemQueueItem from '@/components/ListItemQueueItem'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import draggable from 'vuedraggable'
export default {
name: 'PageQueue',
components: { ContentWithHeading, ListItemQueueItem, draggable },
components: { ContentWithHeading, ListItemQueueItem, draggable, ModalDialogQueueItem },
data () {
return {
edit_mode: false
edit_mode: false,
show_details_modal: false,
selected_item: {}
}
},
@ -92,6 +106,10 @@ export default {
this.$store.commit(types.SHOW_ONLY_NEXT_ITEMS, !this.show_only_next_items)
},
remove: function (item) {
webapi.queue_remove(item.id)
},
move_item: function (e) {
var oldPosition = !this.show_only_next_items ? e.oldIndex : e.oldIndex + this.current_position
var item = this.queue_items[oldPosition]
@ -99,6 +117,11 @@ export default {
if (newPosition !== oldPosition) {
webapi.queue_move(item.id, newPosition)
}
},
open_dialog: function (item) {
this.selected_item = item
this.show_details_modal = true
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div>
<!-- Search field + recent searches -->
<section class="section fd-tabs-section">
<section class="section fd-remove-padding-bottom">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
@ -31,7 +31,14 @@
<p class="title is-4">Tracks</p>
</template>
<template slot="content">
<list-item-track v-for="track in tracks.items" :key="track.id" :track="track" :position="0" :context_uri="track.uri"></list-item-track>
<list-item-track v-for="track in tracks.items" :key="track.id" :track="track" @click="play_track(track)">
<template 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>
</list-item-track>
<modal-dialog-track :show="show_track_details_modal" :track="selected_track" @close="show_track_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_tracks_button" class="level">
@ -49,7 +56,14 @@
<p class="title is-4">Artists</p>
</template>
<template slot="content">
<list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist"></list-item-artist>
<list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist" @click="open_artist(artist)">
<template 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>
</list-item-artist>
<modal-dialog-artist :show="show_artist_details_modal" :artist="selected_artist" @close="show_artist_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_artists_button" class="level">
@ -67,7 +81,14 @@
<p class="title is-4">Albums</p>
</template>
<template slot="content">
<list-item-album v-for="album in albums.items" :key="album.id" :album="album"></list-item-album>
<list-item-album v-for="album in albums.items" :key="album.id" :album="album" @click="open_album(album)">
<template 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>
</list-item-album>
<modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_albums_button" class="level">
@ -85,7 +106,14 @@
<p class="title is-4">Playlists</p>
</template>
<template slot="content">
<list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist"></list-item-playlist>
<list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
<template 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>
</list-item-playlist>
<modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_playlists_button" class="level">
@ -106,12 +134,16 @@ import ListItemTrack from '@/components/ListItemTrack'
import ListItemArtist from '@/components/ListItemArtist'
import ListItemAlbum from '@/components/ListItemAlbum'
import ListItemPlaylist from '@/components/ListItemPlaylist'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
import ModalDialogArtist from '@/components/ModalDialogArtist'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
export default {
name: 'PageSearch',
components: { ContentWithHeading, TabsSearch, ListItemTrack, ListItemArtist, ListItemAlbum, ListItemPlaylist },
components: { ContentWithHeading, TabsSearch, ListItemTrack, ListItemArtist, ListItemAlbum, ListItemPlaylist, ModalDialogTrack, ModalDialogAlbum, ModalDialogArtist, ModalDialogPlaylist },
data () {
return {
@ -119,7 +151,19 @@ export default {
tracks: { items: [], total: 0 },
artists: { items: [], total: 0 },
albums: { items: [], total: 0 },
playlists: { items: [], total: 0 }
playlists: { items: [], total: 0 },
show_track_details_modal: false,
selected_track: {},
show_album_details_modal: false,
selected_album: {},
show_artist_details_modal: false,
selected_artist: {},
show_playlist_details_modal: false,
selected_playlist: {}
}
},
@ -238,9 +282,45 @@ export default {
})
},
play_track: function (track) {
webapi.player_play_uri(track.uri, false)
},
open_artist: function (artist) {
this.$router.push({ path: '/music/artists/' + artist.id })
},
open_album: function (album) {
this.$router.push({ path: '/music/albums/' + album.id })
},
open_playlist: function (playlist) {
this.$router.push({ path: '/playlists/' + playlist.id })
},
open_recent_search: function (query) {
this.search_query = query
this.new_search()
},
open_track_dialog: function (track) {
this.selected_track = track
this.show_track_details_modal = true
},
open_album_dialog: function (album) {
this.selected_album = album
this.show_album_details_modal = true
},
open_artist_dialog: function (artist) {
this.selected_artist = artist
this.show_artist_details_modal = true
},
open_playlist_dialog: function (playlist) {
this.selected_playlist = playlist
this.show_playlist_details_modal = true
}
},

View File

@ -11,7 +11,14 @@
</template>
<template slot="content">
<p class="heading has-text-centered-mobile">{{ 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"></spotify-list-item-track>
<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">
<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>
<spotify-modal-dialog-track :show="show_track_details_modal" :track="selected_track" :album="album" @close="show_track_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -20,6 +27,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack'
import store from '@/store'
import webapi from '@/webapi'
import SpotifyWebApi from 'spotify-web-api-js'
@ -39,11 +47,14 @@ const albumData = {
export default {
name: 'PageAlbum',
mixins: [ LoadDataBeforeEnterMixin(albumData) ],
components: { ContentWithHeading, SpotifyListItemTrack },
components: { ContentWithHeading, SpotifyListItemTrack, SpotifyModalDialogTrack },
data () {
return {
album: {}
album: { artists: [{}], tracks: {} },
show_track_details_modal: false,
selected_track: {}
}
},
@ -55,6 +66,11 @@ export default {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.album.uri, true)
},
open_track_dialog: function (track) {
this.selected_track = track
this.show_track_details_modal = true
}
}
}

View File

@ -5,8 +5,15 @@
</template>
<template 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"></spotify-list-item-album>
<spotify-list-item-album v-for="album in albums" :key="album.id" :album="album">
<template slot="actions">
<a @click="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-album>
<infinite-loading v-if="offset < total" @infinite="load_next"><span slot="no-more">.</span></infinite-loading>
<spotify-modal-dialog-album :show="show_details_modal" :album="selected_album" @close="show_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -15,6 +22,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum'
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum'
import store from '@/store'
import SpotifyWebApi from 'spotify-web-api-js'
import InfiniteLoading from 'vue-infinite-loading'
@ -42,14 +50,17 @@ const artistData = {
export default {
name: 'SpotifyPageArtist',
mixins: [ LoadDataBeforeEnterMixin(artistData) ],
components: { ContentWithHeading, SpotifyListItemAlbum, InfiniteLoading },
components: { ContentWithHeading, SpotifyListItemAlbum, SpotifyModalDialogAlbum, InfiniteLoading },
data () {
return {
artist: {},
albums: [],
total: 0,
offset: 0
offset: 0,
show_details_modal: false,
selected_album: {}
}
},
@ -73,6 +84,11 @@ export default {
$state.complete()
}
}
},
open_dialog: function (album) {
this.selected_album = album
this.show_details_modal = true
}
}
}

View File

@ -8,7 +8,14 @@
<p class="title is-4">New Releases</p>
</template>
<template slot="content">
<spotify-list-item-album v-for="album in new_releases" :key="album.id" :album="album"></spotify-list-item-album>
<spotify-list-item-album v-for="album in new_releases" :key="album.id" :album="album">
<template 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>
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
<template slot="footer">
<nav class="level">
@ -27,7 +34,14 @@
<p class="title is-4">Featured Playlists</p>
</template>
<template slot="content">
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist"></spotify-list-item-playlist>
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
<template 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>
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
</template>
<template slot="footer">
<nav class="level">
@ -48,6 +62,8 @@ 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 store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
@ -77,7 +93,17 @@ const browseData = {
export default {
name: 'SpotifyPageBrowse',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyListItemPlaylist },
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist },
data () {
return {
show_album_details_modal: false,
selected_album: {},
show_playlist_details_modal: false,
selected_playlist: {}
}
},
computed: {
new_releases () {
@ -87,6 +113,18 @@ export default {
featured_playlists () {
return this.$store.state.spotify_featured_playlists.slice(0, 3)
}
},
methods: {
open_album_dialog: function (album) {
this.selected_album = album
this.show_album_details_modal = true
},
open_playlist_dialog: function (playlist) {
this.selected_playlist = playlist
this.show_playlist_details_modal = true
}
}
}
</script>

View File

@ -7,7 +7,14 @@
<p class="title is-4">Featured Playlists</p>
</template>
<template slot="content">
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist"></spotify-list-item-playlist>
<spotify-list-item-playlist v-for="playlist in featured_playlists" :key="playlist.id" :playlist="playlist">
<template 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>
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -18,6 +25,7 @@ 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 store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
@ -43,12 +51,26 @@ const browseData = {
export default {
name: 'SpotifyPageBrowseFeaturedPlaylists',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, SpotifyListItemPlaylist },
components: { ContentWithHeading, TabsMusic, SpotifyListItemPlaylist, SpotifyModalDialogPlaylist },
data () {
return {
show_playlist_details_modal: false,
selected_playlist: {}
}
},
computed: {
featured_playlists () {
return this.$store.state.spotify_featured_playlists
}
},
methods: {
open_playlist_dialog: function (playlist) {
this.selected_playlist = playlist
this.show_playlist_details_modal = true
}
}
}
</script>

View File

@ -7,7 +7,14 @@
<p class="title is-4">New Releases</p>
</template>
<template slot="content">
<spotify-list-item-album v-for="album in new_releases" :key="album.id" :album="album"></spotify-list-item-album>
<spotify-list-item-album v-for="album in new_releases" :key="album.id" :album="album">
<template slot="actions">
<a @click="open_album(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
</spotify-list-item-album>
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
</content-with-heading>
</div>
@ -18,6 +25,7 @@ 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 store from '@/store'
import * as types from '@/store/mutation_types'
import SpotifyWebApi from 'spotify-web-api-js'
@ -43,12 +51,26 @@ const browseData = {
export default {
name: 'SpotifyPageBrowseNewReleases',
mixins: [ LoadDataBeforeEnterMixin(browseData) ],
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum },
components: { ContentWithHeading, TabsMusic, SpotifyListItemAlbum, SpotifyModalDialogAlbum },
data () {
return {
show_album_details_modal: false,
selected_album: {}
}
},
computed: {
new_releases () {
return this.$store.state.spotify_new_releases
}
},
methods: {
open_album: function (album) {
this.selected_album = album
this.show_album_details_modal = true
}
}
}
</script>

View File

@ -10,8 +10,15 @@
</template>
<template 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"></spotify-list-item-track>
<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">
<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>
<spotify-modal-dialog-track :show="show_track_details_modal" :track="selected_track" :album="selected_track.album" @close="show_track_details_modal = false" />
</template>
</content-with-heading>
</template>
@ -20,6 +27,7 @@
import { LoadDataBeforeEnterMixin } from './mixin'
import ContentWithHeading from '@/templates/ContentWithHeading'
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack'
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack'
import store from '@/store'
import webapi from '@/webapi'
import SpotifyWebApi from 'spotify-web-api-js'
@ -47,14 +55,17 @@ const playlistData = {
export default {
name: 'SpotifyPagePlaylist',
mixins: [ LoadDataBeforeEnterMixin(playlistData) ],
components: { ContentWithHeading, SpotifyListItemTrack, InfiniteLoading },
components: { ContentWithHeading, SpotifyListItemTrack, SpotifyModalDialogTrack, InfiniteLoading },
data () {
return {
playlist: {},
playlist: { tracks: {} },
tracks: [],
total: 0,
offset: 0
offset: 0,
show_track_details_modal: false,
selected_track: {}
}
},
@ -83,6 +94,11 @@ export default {
play: function () {
this.show_details_modal = false
webapi.player_play_uri(this.playlist.uri, true)
},
open_track_dialog: function (track) {
this.selected_track = track
this.show_track_details_modal = true
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div>
<!-- Search field + recent searches -->
<section class="section fd-tabs-section">
<section class="section fd-remove-padding-bottom">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
@ -31,8 +31,15 @@
<p class="title is-4">Tracks</p>
</template>
<template 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"></spotify-list-item-track>
<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">
<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>
<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">
<nav v-if="show_all_tracks_button" class="level">
@ -50,8 +57,15 @@
<p class="title is-4">Artists</p>
</template>
<template slot="content">
<spotify-list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist"></spotify-list-item-artist>
<spotify-list-item-artist v-for="artist in artists.items" :key="artist.id" :artist="artist">
<template 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>
<spotify-modal-dialog-artist :show="show_artist_details_modal" :artist="selected_artist" @close="show_artist_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_artists_button" class="level">
@ -69,8 +83,15 @@
<p class="title is-4">Albums</p>
</template>
<template slot="content">
<spotify-list-item-album v-for="album in albums.items" :key="album.id" :album="album"></spotify-list-item-album>
<spotify-list-item-album v-for="album in albums.items" :key="album.id" :album="album">
<template 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>
<spotify-modal-dialog-album :show="show_album_details_modal" :album="selected_album" @close="show_album_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_albums_button" class="level">
@ -88,8 +109,15 @@
<p class="title is-4">Playlists</p>
</template>
<template slot="content">
<spotify-list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist"></spotify-list-item-playlist>
<spotify-list-item-playlist v-for="playlist in playlists.items" :key="playlist.id" :playlist="playlist">
<template 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>
<spotify-modal-dialog-playlist :show="show_playlist_details_modal" :playlist="selected_playlist" @close="show_playlist_details_modal = false" />
</template>
<template slot="footer">
<nav v-if="show_all_playlists_button" class="level">
@ -110,6 +138,10 @@ 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 SpotifyWebApi from 'spotify-web-api-js'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
@ -117,7 +149,7 @@ import InfiniteLoading from 'vue-infinite-loading'
export default {
name: 'SpotifyPageSearch',
components: { ContentWithHeading, TabsSearch, SpotifyListItemTrack, SpotifyListItemArtist, SpotifyListItemAlbum, SpotifyListItemPlaylist, InfiniteLoading },
components: { ContentWithHeading, TabsSearch, SpotifyListItemTrack, SpotifyListItemArtist, SpotifyListItemAlbum, SpotifyListItemPlaylist, SpotifyModalDialogTrack, SpotifyModalDialogArtist, SpotifyModalDialogAlbum, SpotifyModalDialogPlaylist, InfiniteLoading },
data () {
return {
@ -128,7 +160,19 @@ export default {
playlists: { items: [], total: 0 },
query: {},
search_param: {}
search_param: {},
show_track_details_modal: false,
selected_track: {},
show_album_details_modal: false,
selected_album: {},
show_artist_details_modal: false,
selected_artist: {},
show_playlist_details_modal: false,
selected_playlist: {}
}
},
@ -321,6 +365,26 @@ export default {
open_recent_search: function (query) {
this.search_query = query
this.new_search()
},
open_track_dialog: function (track) {
this.selected_track = track
this.show_track_details_modal = true
},
open_album_dialog: function (album) {
this.selected_album = album
this.show_album_details_modal = true
},
open_artist_dialog: function (artist) {
this.selected_artist = artist
this.show_artist_details_modal = true
},
open_playlist_dialog: function (playlist) {
this.selected_playlist = playlist
this.show_playlist_details_modal = true
}
},

View File

@ -13,7 +13,8 @@ import PageAlbums from '@/pages/PageAlbums'
import PageAlbum from '@/pages/PageAlbum'
import PageGenres from '@/pages/PageGenres'
import PageGenre from '@/pages/PageGenre'
import PageTracks from '@/pages/PageTracks'
import PageGenreTracks from '@/pages/PageGenreTracks'
import PageArtistTracks from '@/pages/PageArtistTracks'
import PagePodcasts from '@/pages/PagePodcasts'
import PagePodcast from '@/pages/PagePodcast'
import PageAudiobooks from '@/pages/PageAudiobooks'
@ -57,25 +58,25 @@ export const router = new VueRouter({
path: '/music/browse',
name: 'Browse',
component: PageBrowse,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/browse/recently_added',
name: 'Browse Recently Added',
component: PageBrowseRecentlyAdded,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/browse/recently_played',
name: 'Browse Recently Played',
component: PageBrowseRecentlyPlayed,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/artists',
name: 'Artists',
component: PageArtists,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true, has_index: true }
},
{
path: '/music/artists/:artist_id',
@ -86,14 +87,14 @@ export const router = new VueRouter({
{
path: '/music/artists/:artist_id/tracks',
name: 'Tracks',
component: PageTracks,
meta: { show_progress: true }
component: PageArtistTracks,
meta: { show_progress: true, has_index: true }
},
{
path: '/music/albums',
name: 'Albums',
component: PageAlbums,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true, has_index: true }
},
{
path: '/music/albums/:album_id',
@ -105,13 +106,19 @@ export const router = new VueRouter({
path: '/music/genres',
name: 'Genres',
component: PageGenres,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true, has_index: true }
},
{
path: '/music/genres/:genre',
name: 'Genre',
component: PageGenre,
meta: { show_progress: true }
meta: { show_progress: true, has_index: true }
},
{
path: '/music/genres/:genre/tracks',
name: 'GenreTracks',
component: PageGenreTracks,
meta: { show_progress: true, has_index: true }
},
{
path: '/podcasts',
@ -162,19 +169,19 @@ export const router = new VueRouter({
path: '/music/spotify',
name: 'Spotify',
component: SpotifyPageBrowse,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/spotify/new-releases',
name: 'Spotify Browse New Releases',
component: SpotifyPageBrowseNewReleases,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/spotify/featured-playlists',
name: 'Spotify Browse Featured Playlists',
component: SpotifyPageBrowseFeaturedPlaylists,
meta: { show_progress: true }
meta: { show_progress: true, has_tabs: true }
},
{
path: '/music/spotify/artists/:artist_id',
@ -201,11 +208,30 @@ export const router = new VueRouter({
}
],
scrollBehavior (to, from, savedPosition) {
// console.log(to.path + '_' + from.path + '__' + to.hash + ' savedPosition:' + savedPosition)
if (savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(savedPosition)
}, 500)
}, 10)
})
} else if (to.path === from.path && to.hash) {
return { selector: to.hash, offset: { x: 0, y: 90 } }
} else if (to.hash) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ selector: to.hash, offset: { x: 0, y: 90 } })
}, 10)
})
} else if (to.meta.has_index) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (to.meta.has_tabs) {
resolve({ selector: '#top', offset: { x: 0, y: 140 } })
} else {
resolve({ selector: '#top', offset: { x: 0, y: 100 } })
}
}, 10)
})
} else {
return { x: 0, y: 0 }

View File

@ -1,9 +1,10 @@
<template>
<section class="section">
<section class="section fd-content">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
<nav class="level">
<slot name="options"></slot>
<nav class="level" id="top">
<!-- Left side -->
<div class="level-left">
<div class="level-item has-text-centered-mobile">

View File

@ -41,8 +41,13 @@ export default {
return axios.put('/api/queue/items/' + itemId + '?new_position=' + newPosition)
},
queue_add (uri) {
return axios.post('/api/queue/items/add?uris=' + uri)
queue_add (uri, showNotification = true) {
return axios.post('/api/queue/items/add?uris=' + uri).then((response) => {
if (showNotification) {
store.dispatch('add_notification', { text: response.data.count + ' tracks appended to queue', type: 'info', timeout: 2000 })
}
return Promise.resolve(response)
})
},
queue_add_next (uri) {
@ -50,17 +55,20 @@ export default {
if (store.getters.now_playing && store.getters.now_playing.id) {
position = store.getters.now_playing.position + 1
}
return axios.post('/api/queue/items/add?uris=' + uri + '&position=' + position)
return axios.post('/api/queue/items/add?uris=' + uri + '&position=' + position).then((response) => {
store.dispatch('add_notification', { text: response.data.count + ' tracks appended to queue', type: 'info', timeout: 2000 })
return Promise.resolve(response)
})
},
player_status () {
return axios.get('/api/player')
},
player_play_uri (uris, shuffle, position = 0) {
player_play_uri (uris, shuffle, position = undefined) {
return this.queue_clear().then(() =>
this.player_shuffle(shuffle).then(() =>
this.queue_add(uris).then(() =>
this.queue_add(uris, false).then(() =>
this.player_play({ 'position': position })
)
)
@ -163,6 +171,17 @@ export default {
})
},
library_genre_tracks (genre) {
var genreParams = {
'type': 'tracks',
'media_kind': 'music',
'expression': 'genre is "' + genre + '"'
}
return axios.get('/api/search', {
params: genreParams
})
},
library_artist_tracks (artist) {
if (artist) {
var artistParams = {