Revamp dashboard, fix #231

This commit is contained in:
An Phan 2016-02-14 15:18:40 +08:00
parent 1c84cb93ec
commit 5066a14858
4 changed files with 204 additions and 81 deletions

View file

@ -5,22 +5,59 @@
</h1> </h1>
<div class="main-scroll-wrap"> <div class="main-scroll-wrap">
<section class="recent"> <div class="top-sections">
<h1>Recently Played</h1> <section v-if="topSongs.length">
<h1>Most Played Songs</h1>
<song-list <ol class="top-song-list">
v-show="songState.recent.length" <li v-for="song in topSongs"
:items="songState.recent" :class="{ playing: song.playbackState === 'playing' || song.playbackState === 'paused' }"
:sortable="false"> @dblclick.prevent="play(song)"
</song-list> >
<p class="none" v-show="!songState.recent.length">No songs played yet. What ya waiting for?</p> <span class="cover" :style="{ backgroundImage: 'url(' + song.album.cover + ')' }">
</section> <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.album.artist.name }}
{{ song.playCount }} {{ song.playCount | pluralize 'play' }}</span>
</span>
</li>
</ol>
</section>
<section v-if="topSongs.length"> <section class="recent">
<h1>Most Played Songs</h1> <h1>Recently Played</h1>
<song-list :items="topSongs" :sortable="false" type="top-songs"></song-list> <ol class="recent-song-list" v-show="recentSongs.length">
</section> <li v-for="song in recentSongs"
:class="{ playing: song.playbackState === 'playing' || song.playbackState === 'paused' }"
@dblclick.prevent="play(song)"
>
<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.album.artist.name }}</span>
</span>
</li>
</ol>
<p class="none" v-show="!recentSongs.length">
Your most-recent songs in this session will be displayed here.<br />
Start listening!
</p>
</section>
</div>
<section class="top-artists" v-if="topArtists.length"> <section class="top-artists" v-if="topArtists.length">
<h1>Top Artists</h1> <h1>Top Artists</h1>
@ -45,27 +82,22 @@
<script> <script>
import _ from 'lodash'; import _ from 'lodash';
import isMobile from 'ismobilejs';
import playback from '../../../services/playback'; 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 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 hasSongList from '../../../mixins/has-song-list';
export default { export default {
mixins: [hasSongList],
components: { albumItem, artistItem }, components: { albumItem, artistItem },
data () { data () {
return { return {
isPhone: isMobile.phone,
songState: songStore.state,
greetings: [ greetings: [
'Oh hai!', 'Oh hai!',
'Hey, %s!', 'Hey, %s!',
@ -85,13 +117,22 @@
return _.sample(this.greetings).replace('%s', userStore.current().name); return _.sample(this.greetings).replace('%s', userStore.current().name);
}, },
/**
* The most recently played songs.
*
* @return {Array.<Object>}
*/
recentSongs() {
return songStore.getRecent(7);
},
/** /**
* The most played songs. * The most played songs.
* *
* @return {Array.<Object>} * @return {Array.<Object>}
*/ */
topSongs() { topSongs() {
return songStore.getMostPlayed(); return songStore.getMostPlayed(7);
}, },
/** /**
@ -111,6 +152,29 @@
return artistStore.getMostPlayed(); return artistStore.getMostPlayed();
}, },
}, },
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();
}
},
},
}; };
</script> </script>
@ -119,14 +183,112 @@
@import "resources/assets/sass/partials/_mixins.scss"; @import "resources/assets/sass/partials/_mixins.scss";
#homeWrapper { #homeWrapper {
button.play-shuffle, button.del { .top-sections {
i { display: flex;
margin-right: 0 !important;
}
}
.song-list-wrap { > section {
padding: 0 !important; flex-grow: 1;
flex-basis: 0;
&:first-of-type {
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: 28px;
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 {
@ -153,5 +315,17 @@
font-weight: $fontWeight_UltraThin; font-weight: $fontWeight_UltraThin;
} }
} }
@media only screen and (max-device-width: 768px) {
.top-sections {
display: block;
> section {
&:first-of-type {
margin-right: 0;
}
}
}
}
} }
</style> </style>

View file

@ -4,14 +4,7 @@
class="song-item" class="song-item"
:class="{ selected: selected, playing: song.playbackState === 'playing' || song.playbackState === 'paused' }" :class="{ selected: selected, playing: song.playbackState === 'playing' || song.playbackState === 'paused' }"
> >
<td class="title"> <td class="title">{{ song.title }}</td>
<span class="play-count" v-if="showPlayCount"
title="{{ song.playCount }} {{ song.playCount | pluralize 'play' }}"
:style="{ width: song.playCount * 100 / topPlayCount + '%' }"
>{{ song.title }}
</span>
<span v-else>{{ song.title }}</span>
</td>
<td class="artist">{{ song.album.artist.name }}</td> <td class="artist">{{ song.album.artist.name }}</td>
<td class="album">{{ song.album.name }}</td> <td class="album">{{ song.album.name }}</td>
<td class="time">{{ song.fmtLength }}</td> <td class="time">{{ song.fmtLength }}</td>
@ -27,23 +20,7 @@
import queueStore from '../../stores/queue'; import queueStore from '../../stores/queue';
export default { export default {
props: [ props: ['song'],
'song',
/**
* Whether or not we should display the play count indicators.
*
* @type {boolean}
*/
'showPlayCount',
/**
* The play count of the most-played song, so that we can have some percentage-base comparison.
*
* @type {integer}
*/
'topPlayCount'
],
data() { data() {
return { return {
@ -121,17 +98,6 @@
.title { .title {
min-width: 192px; min-width: 192px;
padding: 0;
span {
display: inline-block;
padding: 8px;
&.play-count {
background: rgba(255, 255, 255, 0.08);
white-space: nowrap;
}
}
} }
.play { .play {
@ -150,20 +116,5 @@
&.playing { &.playing {
color: $colorHighlight; color: $colorHighlight;
} }
@media only screen and (max-device-width : 768px) {
.title {
padding: 0;
span {
display: inline;
padding: 0;
&.play-count {
background: none;
}
}
}
}
} }
</style> </style>

View file

@ -514,8 +514,7 @@
} }
@media only screen @media only screen and (max-device-width: 768px) {
and (max-device-width : 768px) {
table, tbody, tr { table, tbody, tr {
display: block; display: block;
} }

View file

@ -244,7 +244,6 @@ export default {
* @return {Array.<Object>} * @return {Array.<Object>}
*/ */
getRecent(n = 10) { getRecent(n = 10) {
// And last, make sure the list doesn't exceed 10 items.
return _.take(this.state.recent, n); return _.take(this.state.recent, n);
}, },