Add ability to view album info individually

This commit is contained in:
An Phan 2016-06-05 18:46:40 +08:00
parent 27b44ec79c
commit db340438ad
9 changed files with 299 additions and 120 deletions

View 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());
}
}

View file

@ -62,6 +62,8 @@ Route::group(['prefix' => 'api', 'namespace' => 'API'], function () {
}); });
// Info routes // Info routes
//if (Lastfm::used()) if (Lastfm::used()) {
Route::get('album/{album}/info', 'AlbumController@getInfo');
}
}); });
}); });

View file

@ -1,6 +1,6 @@
<template> <template>
<article v-if="album.id" id="albumInfo"> <article v-if="album.id" id="albumInfo" :class="mode">
<h1> <h1 class="name">
<span>{{ album.name }}</span> <span>{{ album.name }}</span>
<a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a> <a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a>
@ -12,10 +12,15 @@
class="cover"> class="cover">
<div class="wiki" v-if="album.info.wiki && album.info.wiki.summary"> <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="summary" v-show="mode !== 'full' && !showingFullWiki">
<div class="full" v-show="showingFullWiki">{{{ album.info.wiki.full }}}</div> {{{ 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 Full Wiki
</button> </button>
</div> </div>
@ -34,7 +39,7 @@
<footer>Data &copy; <a target="_blank" href="{{{ album.info.url }}}">Last.fm</a></footer> <footer>Data &copy; <a target="_blank" href="{{{ album.info.url }}}">Last.fm</a></footer>
</div> </div>
<p class="none" v-else>No album information found. At all.</p> <p class="none" v-else>No album information found.</p>
</article> </article>
</template> </template>
@ -43,7 +48,7 @@
export default { export default {
replace: false, replace: false,
props: ['album'], props: ['album', 'mode'],
data() { data() {
return { return {
@ -74,48 +79,6 @@
@import "../../../../sass/partials/_mixins.scss"; @import "../../../../sass/partials/_mixins.scss";
#albumInfo { #albumInfo {
img.cover { @include artist-album-info();
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;
}
}
}
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<article v-if="artist.id" id="artistInfo"> <article v-if="artist.id" id="artistInfo">
<h1> <h1 class="name">
<span>{{ artist.name }}</span> <span>{{ artist.name }}</span>
<a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a> <a class="shuffle" @click.prevent="shuffleAll"><i class="fa fa-random"></i></a>
@ -64,13 +64,6 @@
@import "../../../../sass/partials/_mixins.scss"; @import "../../../../sass/partials/_mixins.scss";
#artistInfo { #artistInfo {
img.cool-guys-posing { @include artist-album-info();
width: 100%;
height: auto;
}
.bio {
margin-top: 16px;
}
} }
</style> </style>

View file

@ -12,8 +12,10 @@
<div class="panes"> <div class="panes">
<lyrics :song="song" v-ref:lyrics v-show="currentView === 'lyrics'"></lyrics> <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> <artist-info :artist="song.artist" :mode="'sidebar'" v-ref:artist-info v-show="currentView === 'artistInfo'">
<album-info :album="song.album" v-ref:album-info v-show="currentView === 'albumInfo'"></album-info> </artist-info>
<album-info :album="song.album" :mode="'sidebar'" v-ref:album-info v-show="currentView === 'albumInfo'">
</album-info>
</div> </div>
</div> </div>
</section> </section>
@ -130,50 +132,8 @@
font-size: 2.2rem; font-size: 2.2rem;
margin-bottom: 16px; margin-bottom: 16px;
line-height: 2.8rem; 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) { @media only screen and (max-width : 1024px) {
position: fixed; position: fixed;
height: calc(100vh - #{$headerHeight + $footerHeight}); height: calc(100vh - #{$headerHeight + $footerHeight});

View file

@ -20,6 +20,10 @@
{{ meta.totalLength }} {{ 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"> <template v-if="sharedState.allowDownload">
<a href="#" @click.prevent="download" title="Download all songs in album">Download</a> <a href="#" @click.prevent="download" title="Download all songs in album">Download</a>
@ -43,6 +47,16 @@
</h1> </h1>
<song-list :items="album.songs" :selected-songs.sync="selectedSongs" type="album"></song-list> <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> </section>
</template> </template>
@ -56,9 +70,12 @@
import download from '../../../services/download'; import download from '../../../services/download';
import hasSongList from '../../../mixins/has-song-list'; import hasSongList from '../../../mixins/has-song-list';
import artistAlbumDetails from '../../../mixins/artist-album-details'; import artistAlbumDetails from '../../../mixins/artist-album-details';
import albumInfo from '../extra/album-info.vue';
import soundBar from '../../shared/sound-bar.vue';
export default { export default {
mixins: [hasSongList, artistAlbumDetails], mixins: [hasSongList, artistAlbumDetails],
components: { albumInfo, soundBar },
data() { data() {
return { return {
@ -66,6 +83,10 @@
album: albumStore.stub, album: albumStore.stub,
isPhone: isMobile.phone, isPhone: isMobile.phone,
showingControls: false, showingControls: false,
info: {
showing: false,
loading: true,
},
}; };
}, },
@ -100,6 +121,7 @@
*/ */
'main-content-view:load': function (view, album) { 'main-content-view:load': function (view, album) {
if (view === 'album') { if (view === 'album') {
this.info.showing = false;
this.album = album; this.album = album;
} }
}, },
@ -119,6 +141,18 @@
download() { download() {
download.fromAlbum(this.album); 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> </script>
@ -134,6 +168,11 @@
} }
} }
.loading {
@include vertical-center();
height: 100%;
}
.heading { .heading {
.overview { .overview {
position: relative; 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> </style>

View file

@ -11,6 +11,7 @@ import {
} from 'lodash'; } from 'lodash';
import { secondsToHis } from '../services/utils'; import { secondsToHis } from '../services/utils';
import http from '../services/http';
import stub from '../stubs/album'; import stub from '../stubs/album';
import songStore from './song'; import songStore from './song';
import artistStore from './artist'; 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. * Get top n most-played albums.
* *

View file

@ -215,22 +215,7 @@ export default {
song.artist.image = data.artist_info.image; song.artist.image = data.artist_info.image;
} }
// Convert the duration into i:s data.album_info && albumStore.mergeAlbumInfo(song.album, data.album_info);
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;
}
song.infoRetrieved = true; song.infoRetrieved = true;

View file

@ -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() { @mixin inset-when-pressed() {
&:active { &:active {
box-shadow: inset 0px 10px 10px -10px rgba(0,0,0,1); box-shadow: inset 0px 10px 10px -10px rgba(0,0,0,1);