This commit is contained in:
An Phan 2016-04-05 15:38:10 +08:00
parent 1fb535b89f
commit cfdb4034d1
12 changed files with 186 additions and 96 deletions

View file

@ -62,12 +62,7 @@ class File
*/
public function __construct($path, $getID3 = null)
{
if ($path instanceof SplFileInfo) {
$this->splFileInfo = $path;
} else {
$this->splFileInfo = new SplFileInfo($path);
}
$this->splFileInfo = $path instanceof SplFileInfo ? $path : new SplFileInfo($path);
$this->setGetID3($getID3);
$this->mtime = $this->splFileInfo->getMTime();
$this->path = $this->splFileInfo->getPathname();

View file

@ -237,9 +237,9 @@ class Song extends Model
*/
public function getLyricsAttribute($value)
{
// We don't use nl2br() here, because the function actually preserve linebreaks -
// it just _appends_ a "<br />" after each of them. This would case our client
// implementation of br2nl fails with duplicated linebreaks.
// We don't use nl2br() here, because the function actually preserves linebreaks -
// it just _appends_ a "<br />" after each of them. This would cause our client
// implementation of br2nl to fail with duplicated linebreaks.
return str_replace(["\r\n", "\r", "\n"], '<br />', $value);
}
}

View file

@ -1,12 +1,12 @@
import { shuffle } from 'lodash';
import $ from 'jquery';
import plyr from 'plyr';
import queueStore from '../stores/queue';
import songStore from '../stores/song';
import artistStore from '../stores/artist';
import preferenceStore from '../stores/preference';
import config from '../config';
import plyr from 'plyr';
export default {
app: null,

View file

@ -29,7 +29,7 @@ export default {
*/
init(artists) {
// Traverse through the artists array and add their albums into our master album list.
this.state.albums = reduce(artists, (albums, artist) => {
this.all = reduce(artists, (albums, artist) => {
// While we're doing so, for each album, we get its length
// and keep a back reference to the artist too.
each(artist.albums, album => {
@ -40,7 +40,7 @@ export default {
}, []);
// Then we init the song store.
songStore.init(this.state.albums);
songStore.init(this.all);
},
setupAlbum(album, artist) {
@ -61,6 +61,15 @@ export default {
return this.state.albums;
},
/**
* Set all albums.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.albums = value;
},
byId(id) {
return find(this.all, { id });
},
@ -81,13 +90,18 @@ export default {
},
/**
* Appends a new album into the current collection.
* Add new album/albums into the current collection.
*
* @param {Object} album
* @param {Array.<Object>|Object} albums
*/
append(album) {
this.state.albums.push(this.setupAlbum(album, album.artist));
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0);
add(albums) {
albums = [].concat(albums);
each(albums, a => {
this.setupAlbum(a, a.artist)
a.playCount = reduce(a.songs, (count, song) => count + song.playCount, 0);
});
this.all = union(this.all, albums);
},
/**
@ -101,7 +115,7 @@ export default {
album.songs = union(album.songs ? album.songs : [], songs);
songs.forEach(song => {
each(songs, song => {
song.album_id = album.id;
song.album = album;
});
@ -140,10 +154,10 @@ export default {
*/
remove(albums) {
albums = [].concat(albums);
this.state.albums = difference(this.state.albums, albums);
this.all = difference(this.all, albums);
// Remove from the artist as well
albums.forEach(album => {
each(albums, album => {
artistStore.removeAlbumsFromArtist(album.artist, album);
});
},
@ -157,7 +171,7 @@ export default {
*/
getMostPlayed(n = 6) {
// Only non-unknown albums with actually play count are applicable.
const applicable = filter(this.state.albums, album => {
const applicable = filter(this.all, album => {
return album.playCount && album.id !== 1;
});

View file

@ -27,14 +27,14 @@ export default {
* @param {Array.<Object>} artists The array of artists we got from the server.
*/
init(artists) {
this.state.artists = artists;
this.all = artists;
// Traverse through artists array to get the cover and number of songs for each.
each(this.state.artists, artist => {
each(this.all, artist => {
this.setupArtist(artist);
});
albumStore.init(this.state.artists);
albumStore.init(this.all);
},
setupArtist(artist) {
@ -46,29 +46,67 @@ export default {
return artist;
},
/**
* Get all artists.
*
* @return {Array.<Object>}
*/
get all() {
return this.state.artists;
},
/**
* Set all artists.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.artists = value;
},
/**
* Get an artist object by its ID.
*
* @param {Number} id
*/
byId(id) {
return find(this.all, { id });
},
/**
* Appends a new artist into the current collection.
* Adds an artist/artists into the current collection.
*
* @param {Object} artist
* @param {Array.<Object>|Object} artists
*/
append(artist) {
this.state.artists.push(this.setupArtist(artist));
add(artists) {
artists = [].concat(artists);
each(artists, a => this.setupArtist(a));
this.all = union(this.all, artists);
},
/**
* Remove artist(s) from the store.
*
* @param {Array.<Object>|Object} artists
*/
remove(artists) {
this.all = difference(this.all, [].concat(artists));
},
/**
* Add album(s) into an artist.
*
* @param {Object} artist
* @param {Array.<Object>|Object} albums
*
*/
addAlbumsIntoArtist(artist, albums) {
albums = [].concat(albums);
artist.albums = union(artist.albums ? artist.albums : [], albums);
albums.forEach(album => {
each(albums, album => {
album.artist_id = artist.id;
album.artist = artist;
});
@ -98,15 +136,6 @@ export default {
return !artist.albums.length;
},
/**
* Remove artist(s) from the store.
*
* @param {Array.<Object>|Object} artists
*/
remove(artists) {
this.state.artists = difference(this.state.artists, [].concat(artists));
},
/**
* Get all songs performed by an artist.
*
@ -130,24 +159,21 @@ export default {
* @return {String}
*/
getImage(artist) {
// If the artist already has a proper image, just return it.
if (artist.image) {
return artist.image;
if (!artist.image) {
// Try to get an image from one of the albums.
artist.image = config.unknownCover;
artist.albums.every(album => {
// If there's a "real" cover, use it.
if (album.image !== config.unknownCover) {
artist.image = album.cover;
// I want to break free.
return false;
}
});
}
// Otherwise, we try to get an image from one of their albums.
artist.image = config.unknownCover;
artist.albums.every(album => {
// If there's a "real" cover, use it.
if (album.image !== config.unknownCover) {
artist.image = album.cover;
// I want to break free.
return false;
}
});
return artist.image;
},
@ -160,7 +186,7 @@ export default {
*/
getMostPlayed(n = 6) {
// Only non-unknown artists with actually play count are applicable.
const applicable = filter(this.state.artists, artist => {
const applicable = filter(this.all, artist => {
return artist.playCount && artist.id !== 1;
});

View file

@ -23,8 +23,13 @@ export default {
return this.state.songs;
},
clear() {
this.state.songs = [];
/**
* Set all favorite'd songs.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.songs = value;
},
/**
@ -53,21 +58,28 @@ export default {
},
/**
* Add a song into the store.
* Add a song/songs into the store.
*
* @param {Object} song
* @param {Array.<Object>|Object} songs
*/
add(song) {
this.state.songs.push(song);
add(songs) {
this.all = union(this.all, [].concat(songs));
},
/**
* Remove a song from the store.
* Remove a song/songs from the store.
*
* @param {Object} song
* @param {Array.<Object>|Object} songs
*/
remove(song) {
this.state.songs = difference(this.state.songs, [song]);
remove(songs) {
this.all = difference(this.all, [].concat(songs));
},
/**
* Remove all favorites.
*/
clear() {
this.all = [];
},
/**
@ -80,7 +92,7 @@ export default {
// Don't wait for the HTTP response to update the status, just set them to Liked right away.
// This may cause a minor problem if the request fails somehow, but do we care?
each(songs, song => song.liked = true);
this.state.songs = union(this.state.songs, songs);
this.add(songs);
http.post('interaction/batch/like', { songs: map(songs, 'id') }, () => {
if (cb) {
@ -99,7 +111,7 @@ export default {
// Don't wait for the HTTP response to update the status, just set them to Unliked right away.
// This may cause a minor problem if the request fails somehow, but do we care?
each(songs, song => song.liked = false);
this.state.songs = difference(this.state.songs, songs);
this.remove(songs);
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, () => {
if (cb) {

View file

@ -19,9 +19,8 @@ export default {
},
init(playlists) {
this.state.playlists = playlists;
each(this.state.playlists, this.getSongs);
this.all = playlists;
each(this.all, this.getSongs);
},
/**
@ -33,6 +32,15 @@ export default {
return this.state.playlists;
},
/**
* Set all playlists.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.playlists = value;
},
/**
* Get all songs in a playlist.
*
@ -42,6 +50,24 @@ export default {
return (playlist.songs = songStore.byIds(playlist.songs));
},
/**
* Add a playlist/playlists into the store.
*
* @param {Array.<Object>|Object} playlists
*/
add(playlists) {
this.all = union(this.all, [].concat(playlists));
},
/**
* Remove a playlist/playlists from the store.
*
* @param {Array.<Object>|Object} playlist
*/
remove(playlists) {
this.all = difference(this.all, [].concat(playlists));
},
/**
* Create a new playlist, optionally with its songs.
*
@ -61,7 +87,7 @@ export default {
const playlist = response.data;
playlist.songs = songs;
this.getSongs(playlist);
this.state.playlists.push(playlist);
this.add(playlist);
if (cb) {
cb();
@ -79,7 +105,7 @@ export default {
NProgress.start();
http.delete(`playlist/${playlist.id}`, {}, () => {
this.state.playlists = without(this.state.playlists, playlist);
this.remove(playlist);
if (cb) {
cb();

View file

@ -1,6 +1,7 @@
import {
head,
last,
each,
includes,
union,
difference,
@ -139,7 +140,7 @@ export default {
move(songs, target) {
const $targetIndex = this.indexOf(target);
songs.forEach(song => {
each(songs, song => {
this.all.splice(this.indexOf(song), 1);
this.all.splice($targetIndex, 0, song);
});

View file

@ -1,5 +1,5 @@
import Vue from 'vue';
import { without, map, take, remove, orderBy } from 'lodash';
import { without, map, take, remove, orderBy, each } from 'lodash';
import http from '../services/http';
import utils from '../services/utils';
@ -39,9 +39,9 @@ export default {
*/
init(albums) {
// Iterate through the albums. With each, add its songs into our master song list.
this.state.songs = albums.reduce((songs, album) => {
this.all = albums.reduce((songs, album) => {
// While doing so, we populate some other information into the songs as well.
album.songs.forEach(song => {
each(album.songs, song => {
song.fmtLength = utils.secondsToHis(song.length);
// Manually set these additional properties to be reactive
@ -67,7 +67,7 @@ export default {
initInteractions(interactions) {
favoriteStore.clear();
interactions.forEach(interaction => {
each(interactions, interaction => {
const song = this.byId(interaction.song_id);
if (!song) {
@ -112,6 +112,15 @@ export default {
return this.state.songs;
},
/**
* Set all songs.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.songs = value;
},
/**
* Get a song by its ID.
*
@ -203,7 +212,7 @@ export default {
// Convert the duration into i:s
if (data.album_info && data.album_info.tracks) {
data.album_info.tracks.forEach(track => track.fmtLength = utils.secondsToHis(track.length));
each(data.album_info.tracks, track => track.fmtLength = utils.secondsToHis(track.length));
}
// If the album cover is not in a nice form, don't use it.
@ -261,9 +270,7 @@ export default {
data,
songs: map(songs, 'id'),
}, response => {
response.data.forEach(song => {
this.syncUpdatedSong(song);
});
each(response.data, song => this.syncUpdatedSong(song));
if (successCb) {
successCb();
@ -327,7 +334,7 @@ export default {
// - Add the new album into our collection
// - Add the song into it
albumStore.addSongsIntoAlbum(updatedSong.album, originalSong);
albumStore.append(updatedSong.album);
albumStore.add(updatedSong.album);
}
if (updatedSong.album.artist.id === originalArtistId) { // case 2.a
@ -345,7 +352,7 @@ export default {
// (there's no "new artist with existing album" in our system).
// - Add the new artist into our collection
artistStore.addAlbumsIntoArtist(updatedSong.album.artist, updatedSong.album);
artistStore.append(updatedSong.album.artist);
artistStore.add(updatedSong.album.artist);
}
}
@ -395,7 +402,7 @@ export default {
* @return {Array.<Object>}
*/
getMostPlayed(n = 10) {
const songs = take(orderBy(this.state.songs, 'playCount', 'desc'), n);
const songs = take(orderBy(this.all, 'playCount', 'desc'), n);
// Remove those with playCount=0
remove(songs, song => !song.playCount);

View file

@ -21,11 +21,11 @@ export default {
* @param {Object} currentUser The current user.
*/
init(users, currentUser) {
this.state.users = users;
this.state.current = currentUser;
this.all = users;
this.current = currentUser;
// Set the avatar for each of the users…
each(this.state.users, this.setAvatar);
each(this.all, this.setAvatar);
// …and the current user as well.
this.setAvatar();
@ -40,6 +40,15 @@ export default {
return this.state.users;
},
/**
* Set all users.
*
* @param {Array.<Object>} value
*/
set all(value) {
this.state.users = value;
},
/**
* Get a user by his ID
*
@ -48,7 +57,7 @@ export default {
* @return {Object}
*/
byId(id) {
return find(this.state.users, { id });
return find(this.all, { id });
},
/**
@ -155,7 +164,7 @@ export default {
const user = response.data;
this.setAvatar(user);
this.state.users.unshift(user);
this.all.unshift(user);
if (cb) {
cb();
@ -195,7 +204,7 @@ export default {
NProgress.start();
http.delete(`user/${user.id}`, {}, () => {
this.state.users = without(this.state.users, user);
this.all = without(this.all, user);
// Mama, just killed a man
// Put a gun against his head

View file

@ -37,12 +37,12 @@ describe('stores/album', () => {
});
});
describe('#append', () => {
describe('#add', () => {
beforeEach(() => {
albumStore.append(cloneDeep(singleAlbum));
albumStore.add(cloneDeep(singleAlbum));
});
it('correctly appends a new album into the state', () => {
it('correctly adds a new album into the state', () => {
last(albumStore.state.albums).id.should.equal(9999);
});

View file

@ -34,10 +34,10 @@ describe('stores/artist', () => {
});
});
describe('#append', () => {
beforeEach(() => artistStore.append(cloneDeep(singleArtist)));
describe('#add', () => {
beforeEach(() => artistStore.add(cloneDeep(singleArtist)));
it('correctly appends an artist', () => {
it('correctly adds an artist', () => {
last(artistStore.state.artists).name.should.equal('John Cena');
});
});