First try

This commit is contained in:
An Phan 2016-04-17 23:38:06 +08:00
parent dc27eeba8a
commit 4dc06719b3
30 changed files with 362 additions and 81 deletions

View file

@ -46,7 +46,7 @@ class SongController extends Controller
return response()->json([
'lyrics' => $song->lyrics,
'album_info' => $song->album->getInfo(),
'artist_info' => $song->album->artist->getInfo(),
'artist_info' => $song->artist->getInfo(),
]);
}

View file

@ -2,9 +2,7 @@
namespace App\Listeners;
use App\Models\Album;
use App\Models\Artist;
use App\Models\Song;
use Media;
class TidyLibrary
{
@ -17,16 +15,10 @@ class TidyLibrary
/**
* Fired every time a LibraryChanged event is triggered.
* Remove empty albums and artists from our system.
* Tidies up our lib.
*/
public function handle()
{
$inUseAlbums = Song::select('album_id')->groupBy('album_id')->get()->lists('album_id');
$inUseAlbums[] = Album::UNKNOWN_ID;
Album::whereNotIn('id', $inUseAlbums)->delete();
$inUseArtists = Album::select('artist_id')->groupBy('artist_id')->get()->lists('artist_id');
$inUseArtists[] = Artist::UNKNOWN_ID;
Artist::whereNotIn('id', $inUseArtists)->delete();
Media::tidy();
}
}

View file

@ -34,13 +34,13 @@ class UpdateLastfmNowPlaying
{
if (!$this->lastfm->enabled() ||
!($sessionKey = $event->user->getLastfmSessionKey()) ||
$event->song->album->artist->isUnknown()
$event->song->artist->isUnknown()
) {
return;
}
$this->lastfm->updateNowPlaying(
$event->song->album->artist->name,
$event->song->artist->name,
$event->song->title,
$event->song->album->name === Album::UNKNOWN_NAME ? null : $event->song->album->name,
$event->song->length,

View file

@ -6,11 +6,12 @@ use App\Facades\Lastfm;
use Illuminate\Database\Eloquent\Model;
/**
* @property string cover The path to the album's cover
* @property bool has_cover If the album has a cover image
* @property string cover The path to the album's cover
* @property bool has_cover If the album has a cover image
* @property int id
* @property string name Name of the album
* @property Artist artist The album's artist
* @property string name Name of the album
* @property bool is_compilation If the album is a compilation from multiple artists
* @property Artist artist The album's artist
*/
class Album extends Model
{
@ -20,6 +21,7 @@ class Album extends Model
protected $guarded = ['id'];
protected $hidden = ['created_at', 'updated_at'];
protected $casts = ['is_compilation' => 'bool'];
public function artist()
{
@ -40,21 +42,23 @@ class Album extends Model
* Get an album using some provided information.
*
* @param Artist $artist
* @param $name
* @param string $name
* @param bool $isCompilation
*
* @return self
*/
public static function get(Artist $artist, $name)
public static function get(Artist $artist, $name, $isCompilation = false)
{
// If an empty name is provided, turn it into our "Unknown Album"
$name = $name ?: self::UNKNOWN_NAME;
// If this is a compilation album, its artist must be "Various Artists"
if ($isCompilation) {
$artist = Artist::getVarious();
}
$album = self::firstOrCreate([
return self::firstOrCreate([
'artist_id' => $artist->id,
'name' => $name,
'name' => $name ?: self::UNKNOWN_NAME,
'is_compilation' => $isCompilation,
]);
return $album;
}
/**

View file

@ -16,6 +16,8 @@ class Artist extends Model
{
const UNKNOWN_ID = 1;
const UNKNOWN_NAME = 'Unknown Artist';
const VARIOUS_ID = 2;
const VARIOUS_NAME = 'Various Artists';
protected $guarded = ['id'];
@ -31,6 +33,21 @@ class Artist extends Model
return $this->id === self::UNKNOWN_ID;
}
public function isVarious()
{
return $this->id === self::VARIOUS_ID;
}
/**
* Get the "Various Artists" object.
*
* @return Artist
*/
public static function getVarious()
{
return self::find(self::VARIOUS_ID);
}
/**
* Sometimes the tags extracted from getID3 are HTML entity encoded.
* This makes sure they are always sane.

View file

@ -0,0 +1,8 @@
<?php
namespace App\Models;
class ContributingArtist extends Artist
{
protected $table = 'artists';
}

View file

@ -79,7 +79,7 @@ class File
{
$info = $this->getID3->analyze($this->path);
if (isset($info['error'])) {
if (isset($info['error']) || !isset($info['playtime_seconds'])) {
return;
}
@ -89,10 +89,6 @@ class File
// Read on.
getid3_lib::CopyTagsToComments($info);
if (!isset($info['playtime_seconds'])) {
return;
}
$track = 0;
// Apparently track number can be stored with different indices as the following.
@ -109,6 +105,7 @@ class File
$props = [
'artist' => '',
'album' => '',
'part_of_a_compilation' => false,
'title' => '',
'length' => $info['playtime_seconds'],
'track' => (int) $track,
@ -122,6 +119,10 @@ class File
return $props;
}
// getID3's 'part_of_a_compilation' value can either be null, ['0'], or ['1']
// We convert it into a boolean here.
$props['part_of_a_compilation'] = !!array_get($comments, 'part_of_a_compilation', [false])[0];
// We prefer id3v2 tags over others.
if (!$artist = array_get($info, 'tags.id3v2.artist', [null])[0]) {
$artist = array_get($comments, 'artist', [''])[0];
@ -175,9 +176,11 @@ class File
$info = array_intersect_key($info, array_flip($tags));
$artist = isset($info['artist']) ? Artist::get($info['artist']) : $this->song->album->artist;
$album = isset($info['album']) ? Album::get($artist, $info['album']) : $this->song->album;
$album = isset($info['album'])
? Album::get($artist, $info['album'], array_get($info, 'part_of_a_compilation'))
: $this->song->album;
} else {
$album = Album::get(Artist::get($info['artist']), $info['album']);
$album = Album::get(Artist::get($info['artist']), $info['album'], array_get($info, 'part_of_a_compilation'));
}
if (!empty($info['cover']) && !$album->has_cover) {
@ -190,8 +193,13 @@ class File
$info['album_id'] = $album->id;
// If the song is part of a compilation, we set its artist as the contributing artist.
if (array_get($info, 'part_of_a_compilation')) {
$info['contributing_artist_id'] = Artist::get($info['artist'])->id;
}
// Remove these values from the info array, so that we can just use the array as model's input data.
array_forget($info, ['artist', 'album', 'cover']);
array_forget($info, ['artist', 'album', 'cover', 'part_of_a_compilation']);
$song = Song::updateOrCreate(['id' => $this->hash], $info);
$song->save();

View file

@ -31,6 +31,7 @@ class Song extends Model
'length' => 'float',
'mtime' => 'int',
'track' => 'int',
'contributing_artist_id' => 'int',
];
/**
@ -40,6 +41,11 @@ class Song extends Model
*/
public $incrementing = false;
public function contributingArtist()
{
return $this->belongsTo(ContributingArtist::class);
}
public function album()
{
return $this->belongsTo(Album::class);
@ -60,7 +66,7 @@ class Song extends Model
public function scrobble($timestamp)
{
// Don't scrobble the unknown guys. No one knows them.
if ($this->album->artist->isUnknown()) {
if ($this->artist->isUnknown()) {
return false;
}
@ -70,7 +76,7 @@ class Song extends Model
}
return Lastfm::scrobble(
$this->album->artist->name,
$this->artist->name,
$this->title,
$timestamp,
$this->album->name === Album::UNKNOWN_NAME ? '' : $this->album->name,
@ -242,4 +248,18 @@ class Song extends Model
// implementation of br2nl to fail with duplicated linebreaks.
return str_replace(["\r\n", "\r", "\n"], '<br />', $value);
}
/**
* Get the correct artist of the song.
* If it's part of a compilation, that would be the contributing artist.
* Otherwise, it's the album artist.
*
* @return Artist
*/
public function getArtistAttribute()
{
return $this->contributing_artist_id
? $this->contributingArtist
: $this->album->artist;
}
}

View file

@ -5,6 +5,8 @@ namespace App\Services;
use App\Console\Commands\SyncMedia;
use App\Events\LibraryChanged;
use App\Libraries\WatchRecord\WatchRecordInterface;
use App\Models\Album;
use App\Models\Artist;
use App\Models\File;
use App\Models\Setting;
use App\Models\Song;
@ -20,7 +22,17 @@ class Media
*
* @var array
*/
protected $allTags = ['artist', 'album', 'title', 'length', 'track', 'lyrics', 'cover', 'mtime'];
protected $allTags = [
'artist',
'album',
'title',
'length',
'track',
'lyrics',
'cover',
'mtime',
'part_of_a_compilation',
];
/**
* Tags to be synced.
@ -190,4 +202,29 @@ class Media
{
return File::getHash($path);
}
/**
* Tidy up the library by deleting empty albums and artists.
*/
public function tidy()
{
$inUseAlbums = Song::select('album_id')->groupBy('album_id')->get()->lists('album_id')->toArray();
$inUseAlbums[] = Album::UNKNOWN_ID;
Album::whereNotIn('id', $inUseAlbums)->delete();
$inUseArtists = Album::select('artist_id')->groupBy('artist_id')->get()->lists('artist_id')->toArray();
$contributingArtists = Song::distinct()
->select('contributing_artist_id')
->groupBy('contributing_artist_id')
->get()
->lists('contributing_artist_id')
->toArray();
$inUseArtists = array_merge($inUseArtists, $contributingArtists);
$inUseArtists[] = Artist::UNKNOWN_ID;
$inUseArtists[] = Artist::VARIOUS_ID;
Artist::whereNotIn('id', $inUseArtists)->delete();
}
}

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddIsComplilationIntoAlbums extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('albums', function (Blueprint $table) {
$table->boolean('is_compilation')->nullable()->defaults(false)->after('cover');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('albums', function (Blueprint $table) {
$table->dropColumn('is_compilation');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddContributingArtistIdIntoSongs extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('songs', function (Blueprint $table) {
$table->integer('contributing_artist_id')->unsigned()->nullable()->after('album_id');
$table->foreign('contributing_artist_id')->references('id')->on('artists')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('songs', function (Blueprint $table) {
$table->dropColumn('contributing_artist_id');
});
}
}

View file

@ -0,0 +1,49 @@
<?php
use App\Models\Artist;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateVariousArtists extends Migration
{
/**
* Create the "Various Artists".
*
* @return void
*/
public function up()
{
Artist::unguard();
$existingArtist = Artist::find(Artist::VARIOUS_ID);
if ($existingArtist) {
if ($existingArtist->name === Artist::VARIOUS_NAME) {
goto ret;
}
// There's an existing artist with that special ID, but it's not our Various Artist
// We move it to the end of the table.
$latestArtist = Artist::orderBy('id', 'DESC')->first();
$existingArtist->id = $latestArtist->id + 1;
$existingArtist->save();
}
Artist::create([
'id' => Artist::VARIOUS_ID,
'name' => Artist::VARIOUS_NAME,
]);
ret:
Artist::reguard();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View file

@ -12,7 +12,7 @@
<div class="panes">
<lyrics :song="song" v-ref:lyrics v-show="currentView === 'lyrics'"></lyrics>
<artist-info :artist="song.album.artist" v-ref:artist-info v-show="currentView === 'artistInfo'"></artist-info>
<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>
</div>
</div>

View file

@ -12,7 +12,9 @@
@click.prevent="showingControls = false"></i>
<span class="meta" v-show="meta.songCount">
by <a class="artist" @click.prevent="viewArtistDetails">{{ album.artist.name }}</a>
by
<a class="artist" v-if="isNormalArtist" @click.prevent="viewArtistDetails">{{ album.artist.name }}</a>
<span class="nope" v-else>{{ album.artist.name }}</span>
{{ meta.songCount }} {{ meta.songCount | pluralize 'song' }}
@ -43,6 +45,7 @@
import isMobile from 'ismobilejs';
import albumStore from '../../../stores/album';
import artistStore from '../../../stores/artist';
import playback from '../../../services/playback';
import hasSongList from '../../../mixins/has-song-list';
@ -57,6 +60,13 @@
};
},
computed: {
isNormalArtist() {
return !artistStore.isVariousArtists(this.album.artist)
&& !artistStore.isUnknownArtist(this.album.artist);
},
},
watch: {
/**
* Watch the album's song count.

View file

@ -81,7 +81,6 @@
*/
'main-content-view:load': function (view, artist) {
if (view === 'artist') {
artistStore.getSongsByArtist(artist);
this.artist = artist;
}
},

View file

@ -24,7 +24,7 @@
<span :style="{ width: song.playCount * 100 / topSongs[0].playCount + '%' }"
class="play-count"></span>
{{ song.title }}
<span class="by">{{ song.album.artist.name }}
<span class="by">{{ song.artist.name }}
{{ song.playCount }} {{ song.playCount | pluralize 'play' }}</span>
</span>
</li>
@ -47,7 +47,7 @@
</span>
<span class="details">
{{ song.title }}
<span class="by">{{ song.album.artist.name }}</span>
<span class="by">{{ song.artist.name }}</span>
</span>
</li>
</ol>

View file

@ -131,7 +131,7 @@
* @return {boolean}
*/
bySameArtist() {
return every(this.songs, song => song.album.artist.id === this.songs[0].album.artist.id);
return every(this.songs, song => song.artist.id === this.songs[0].artist.id);
},
/**
@ -188,7 +188,7 @@
if (this.editSingle) {
this.formData.title = this.songs[0].title;
this.formData.albumName = this.songs[0].album.name;
this.formData.artistName = this.songs[0].album.artist.name;
this.formData.artistName = this.songs[0].artist.name;
// If we're editing only one song and the song's info (including lyrics)
// hasn't been loaded, load it now.
@ -206,7 +206,7 @@
}
} else {
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : '';
this.formData.artistName = this.bySameArtist ? this.songs[0].album.artist.name : '';
this.formData.artistName = this.bySameArtist ? this.songs[0].artist.name : '';
this.loading = false;
}
},

View file

@ -8,7 +8,8 @@
<footer>
<a class="name" @click.prevent="viewDetails">{{ album.name }}</a>
<span class="sep">by</span>
<a class="artist" @click.prevent="viewArtistDetails">{{ album.artist.name }}</a>
<a class="artist" v-if="isNormalArtist" @click.prevent="viewArtistDetails">{{ album.artist.name }}</a>
<span class="artist nope" v-else>{{ album.artist.name }}</span>
<p class="meta">
{{ album.songs.length }} {{ album.songs.length | pluralize 'song' }}
@ -26,10 +27,18 @@
import playback from '../../services/playback';
import queueStore from '../../stores/queue';
import artistStore from '../../stores/artist';
export default {
props: ['album'],
computed: {
isNormalArtist() {
return !artistStore.isVariousArtists(this.album.artist)
&& !artistStore.isUnknownArtist(this.album.artist);
},
},
methods: {
/**
* Play all songs in the current album, or queue them up if Ctrl/Cmd key is pressed.

View file

@ -1,5 +1,5 @@
<template>
<article class="item" v-if="artist.songCount" draggable="true" @dragstart="dragStart">
<article class="item" v-if="showing" draggable="true" @dragstart="dragStart">
<span class="cover" :style="{ backgroundImage: 'url(' + artist.image + ')' }">
<a class="control" @click.prevent="play">
<i class="fa fa-play"></i>
@ -29,13 +29,25 @@
export default {
props: ['artist'],
computed: {
/**
* Determine if the artist item should be shown.
* We're not showing those without any songs, or the special "Various Artists".
*
* @return {Boolean}
*/
showing() {
return this.artist.songCount && !artistStore.isVariousArtists(this.artist);
}
},
methods: {
/**
* Play all songs by the current artist, or queue them up if Ctrl/Cmd key is pressed.
*/
play(e) {
if (e.metaKey || e.ctrlKey) {
queueStore.queue(artistStore.getSongsByArtist(this.artist));
queueStore.queue(this.artist.songs);
} else {
playback.playAllByArtist(this.artist);
}
@ -49,7 +61,7 @@
* Allow dragging the artist (actually, their songs).
*/
dragStart(e) {
const songIds = map(artistStore.getSongsByArtist(this.artist), 'id');
const songIds = map(this.artist.songs, 'id');
e.dataTransfer.setData('text/plain', songIds);
e.dataTransfer.effectAllowed = 'move';

View file

@ -6,7 +6,7 @@
>
<td class="track-number">{{ song.track || '' }}</td>
<td class="title">{{ song.title }}</td>
<td class="artist">{{ song.album.artist.name }}</td>
<td class="artist">{{ song.artist.name }}</td>
<td class="album">{{ song.album.name }}</td>
<td class="time">{{* song.fmtLength }}</td>
<td class="play" @click.stop="doPlayback">

View file

@ -133,7 +133,7 @@
* Navigate to the song's artist view
*/
goToArtist() {
this.$root.loadArtist(this.songs[0].album.artist);
this.$root.loadArtist(this.songs[0].artist);
},
},

View file

@ -24,7 +24,7 @@
<div class="progress" id="progressPane">
<h3 class="title">{{ song.title }}</h3>
<p class="meta">
<a class="artist" @click.prevent="loadArtist(song.album.artist)">{{ song.album.artist.name }}</a>
<a class="artist" @click.prevent="loadArtist(song.artist)">{{ song.artist.name }}</a>
<a class="album" @click.prevent="loadAlbum(song.album)">{{ song.album.name }}</a>
</p>

View file

@ -15,6 +15,6 @@ export function filterSongBy (songs, search, delimiter) {
return filter(songs, song => {
return song.title.toLowerCase().indexOf(search) !== -1 ||
song.album.name.toLowerCase().indexOf(search) !== -1 ||
song.album.artist.name.toLowerCase().indexOf(search) !== -1;
song.artist.name.toLowerCase().indexOf(search) !== -1;
});
}

View file

@ -128,7 +128,7 @@ export default {
this.player.media.src = songStore.getSourceUrl(song);
$('title').text(`${song.title}${config.appTitle}`);
$('.plyr audio').attr('title', `${song.album.artist.name} - ${song.title}`);
$('.plyr audio').attr('title', `${song.artist.name} - ${song.title}`);
// We'll just "restart" playing the song, which will handle notification, scrobbling etc.
this.restart();
@ -159,7 +159,7 @@ export default {
try {
const notification = new Notification(`${song.title}`, {
icon: song.album.cover,
body: `${song.album.name} ${song.album.artist.name}`
body: `${song.album.name} ${song.artist.name}`
});
notification.onclick = () => window.focus();
@ -377,7 +377,7 @@ export default {
* @param {Boolean=true} shuffle Whether to shuffle the songs
*/
playAllByArtist(artist, shuffle = true) {
this.queueAndPlay(artistStore.getSongsByArtist(artist), true);
this.queueAndPlay(artist.songs, true);
},
/**

View file

@ -7,13 +7,16 @@ import {
difference,
take,
filter,
orderBy
orderBy,
} from 'lodash';
import config from '../config';
import stub from '../stubs/artist';
import albumStore from './album';
const UNKNOWN_ARTIST_ID = 1;
const VARIOUS_ARTISTS_ID = 2;
export default {
stub,
@ -29,18 +32,38 @@ export default {
init(artists) {
this.all = artists;
albumStore.init(this.all);
// Traverse through artists array to get the cover and number of songs for each.
each(this.all, artist => {
this.setupArtist(artist);
});
albumStore.init(this.all);
},
/**
* Set up the (reactive) properties of an artist.
*
* @param {Object} artist
*/
setupArtist(artist) {
this.getImage(artist);
Vue.set(artist, 'playCount', 0);
Vue.set(artist, 'songCount', reduce(artist.albums, (count, album) => count + album.songs.length, 0));
// Here we build a list of songs performed by the artist, so that we don't need to traverse
// down the "artist > albums > items" route later.
// This also makes sure songs in compilation albums are counted as well.
Vue.set(artist, 'songs', reduce(artist.albums, (songs, album) => {
// If the album is compilation, we cater for the songs contributed by this artist only.
if (album.is_compilation) {
return songs.concat(filter(album.songs, { contributing_artist_id: artist.id }));
}
// Otherwise, just use all songs.
return songs.concat(album.songs);
}, []));
Vue.set(artist, 'songCount', artist.songs.length);
Vue.set(artist, 'info', null);
return artist;
@ -136,6 +159,28 @@ export default {
return !artist.albums.length;
},
/**
* Determine if the artist is the special "Various Artists".
*
* @param {Object} artist
*
* @return {Boolean}
*/
isVariousArtists(artist) {
return artist.id === VARIOUS_ARTISTS_ID;
},
/**
* Determine if the artist is the special "Unknown Artist".
*
* @param {Object} artist [description]
*
* @return {Boolean}
*/
isUnknownArtist(artist) {
return artist.id === UNKNOWN_ARTIST_ID;
},
/**
* Get all songs performed by an artist.
*
@ -144,10 +189,6 @@ export default {
* @return {Array.<Object>}
*/
getSongsByArtist(artist) {
if (!artist.songs) {
artist.songs = reduce(artist.albums, (songs, album) => songs.concat(album.songs), []);
}
return artist.songs;
},
@ -186,8 +227,11 @@ export default {
*/
getMostPlayed(n = 6) {
// Only non-unknown artists with actually play count are applicable.
// Also, "Various Artists" doesn't count.
const applicable = filter(this.all, artist => {
return artist.playCount && artist.id !== 1;
return artist.playCount
&& !this.isUnknownArtist(artist)
&& !this.isVariousArtists(artist);
});
return take(orderBy(applicable, 'playCount', 'desc'), n);

View file

@ -1,5 +1,5 @@
import Vue from 'vue';
import { without, map, take, remove, orderBy, each } from 'lodash';
import { without, map, take, remove, orderBy, each, union } from 'lodash';
import http from '../services/http';
import { secondsToHis } from '../services/utils';
@ -51,6 +51,15 @@ export default {
Vue.set(song, 'lyrics', null);
Vue.set(song, 'playbackState', 'stopped');
if (song.contributing_artist_id) {
const artist = artistStore.byId(song.contributing_artist_id);
artist.albums = union(artist.albums, [album]);
artistStore.setupArtist(artist);
Vue.set(song, 'artist', artist);
} else {
Vue.set(song, 'artist', artistStore.byId(song.album.artist.id));
}
// Cache the song, so that byId() is faster
this.cache[song.id] = song;
});
@ -77,7 +86,7 @@ export default {
song.liked = interaction.liked;
song.playCount = interaction.play_count;
song.album.playCount += song.playCount;
song.album.artist.playCount += song.playCount;
song.artist.playCount += song.playCount;
if (song.liked) {
favoriteStore.add(song);
@ -156,7 +165,7 @@ export default {
// Use the data from the server to make sure we don't miss a play from another device.
song.playCount = response.data.play_count;
song.album.playCount += song.playCount - oldCount;
song.album.artist.playCount += song.playCount - oldCount;
song.artist.playCount += song.playCount - oldCount;
if (cb) {
cb();
@ -203,11 +212,11 @@ export default {
data.artist_info.image = null;
}
song.album.artist.info = data.artist_info;
song.artist.info = data.artist_info;
// Set the artist image on the client side to the retrieved image from server.
if (data.artist_info.image) {
song.album.artist.image = data.artist_info.image;
song.artist.image = data.artist_info.image;
}
// Convert the duration into i:s
@ -310,7 +319,7 @@ export default {
// and keep track of original album/artist.
const originalAlbumId = originalSong.album.id;
const originalArtistId = originalSong.album.artist.id;
const originalArtistId = originalSong.artist.id;
// First, we update the title, lyrics, and track #
originalSong.title = updatedSong.title;

View file

@ -1,7 +1,9 @@
import album from './album';
import artist from './artist';
export default {
album,
artist,
id: null,
album_id: 0,
title: '',

View file

@ -22,12 +22,6 @@ describe('stores/artist', () => {
});
});
describe('#getSongsByArtist', () => {
it('correctly gathers all songs by artist', () => {
artistStore.getSongsByArtist(artistStore.state.artists[0]).length.should.equal(3);
});
});
describe('#getImage', () => {
it('correctly gets an artists image', () => {
artistStore.getImage(artistStore.state.artists[0]).should.equal('/public/img/covers/565c0f7067425.jpeg');

View file

@ -158,6 +158,10 @@
display: inline;
}
}
.nope {
opacity: .5;
}
}
}

View file

@ -19,13 +19,13 @@ class ArtistTest extends TestCase
$this->assertEquals($name, $artist->name);
// Should be only 2 records: UNKNOWN_ARTIST, and our Dave Grohl's band
$this->assertEquals(2, Artist::all()->count());
// Should be only 3 records: UNKNOWN_ARTIST, VARIOUS_ARTISTS, and our Dave Grohl's band
$this->assertEquals(3, Artist::all()->count());
Artist::get($name);
// Should still be 2.
$this->assertEquals(2, Artist::all()->count());
// Should still be 3.
$this->assertEquals(3, Artist::all()->count());
}
public function testArtistWithEmptyNameShouldBeUnknown()