mirror of
https://github.com/koel/koel
synced 2024-11-24 21:23:06 +00:00
Allow Navigate from "Most Played Songs" (fixes #303)
This commit is contained in:
parent
07c1ae0f59
commit
d25e899593
6 changed files with 206 additions and 190 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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).
|
||||||
*/
|
*/
|
||||||
|
|
161
resources/assets/js/components/shared/home-song-item.vue
Normal file
161
resources/assets/js/components/shared/home-song-item.vue
Normal 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>
|
|
@ -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);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
21
resources/assets/js/mixins/artist-album-details.js
Normal file
21
resources/assets/js/mixins/artist-album-details.js
Normal 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);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in a new issue