Refactor and docs

This commit is contained in:
An Phan 2015-12-23 01:46:54 +08:00
parent c071827fd5
commit 5e79a2737e
12 changed files with 96 additions and 116 deletions

View file

@ -24,11 +24,6 @@
import overlay from './components/shared/overlay.vue'; import overlay from './components/shared/overlay.vue';
import sharedStore from './stores/shared'; import sharedStore from './stores/shared';
import artistStore from './stores/artist';
import playlistStore from './stores/playlist';
import queueStore from './stores/queue';
import userStore from './stores/user';
import settingStore from './stores/setting';
import preferenceStore from './stores/preference'; import preferenceStore from './stores/preference';
import playback from './services/playback'; import playback from './services/playback';
@ -56,7 +51,6 @@
// Make the most important HTTP request to get all necessary data from the server. // Make the most important HTTP request to get all necessary data from the server.
// Afterwards, init all mandatory stores and services. // Afterwards, init all mandatory stores and services.
sharedStore.init(() => { sharedStore.init(() => {
this.initStores();
playback.init(this); playback.init(this);
this.hideOverlay(); this.hideOverlay();
@ -70,21 +64,6 @@
}, },
methods: { methods: {
/**
* Initialize all stores to be used throughout the application.
*/
initStores() {
userStore.init();
preferenceStore.init();
// This will init album and song stores as well.
artistStore.init();
playlistStore.init();
queueStore.init();
settingStore.init();
},
/** /**
* Toggle playback when user presses Space key. * Toggle playback when user presses Space key.
* *

View file

@ -6,7 +6,6 @@ import songStore from './song';
export default { export default {
stub, stub,
artists: [],
state: { state: {
albums: [stub], albums: [stub],
@ -15,11 +14,9 @@ export default {
/** /**
* Init the store. * Init the store.
* *
* @param array artists The array of artists to extract album data from. * @param {Array} artists The array of artists to extract album data from.
*/ */
init(artists) { init(artists) {
this.artists = artists;
// Traverse through the artists array and add their albums into our master album list. // Traverse through the artists array and add their albums into our master album list.
this.state.albums = _.reduce(artists, (albums, artist) => { this.state.albums = _.reduce(artists, (albums, artist) => {
// While we're doing so, for each album, we get its length // While we're doing so, for each album, we get its length
@ -44,9 +41,9 @@ export default {
* Get the total length of an album by summing up its songs' duration. * Get the total length of an album by summing up its songs' duration.
* The length will also be converted into a H:i:s format and stored as fmtLength. * The length will also be converted into a H:i:s format and stored as fmtLength.
* *
* @param object album * @param {Array} album
* *
* @return string The H:i:s format of the album. * @return {String} The H:i:s format of the album length.
*/ */
getLength(album) { getLength(album) {
album.length = _.reduce(album.songs, (length, song) => length + song.length, 0); album.length = _.reduce(album.songs, (length, song) => length + song.length, 0);

View file

@ -2,7 +2,6 @@ import _ from 'lodash';
import config from '../config'; import config from '../config';
import albumStore from './album'; import albumStore from './album';
import sharedStore from './shared';
export default { export default {
state: { state: {
@ -14,12 +13,8 @@ export default {
* *
* @param {Array} artists The array of artists we got from the server. * @param {Array} artists The array of artists we got from the server.
*/ */
init(artists = null) { init(artists) {
this.state.artists = artists ? artists: sharedStore.state.artists; this.state.artists = artists;
// Init the album store. This must be called prior to the next logic,
// because we're using some data from the album store later.
albumStore.init(this.state.artists);
// Traverse through artists array to get the cover and number of songs for each. // Traverse through artists array to get the cover and number of songs for each.
_.each(this.state.artists, artist => { _.each(this.state.artists, artist => {
@ -27,6 +22,8 @@ export default {
artist.songCount = _.reduce(artist.albums, (count, album) => count + album.songs.length, 0); artist.songCount = _.reduce(artist.albums, (count, album) => count + album.songs.length, 0);
}); });
albumStore.init(this.state.artists);
}, },
all() { all() {

View file

@ -16,8 +16,8 @@ export default {
* Toggle like/unlike a song. * Toggle like/unlike a song.
* A request to the server will be made. * A request to the server will be made.
* *
* @param object The song object * @param {Object} The song object
* @param closure|null The function to execute afterwards * @param {Function} The function to execute afterwards
*/ */
toggleOne(song, cb = null) { toggleOne(song, cb = null) {
http.post('interaction/like', { id: song.id }, data => { http.post('interaction/like', { id: song.id }, data => {
@ -38,7 +38,7 @@ export default {
/** /**
* Add a song into the store. * Add a song into the store.
* *
* @param object The song object * @param {Object} The song object
*/ */
add(song) { add(song) {
this.state.songs.push(song); this.state.songs.push(song);
@ -47,7 +47,7 @@ export default {
/** /**
* Remove a song from the store. * Remove a song from the store.
* *
* @param object The song object * @param {Object} The song object
*/ */
remove(song) { remove(song) {
this.state.songs = _.difference(this.state.songs, [song]); this.state.songs = _.difference(this.state.songs, [song]);
@ -56,7 +56,7 @@ export default {
/** /**
* Like a bunch of songs. * Like a bunch of songs.
* *
* @param array An array of songs to like * @param {Array} An array of songs to like
*/ */
like(songs, cb = null) { like(songs, cb = null) {
this.state.songs = _.union(this.state.songs, songs); this.state.songs = _.union(this.state.songs, songs);
@ -73,7 +73,7 @@ export default {
/** /**
* Unlike a bunch of songs. * Unlike a bunch of songs.
* *
* @param array An array of songs to unlike * @param {Array} An array of songs to unlike
*/ */
unlike(songs, cb = null) { unlike(songs, cb = null) {
this.state.songs = _.difference(this.state.songs, songs); this.state.songs = _.difference(this.state.songs, songs);

View file

@ -25,7 +25,7 @@ export default {
/** /**
* Get all songs in a playlist. * Get all songs in a playlist.
* *
* return array * return {Array}
*/ */
getSongs(playlist) { getSongs(playlist) {
return (playlist.songs = songStore.byIds(playlist.songs)); return (playlist.songs = songStore.byIds(playlist.songs));

View file

@ -14,8 +14,9 @@ export default {
}, },
/** /**
* Init the store * Init the store.
* @param object user The user whose preferences we are managing. *
* @param {Object} user The user whose preferences we are managing.
*/ */
init(user = null) { init(user = null) {
if (!user) { if (!user) {

View file

@ -51,9 +51,9 @@ export default {
* Add a list of songs to the end of the current queue, * Add a list of songs to the end of the current queue,
* or replace the current queue as a whole if `replace` is true. * or replace the current queue as a whole if `replace` is true.
* *
* @param object|array songs The song, or an array of songs * @param {Object|Array} songs The song, or an array of songs
* @param bool replace Whether to replace the current queue * @param {Boolean} replace Whether to replace the current queue
* @param bool toTop Whether to prepend of append to the queue * @param {Boolean} toTop Whether to prepend of append to the queue
*/ */
queue(songs, replace = false, toTop = false) { queue(songs, replace = false, toTop = false) {
if (!Array.isArray(songs)) { if (!Array.isArray(songs)) {
@ -74,7 +74,7 @@ export default {
/** /**
* Unqueue a song, or several songs at once. * Unqueue a song, or several songs at once.
* *
* @param object|string|array songs The song(s) to unqueue. * @param {Object|String|Array} songs The song(s) to unqueue.
*/ */
unqueue(songs) { unqueue(songs) {
if (!Array.isArray(songs)) { if (!Array.isArray(songs)) {
@ -98,7 +98,7 @@ export default {
/** /**
* Get the next song in queue. * Get the next song in queue.
* *
* @return object|null * @return {Object|Null}
*/ */
getNextSong() { getNextSong() {
var i = _.pluck(this.state.songs, 'id').indexOf(this.current().id) + 1; var i = _.pluck(this.state.songs, 'id').indexOf(this.current().id) + 1;
@ -109,7 +109,7 @@ export default {
/** /**
* Get the previous song in queue. * Get the previous song in queue.
* *
* @return object|null * @return {Object|Null}
*/ */
getPrevSong() { getPrevSong() {
var i = _.pluck(this.state.songs, 'id').indexOf(this.current().id) - 1; var i = _.pluck(this.state.songs, 'id').indexOf(this.current().id) - 1;

View file

@ -1,6 +1,14 @@
import http from '../services/http';
import { assign } from 'lodash'; import { assign } from 'lodash';
import http from '../services/http';
import userStore from './user';
import preferenceStore from './preference';
import artistStore from './artist';
import songStore from './song';
import playlistStore from './playlist';
import queueStore from './queue';
import settingStore from './setting';
export default { export default {
state: { state: {
songs: [], songs: [],
@ -17,7 +25,7 @@ export default {
}, },
init(cb = null) { init(cb = null) {
http.get('data', {}, data => { http.get('data', data => {
assign(this.state, data); assign(this.state, data);
// If this is a new user, initialize his preferences to be an empty object. // If this is a new user, initialize his preferences to be an empty object.
@ -25,6 +33,16 @@ export default {
this.state.currentUser.preferences = {}; this.state.currentUser.preferences = {};
} }
userStore.init(this.state.users, this.state.currentUser);
preferenceStore.init(this.state.preferences);
artistStore.init(this.state.artists); // This will init album and song stores as well.
songStore.initInteractions(this.state.interactions);
playlistStore.init(this.state.playlists);
queueStore.init();
settingStore.init(this.state.settings);
window.useLastfm = this.state.useLastfm = data.useLastfm;
if (cb) { if (cb) {
cb(); cb();
} }

View file

@ -3,32 +3,23 @@ import _ from 'lodash';
import http from '../services/http'; import http from '../services/http';
import utils from '../services/utils'; import utils from '../services/utils';
import stub from '../stubs/song'; import stub from '../stubs/song';
import albumStore from './album';
import favoriteStore from './favorite'; import favoriteStore from './favorite';
import sharedStore from './shared';
import userStore from './user'; import userStore from './user';
export default { export default {
stub, stub,
sharedStore: null,
albums: [], albums: [],
state: { state: {
songs: [stub], songs: [stub],
interactions: [],
}, },
/** /**
* Init the store. * Init the store.
* *
* @param array albums The array of albums to extract our songs from * @param {Array} albums The array of albums to extract our songs from
* @param array interactions The array of interactions (like/play count) of the current user
*/ */
init(albums, interactions = null) { init(albums) {
this.albums = albums;
this.state.interactions = interactions ? interactions : sharedStore.state.interactions;
// Iterate through the albums. With each, add its songs into our master song list. // Iterate through the albums. With each, add its songs into our master song list.
this.state.songs = _.reduce(albums, (songs, album) => { this.state.songs = _.reduce(albums, (songs, album) => {
// While doing so, we populate some other information into the songs as well. // While doing so, we populate some other information into the songs as well.
@ -37,20 +28,38 @@ export default {
// Keep a back reference to the album // Keep a back reference to the album
song.album = album; song.album = album;
this.setInteractionStats(song);
if (song.liked) {
favoriteStore.add(song);
}
}); });
return songs.concat(album.songs); return songs.concat(album.songs);
}, []); }, []);
}, },
/**
* Initializes the interaction (like/play count) information.
*
* @param {Array} interactions The array of interactions of the current user
*/
initInteractions(interactions) {
_.each(interactions, interaction => {
var song = this.byId(interaction.song_id);
if (!song) {
return;
}
song.liked = interaction.liked;
song.playCount = interaction.play_count;
if (song.liked) {
favoriteStore.add(song);
}
});
},
/** /**
* Get all songs. * Get all songs.
*
* @return {Array}
*/ */
all() { all() {
return this.state.songs; return this.state.songs;
@ -59,9 +68,9 @@ export default {
/** /**
* Get a song by its ID * Get a song by its ID
* *
* @param string id * @param {String} id
* *
* @return object * @return {Object}
*/ */
byId(id) { byId(id) {
return _.find(this.state.songs, {id}); return _.find(this.state.songs, {id});
@ -70,37 +79,18 @@ export default {
/** /**
* Get songs by their ID's * Get songs by their ID's
* *
* @param array ids * @param {Array} ids
* *
* @return array * @return {Array}
*/ */
byIds(ids) { byIds(ids) {
return _.filter(this.state.songs, song => _.contains(ids, song.id)); return _.filter(this.state.songs, song => _.contains(ids, song.id));
}, },
/**
* Set the interaction stats (like status and playcount) for a song.
*
* @param object song
*/
setInteractionStats(song) {
var interaction = _.find(this.state.interactions, { song_id: song.id });
if (!interaction) {
song.liked = false;
song.playCount = 0;
return;
}
song.liked = interaction.liked;
song.playCount = interaction.play_count;
},
/** /**
* Increase a play count for a song. * Increase a play count for a song.
* *
* @param object song * @param {Object} song
*/ */
registerPlay(song) { registerPlay(song) {
// Increase playcount // Increase playcount
@ -159,7 +149,7 @@ export default {
* @param {Function} cb * @param {Function} cb
*/ */
scrobble(song, cb = null) { scrobble(song, cb = null) {
if (!sharedStore.state.useLastfm || !userStore.current().preferences.lastfm_session_key) { if (!window.useLastfm || !userStore.current().preferences.lastfm_session_key) {
return; return;
} }

View file

@ -15,19 +15,10 @@ export default {
/** /**
* Init the store. * Init the store.
*
* @param object data The data object that contain the users array.
* Mostly for DI and testing purpose.
* For production, this data is retrieved from the shared store.
*
*/ */
init(data = null) { init(users, currentUser) {
if (!data) { this.state.users = users;
data = sharedStore.state; this.state.current = currentUser;
}
this.state.users = data.users;
this.state.current = data.currentUser;
// Set the avatar for each of the users… // Set the avatar for each of the users…
_.each(this.state.users, this.setAvatar); _.each(this.state.users, this.setAvatar);
@ -36,6 +27,11 @@ export default {
this.setAvatar(); this.setAvatar();
}, },
/**
* Get all users.
*
* @return {Array}
*/
all() { all() {
return this.state.users; return this.state.users;
}, },
@ -43,9 +39,9 @@ export default {
/** /**
* Get a user by his ID * Get a user by his ID
* *
* @param integer id * @param {Integer} id
* *
* @return object * @return {Object}
*/ */
byId(id) { byId(id) {
return _.find(this.state.users, {id}); return _.find(this.state.users, {id});
@ -65,7 +61,7 @@ export default {
/** /**
* Set a user's avatar using Gravatar's service. * Set a user's avatar using Gravatar's service.
* *
* @param object user The user. If null, the current user. * @param {Object} user The user. If null, the current user.
*/ */
setAvatar(user = null) { setAvatar(user = null) {
if (!user) { if (!user) {
@ -78,7 +74,7 @@ export default {
/** /**
* Update the current user's profile. * Update the current user's profile.
* *
* @param string password Can be an empty string if the user is not changing his password. * @param {String} password Can be an empty string if the user is not changing his password.
*/ */
updateProfile(password = null, cb = null) { updateProfile(password = null, cb = null) {
http.put('me', { http.put('me', {
@ -98,10 +94,10 @@ export default {
/** /**
* Stores a new user into the database. * Stores a new user into the database.
* *
* @param string name * @param {String} name
* @param string email * @param {String} email
* @param string password * @param {String} password
* @param function cb * @param {Function} cb
*/ */
store(name, email, password, cb = null) { store(name, email, password, cb = null) {
http.post('user', { name, email, password }, user => { http.post('user', { name, email, password }, user => {

View file

@ -9,7 +9,7 @@ describe('stores/song', () => {
beforeEach(() => { beforeEach(() => {
// This is ugly and not very "unit," but anyway. // This is ugly and not very "unit," but anyway.
albumStore.init(artists); albumStore.init(artists);
songStore.init(albumStore.all(), interactions); songStore.init(albumStore.all());
}); });
describe('#init', () => { describe('#init', () => {
@ -46,7 +46,9 @@ describe('stores/song', () => {
}); });
}); });
describe('#setInteractionStats', () => { describe('#initInteractions', () => {
beforeEach(() => songStore.initInteractions(interactions));
it('correctly sets interaction status', () => { it('correctly sets interaction status', () => {
let song = songStore.byId('cb7edeac1f097143e65b1b2cde102482'); let song = songStore.byId('cb7edeac1f097143e65b1b2cde102482');
song.liked.should.be.true; song.liked.should.be.true;

View file

@ -4,7 +4,7 @@ import userStore from '../../stores/user';
import data from '../blobs/users'; import data from '../blobs/users';
describe('stores/user', () => { describe('stores/user', () => {
beforeEach(() => userStore.init(data)); beforeEach(() => userStore.init(data.users, data.currentUser));
describe('#init', () => { describe('#init', () => {
it('correctly sets data state', () => { it('correctly sets data state', () => {