Allow Navigate from "Most Played Songs" (fixes #303)

This commit is contained in:
An Phan 2016-05-30 16:04:22 +08:00
parent 07c1ae0f59
commit d25e899593
6 changed files with 206 additions and 190 deletions

View file

@ -11,23 +11,9 @@
<ol class="top-song-list"> <ol class="top-song-list">
<li v-for="song in topSongs" <li v-for="song in topSongs"
:class="{ playing: song.playbackState === 'playing' || song.playbackState === 'paused' }" :top-play-count="topSongs[0].playCount"
@dblclick.prevent="play(song)" :song="song"
> is="song-item"></li>
<span class="cover" :style="{ backgroundImage: 'url(' + song.album.cover + ')' }">
<a class="control" @click.prevent="triggerPlay(song)">
<i class="fa fa-play" v-show="song.playbackState !== 'playing'"></i>
<i class="fa fa-pause" v-else></i>
</a>
</span>
<span class="details">
<span :style="{ width: song.playCount * 100 / topSongs[0].playCount + '%' }"
class="play-count"></span>
{{ song.title }}
<span class="by">{{ song.artist.name }}
{{ song.playCount }} {{ song.playCount | pluralize 'play' }}</span>
</span>
</li>
</ol> </ol>
</section> </section>
@ -36,20 +22,9 @@
<ol class="recent-song-list" v-show="recentSongs.length"> <ol class="recent-song-list" v-show="recentSongs.length">
<li v-for="song in recentSongs" <li v-for="song in recentSongs"
:class="{ playing: song.playbackState === 'playing' || song.playbackState === 'paused' }" :top-play-count="topSongs[0].playCount"
@dblclick.prevent="play(song)" :song="song"
> is="song-item"></li>
<span class="cover" :style="{ backgroundImage: 'url(' + song.album.cover + ')' }">
<a class="control" @click.prevent="triggerPlay(song)">
<i class="fa fa-play" v-show="song.playbackState !== 'playing'"></i>
<i class="fa fa-pause" v-else></i>
</a>
</span>
<span class="details">
{{ song.title }}
<span class="by">{{ song.artist.name }}</span>
</span>
</li>
</ol> </ol>
<p class="none" v-show="!recentSongs.length"> <p class="none" v-show="!recentSongs.length">
@ -85,23 +60,22 @@
<script> <script>
import { sample } from 'lodash'; import { sample } from 'lodash';
import playback from '../../../services/playback';
import songStore from '../../../stores/song'; import songStore from '../../../stores/song';
import albumStore from '../../../stores/album'; import albumStore from '../../../stores/album';
import artistStore from '../../../stores/artist'; import artistStore from '../../../stores/artist';
import userStore from '../../../stores/user'; import userStore from '../../../stores/user';
import queueStore from '../../../stores/queue';
import preferenceStore from '../../../stores/preference'; import preferenceStore from '../../../stores/preference';
import infiniteScroll from '../../../mixins/infinite-scroll'; import infiniteScroll from '../../../mixins/infinite-scroll';
import albumItem from '../../shared/album-item.vue'; import albumItem from '../../shared/album-item.vue';
import artistItem from '../../shared/artist-item.vue'; import artistItem from '../../shared/artist-item.vue';
import songItem from '../../shared/home-song-item.vue';
export default { export default {
components: { albumItem, artistItem }, components: { albumItem, artistItem, songItem },
/** /**
* We're not really using infinite scrolling here, but only the handy "Back to Top" button. * Note: We're not really using infinite scrolling here,
* @type {Array} * but only the handy "Back to Top" button.
*/ */
mixins: [infiniteScroll], mixins: [infiniteScroll],
@ -134,27 +108,6 @@
}, },
methods: { methods: {
play(song) {
if (!queueStore.contains(song)) {
queueStore.queueAfterCurrent(song);
}
playback.play(song);
},
/**
* Trigger playing a song.
*/
triggerPlay(song) {
if (song.playbackState === 'stopped') {
this.play(song);
} else if (song.playbackState === 'paused') {
playback.resume();
} else {
playback.pause();
}
},
/** /**
* Refresh the dashboard with latest data. * Refresh the dashboard with latest data.
*/ */
@ -194,101 +147,6 @@
margin-right: 8px; margin-right: 8px;
} }
} }
ol li {
display: flex;
&.playing {
color: $colorHighlight;
}
&:hover .cover {
.control {
display: block;
}
&::before {
opacity: .7;
}
}
.cover {
flex: 0 0 48px;
height: 48px;
background-size: cover;
position: relative;
@include vertical-center();
&::before {
content: " ";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: #000;
opacity: 0;
html.touchevents & {
opacity: .7;
}
}
.control {
border-radius: 50%;
width: 28px;
height: 28px;
background: rgba(0, 0, 0, .7);
border: 1px solid transparent;
line-height: 26px;
font-size: 13px;
text-align: center;
z-index: 1;
display: none;
color: #fff;
transition: .3s;
&:hover {
transform: scale(1.2);
border-color: #fff;
}
html.touchevents & {
display: block;
}
}
}
.details {
flex: 1;
padding: 4px 8px;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
.play-count {
background: rgba(255, 255, 255, 0.08);
position: absolute;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
}
.by {
display: block;
font-size: 90%;
opacity: .6;
margin-top: 2px;
}
}
//border-bottom: 1px solid $color2ndBgr;
margin-bottom: 8px;
}
} }
.none { .none {

View file

@ -6,9 +6,12 @@
</a> </a>
</span> </span>
<footer> <footer>
<a class="name" @click.prevent="viewDetails">{{ album.name }}</a> <a class="name" @click.prevent="viewAlbumDetails(album)">{{ album.name }}</a>
<span class="sep">by</span> <span class="sep">by</span>
<a class="artist" v-if="isNormalArtist" @click.prevent="viewArtistDetails">{{ album.artist.name }}</a> <a class="artist" v-if="isNormalArtist"
@click.prevent="viewArtistDetails(album.artist)">
{{ album.artist.name }}
</a>
<span class="artist nope" v-else>{{ album.artist.name }}</span> <span class="artist nope" v-else>{{ album.artist.name }}</span>
<p class="meta"> <p class="meta">
{{ album.songs.length }} {{ album.songs.length | pluralize 'song' }} {{ album.songs.length }} {{ album.songs.length | pluralize 'song' }}
@ -28,9 +31,11 @@
import playback from '../../services/playback'; import playback from '../../services/playback';
import queueStore from '../../stores/queue'; import queueStore from '../../stores/queue';
import artistStore from '../../stores/artist'; import artistStore from '../../stores/artist';
import artistAlbumDetails from '../../mixins/artist-album-details';
export default { export default {
props: ['album'], props: ['album'],
mixins: [artistAlbumDetails],
computed: { computed: {
isNormalArtist() { isNormalArtist() {
@ -51,20 +56,6 @@
} }
}, },
/**
* Load the album details screen.
*/
viewDetails() {
this.$root.loadAlbum(this.album);
},
/**
* Load the artist details screen.
*/
viewArtistDetails() {
this.$root.loadArtist(this.album.artist);
},
/** /**
* Allow dragging the album (actually, its songs). * Allow dragging the album (actually, its songs).
*/ */

View file

@ -6,7 +6,7 @@
</a> </a>
</span> </span>
<footer> <footer>
<a class="name" @click.prevent="viewDetails">{{ artist.name }}</a> <a class="name" @click.prevent="viewArtistDetails(artist)">{{ artist.name }}</a>
<p class="meta"> <p class="meta">
{{ artist.albums.length }} {{ artist.albums.length | pluralize 'album' }} {{ artist.albums.length }} {{ artist.albums.length | pluralize 'album' }}
@ -25,9 +25,11 @@
import playback from '../../services/playback'; import playback from '../../services/playback';
import artistStore from '../../stores/artist'; import artistStore from '../../stores/artist';
import queueStore from '../../stores/queue'; import queueStore from '../../stores/queue';
import artistAlbumDetails from '../../mixins/artist-album-details';
export default { export default {
props: ['artist'], props: ['artist'],
mixins: [artistAlbumDetails],
computed: { computed: {
/** /**
@ -53,10 +55,6 @@
} }
}, },
viewDetails() {
this.$root.loadArtist(this.artist);
},
/** /**
* Allow dragging the artist (actually, their songs). * Allow dragging the artist (actually, their songs).
*/ */

View file

@ -0,0 +1,161 @@
<template>
<li class="song-item-home"
:class="{ playing: song.playbackState === 'playing' || song.playbackState === 'paused' }"
@dblclick.prevent="play"
>
<span class="cover" :style="{ backgroundImage: 'url(' + song.album.cover + ')' }">
<a class="control" @click.prevent="changeSongState">
<i class="fa fa-play" v-show="song.playbackState !== 'playing'"></i>
<i class="fa fa-pause" v-else></i>
</a>
</span>
<span class="details">
<span :style="{ width: song.playCount * 100 / topPlayCount + '%' }"
class="play-count"></span>
{{ song.title }}
<span class="by">
<a href="#" @click.prevent="viewArtistDetails(song.artist)">{{ song.artist.name }}</a> -
{{ song.playCount }} {{ song.playCount | pluralize 'play' }}
</span>
</span>
</li>
</template>
<script>
import playback from '../../services/playback';
import queueStore from '../../stores/queue';
import artistAlbumDetails from '../../mixins/artist-album-details';
export default {
props: ['song', 'topPlayCount'],
mixins: [artistAlbumDetails],
methods: {
play() {
if (!queueStore.contains(this.song)) {
queueStore.queueAfterCurrent(this.song);
}
playback.play(this.song);
},
changeSongState() {
if (this.song.playbackState === 'stopped') {
this.play(this.song);
} else if (this.song.playbackState === 'paused') {
this.playback.resume();
} else {
this.playback.pause();
}
},
},
};
</script>
<style lang="sass" scoped>
@import "../../../sass/partials/_vars.scss";
@import "../../../sass/partials/_mixins.scss";
.song-item-home {
display: flex;
&.playing {
color: $colorHighlight;
}
&:hover .cover {
.control {
display: block;
}
&::before {
opacity: .7;
}
}
.cover {
flex: 0 0 48px;
height: 48px;
background-size: cover;
position: relative;
@include vertical-center();
&::before {
content: " ";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: #000;
opacity: 0;
html.touchevents & {
opacity: .7;
}
}
.control {
border-radius: 50%;
width: 28px;
height: 28px;
background: rgba(0, 0, 0, .7);
border: 1px solid transparent;
line-height: 26px;
font-size: 13px;
text-align: center;
z-index: 1;
display: none;
color: #fff;
transition: .3s;
&:hover {
transform: scale(1.2);
border-color: #fff;
}
html.touchevents & {
display: block;
}
}
}
.details {
flex: 1;
padding: 4px 8px;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
.play-count {
background: rgba(255, 255, 255, 0.08);
position: absolute;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
}
.by {
display: block;
font-size: 90%;
margin-top: 2px;
color: $color2ndText;
opacity: .8;
a {
color: #fff;
&:hover {
color: $colorHighlight;
}
}
}
}
margin-bottom: 8px;
}
</style>

View file

@ -7,10 +7,10 @@
<span v-if="songs[0].playbackState !== 'playing'">Play</span> <span v-if="songs[0].playbackState !== 'playing'">Play</span>
<span v-else>Pause</span> <span v-else>Pause</span>
</li> </li>
<li v-if="onlyOneSongSelected" @click="goToAlbum"> <li v-if="onlyOneSongSelected" @click="viewAlbumDetails(songs[0].album)">
<span>Go to Album</span> <span>Go to Album</span>
</li> </li>
<li v-if="onlyOneSongSelected" @click="goToArtist"> <li v-if="onlyOneSongSelected" @click="viewArtistDetails(songs[0].artist)">
<span>Go to Artist</span> <span>Go to Artist</span>
</li> </li>
<li class="has-sub">Add To <li class="has-sub">Add To
@ -34,6 +34,7 @@
import $ from 'jquery'; import $ from 'jquery';
import songMenuMethods from '../../mixins/song-menu-methods'; import songMenuMethods from '../../mixins/song-menu-methods';
import artistAlbumDetails from '../../mixins/artist-album-details';
import queueStore from '../../stores/queue'; import queueStore from '../../stores/queue';
import userStore from '../../stores/user'; import userStore from '../../stores/user';
@ -42,7 +43,7 @@
export default { export default {
props: ['songs'], props: ['songs'],
mixins: [songMenuMethods], mixins: [songMenuMethods, artistAlbumDetails],
data() { data() {
return { return {
@ -121,20 +122,6 @@
this.close(); this.close();
}, },
/**
* Navigate to the song's album view
*/
goToAlbum() {
this.$root.loadAlbum(this.songs[0].album);
},
/**
* Navigate to the song's artist view
*/
goToArtist() {
this.$root.loadArtist(this.songs[0].artist);
},
}, },
/** /**

View file

@ -0,0 +1,21 @@
/**
* Add necessary functionalities into a view that triggers artist or album details views.
*/
export default {
methods: {
/**
* Load the album details screen.
*/
viewAlbumDetails(album) {
this.$root.loadAlbum(album);
},
/**
* Load the artist details screen.
*/
viewArtistDetails(artist) {
this.$root.loadArtist(artist);
},
},
};