mirror of
https://github.com/koel/koel
synced 2024-11-24 21:23:06 +00:00
Add ability to view album info individually
This commit is contained in:
parent
27b44ec79c
commit
db340438ad
9 changed files with 299 additions and 120 deletions
20
app/Http/Controllers/API/AlbumController.php
Normal file
20
app/Http/Controllers/API/AlbumController.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Models\Album;
|
||||
|
||||
class AlbumController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get extra information about an album via Last.fm.
|
||||
*
|
||||
* @param Album $album
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getInfo(Album $album)
|
||||
{
|
||||
return response()->json($album->getInfo());
|
||||
}
|
||||
}
|
|
@ -62,6 +62,8 @@ Route::group(['prefix' => 'api', 'namespace' => 'API'], function () {
|
|||
});
|
||||
|
||||
// Info routes
|
||||
//if (Lastfm::used())
|
||||
if (Lastfm::used()) {
|
||||
Route::get('album/{album}/info', 'AlbumController@getInfo');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<article v-if="album.id" id="albumInfo">
|
||||
<h1>
|
||||
<article v-if="album.id" id="albumInfo" :class="mode">
|
||||
<h1 class="name">
|
||||
<span>{{ album.name }}</span>
|
||||
|
||||
<a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a>
|
||||
|
@ -12,10 +12,15 @@
|
|||
class="cover">
|
||||
|
||||
<div class="wiki" v-if="album.info.wiki && album.info.wiki.summary">
|
||||
<div class="summary" v-show="!showingFullWiki">{{{ album.info.wiki.summary }}}</div>
|
||||
<div class="full" v-show="showingFullWiki">{{{ album.info.wiki.full }}}</div>
|
||||
<div class="summary" v-show="mode !== 'full' && !showingFullWiki">
|
||||
{{{ album.info.wiki.summary }}}
|
||||
</div>
|
||||
<div class="full" v-show="mode === 'full' || showingFullWiki">
|
||||
{{{ album.info.wiki.full }}}
|
||||
</div>
|
||||
|
||||
<button class="more" v-show="!showingFullWiki" @click.prevent="showingFullWiki = !showingFullWiki">
|
||||
<button class="more" v-show="mode !== 'full' && !showingFullWiki"
|
||||
@click.prevent="showingFullWiki = !showingFullWiki">
|
||||
Full Wiki
|
||||
</button>
|
||||
</div>
|
||||
|
@ -34,7 +39,7 @@
|
|||
<footer>Data © <a target="_blank" href="{{{ album.info.url }}}">Last.fm</a></footer>
|
||||
</div>
|
||||
|
||||
<p class="none" v-else>No album information found. At all.</p>
|
||||
<p class="none" v-else>No album information found.</p>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
|
@ -43,7 +48,7 @@
|
|||
|
||||
export default {
|
||||
replace: false,
|
||||
props: ['album'],
|
||||
props: ['album', 'mode'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
@ -74,48 +79,6 @@
|
|||
@import "../../../../sass/partials/_mixins.scss";
|
||||
|
||||
#albumInfo {
|
||||
img.cover {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wiki {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.track-listing {
|
||||
margin-top: 16px;
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
&:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.no {
|
||||
flex: 0 0 24px;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.length {
|
||||
flex: 0 0 48px;
|
||||
text-align: right;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include artist-album-info();
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<article v-if="artist.id" id="artistInfo">
|
||||
<h1>
|
||||
<h1 class="name">
|
||||
<span>{{ artist.name }}</span>
|
||||
|
||||
<a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a>
|
||||
|
@ -64,13 +64,6 @@
|
|||
@import "../../../../sass/partials/_mixins.scss";
|
||||
|
||||
#artistInfo {
|
||||
img.cool-guys-posing {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.bio {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@include artist-album-info();
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
|
||||
<div class="panes">
|
||||
<lyrics :song="song" v-ref:lyrics v-show="currentView === 'lyrics'"></lyrics>
|
||||
<artist-info :artist="song.artist" v-ref:artist-info v-show="currentView === 'artistInfo'"></artist-info>
|
||||
<album-info :album="song.album" v-ref:album-info v-show="currentView === 'albumInfo'"></album-info>
|
||||
<artist-info :artist="song.artist" :mode="'sidebar'" v-ref:artist-info v-show="currentView === 'artistInfo'">
|
||||
</artist-info>
|
||||
<album-info :album="song.album" :mode="'sidebar'" v-ref:album-info v-show="currentView === 'albumInfo'">
|
||||
</album-info>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -130,50 +132,8 @@
|
|||
font-size: 2.2rem;
|
||||
margin-bottom: 16px;
|
||||
line-height: 2.8rem;
|
||||
|
||||
@include vertical-center();
|
||||
align-items: initial;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
color: $colorHighlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
margin-top: 8px;
|
||||
border-radius: .23rem;
|
||||
background: $colorBlue;
|
||||
color: #fff;
|
||||
padding: .3rem .6rem;
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 24px;
|
||||
font-size: .9rem;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
font-weight: $fontWeight_Normal;
|
||||
|
||||
&:hover {
|
||||
color: #b90000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width : 1024px) {
|
||||
position: fixed;
|
||||
height: calc(100vh - #{$headerHeight + $footerHeight});
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
•
|
||||
{{ meta.totalLength }}
|
||||
|
||||
<template v-if="sharedState.useLastfm">
|
||||
•
|
||||
<a href="#" @click.prevent="showInfo" title="View album's extra information">Info</a>
|
||||
</template>
|
||||
<template v-if="sharedState.allowDownload">
|
||||
•
|
||||
<a href="#" @click.prevent="download" title="Download all songs in album">Download</a>
|
||||
|
@ -43,6 +47,16 @@
|
|||
</h1>
|
||||
|
||||
<song-list :items="album.songs" :selected-songs.sync="selectedSongs" type="album"></song-list>
|
||||
|
||||
<section class="info-wrapper" v-if="sharedState.useLastfm && info.showing">
|
||||
<a href="#" class="close" @click="info.showing = false"><i class="fa fa-times"></i></a>
|
||||
<div class="inner">
|
||||
<div class="loading" v-show="info.loading">
|
||||
<sound-bar></sound-bar>
|
||||
</div>
|
||||
<album-info :album="album" :mode="'full'" v-else></album-info>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
@ -56,9 +70,12 @@
|
|||
import download from '../../../services/download';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import artistAlbumDetails from '../../../mixins/artist-album-details';
|
||||
import albumInfo from '../extra/album-info.vue';
|
||||
import soundBar from '../../shared/sound-bar.vue';
|
||||
|
||||
export default {
|
||||
mixins: [hasSongList, artistAlbumDetails],
|
||||
components: { albumInfo, soundBar },
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
@ -66,6 +83,10 @@
|
|||
album: albumStore.stub,
|
||||
isPhone: isMobile.phone,
|
||||
showingControls: false,
|
||||
info: {
|
||||
showing: false,
|
||||
loading: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -100,6 +121,7 @@
|
|||
*/
|
||||
'main-content-view:load': function (view, album) {
|
||||
if (view === 'album') {
|
||||
this.info.showing = false;
|
||||
this.album = album;
|
||||
}
|
||||
},
|
||||
|
@ -119,6 +141,18 @@
|
|||
download() {
|
||||
download.fromAlbum(this.album);
|
||||
},
|
||||
|
||||
showInfo() {
|
||||
this.info.showing = true;
|
||||
if (!this.album.info) {
|
||||
this.info.loading = true;
|
||||
albumStore.fetchInfo(this.album, () => {
|
||||
this.info.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.info.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -134,6 +168,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
@include vertical-center();
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.heading {
|
||||
.overview {
|
||||
position: relative;
|
||||
|
@ -163,5 +202,49 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
color: $color2ndText;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: $colorMainBgr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: $colorRed;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
|
||||
html.touchevents & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .close {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.inner {
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding-bottom: 48px;
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from 'lodash';
|
||||
|
||||
import { secondsToHis } from '../services/utils';
|
||||
import http from '../services/http';
|
||||
import stub from '../stubs/album';
|
||||
import songStore from './song';
|
||||
import artistStore from './artist';
|
||||
|
@ -162,6 +163,51 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get extra album info (from Last.fm).
|
||||
*
|
||||
* @param {Object} album
|
||||
* @param {?Function} cb
|
||||
*/
|
||||
fetchInfo(album, cb = null) {
|
||||
if (album.info) {
|
||||
cb && cb();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
http.get(`album/${album.id}/info`, response => {
|
||||
if (response.data) {
|
||||
this.mergeAlbumInfo(album, response.data);
|
||||
}
|
||||
|
||||
cb && cb();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge the (fetched) info into an album.
|
||||
*
|
||||
* @param {Object} album
|
||||
* @param {Object} info
|
||||
*/
|
||||
mergeAlbumInfo(album, info) {
|
||||
// Convert the duration into i:s
|
||||
info.tracks && each(info.tracks, track => track.fmtLength = secondsToHis(track.length));
|
||||
|
||||
// If the album cover is not in a nice form, discard.
|
||||
if (typeof info.image !== 'string') {
|
||||
info.image = null;
|
||||
}
|
||||
|
||||
// Set the album cover on the client side to the retrieved image from server.
|
||||
if (info.cover) {
|
||||
album.cover = info.cover;
|
||||
}
|
||||
|
||||
album.info = info;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get top n most-played albums.
|
||||
*
|
||||
|
|
|
@ -215,22 +215,7 @@ export default {
|
|||
song.artist.image = data.artist_info.image;
|
||||
}
|
||||
|
||||
// Convert the duration into i:s
|
||||
if (data.album_info && data.album_info.tracks) {
|
||||
each(data.album_info.tracks, track => track.fmtLength = secondsToHis(track.length));
|
||||
}
|
||||
|
||||
// If the album cover is not in a nice form, don't use it.
|
||||
if (data.album_info && typeof data.album_info.image !== 'string') {
|
||||
data.album_info.image = null;
|
||||
}
|
||||
|
||||
song.album.info = data.album_info;
|
||||
|
||||
// Set the album on the client side to the retrieved image from server.
|
||||
if (data.album_info.cover) {
|
||||
song.album.cover = data.album_info.cover;
|
||||
}
|
||||
data.album_info && albumStore.mergeAlbumInfo(song.album, data.album_info);
|
||||
|
||||
song.infoRetrieved = true;
|
||||
|
||||
|
|
|
@ -183,6 +183,133 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin artist-album-info() {
|
||||
h1 {
|
||||
font-weight: $fontWeight_UltraThin;
|
||||
line-height: 2.8rem;
|
||||
|
||||
&.name {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
@include vertical-center();
|
||||
align-items: initial;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
color: $colorHighlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bio {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.more {
|
||||
margin-top: 8px;
|
||||
border-radius: .23rem;
|
||||
background: $colorBlue;
|
||||
color: #fff;
|
||||
padding: .3rem .6rem;
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
img.cover, img.cool-guys-posing {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wiki {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.track-listing {
|
||||
margin-top: 16px;
|
||||
|
||||
ul {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
&:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.no {
|
||||
flex: 0 0 24px;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.length {
|
||||
flex: 0 0 48px;
|
||||
text-align: right;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 24px;
|
||||
font-size: .9rem;
|
||||
text-align: right;
|
||||
clear: both;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
font-weight: $fontWeight_Normal;
|
||||
|
||||
&:hover {
|
||||
color: #b90000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.none {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.full {
|
||||
img.cover, img.cool-guys-posing {
|
||||
width: 300px;
|
||||
max-width: 100%;
|
||||
float: left;
|
||||
margin: 0 16px 16px 0;
|
||||
}
|
||||
|
||||
h1.name {
|
||||
font-size: 2.4rem;
|
||||
|
||||
a.shuffle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin inset-when-pressed() {
|
||||
&:active {
|
||||
box-shadow: inset 0px 10px 10px -10px rgba(0,0,0,1);
|
||||
|
|
Loading…
Reference in a new issue