Merge pull request #38 from jaedb/feature/playlist-pool

Asset index storage
This commit is contained in:
James Barnsley
2017-01-16 08:23:44 +13:00
committed by GitHub
29 changed files with 976 additions and 449 deletions

View File

@ -20,5 +20,7 @@ CACHEBUSTER=$(date | md5sum | cut -f1 -d' ')
echo -e Cachebusting js/css URLs $CACHEBUSTER
sed -i 's/app.js/app.min.js?'$CACHEBUSTER'/g' mopidy_iris/static/index.html
sed -i 's/app.css/app.min.css?'$CACHEBUSTER'/g' mopidy_iris/static/index.html
sed -i 's/app.js/app.js?'$CACHEBUSTER'/g' mopidy_iris/static/test.html
sed -i 's/app.css/app.css?'$CACHEBUSTER'/g' mopidy_iris/static/test.html
echo -e "\x1b[32;01m"Done!"\x1b[39;49;00m"

1
src/js/bootstrap.js vendored
View File

@ -65,6 +65,7 @@ var initialState = {
playlists: [],
tracks: []
},
library_playlists: [],
playlists: [],
notifications: []
}

View File

@ -16,14 +16,16 @@ export default class ArtistSentence extends React.Component{
<span className={ this.props.className ? this.props.className+" artist-sentence" : "artist-sentence" }>
{
this.props.artists.map( (artist, index) => {
if (!artist) return <span>-</span>
var separator = null;
if( index == this.props.artists.length - 2 ){
separator = ' and ';
}else if( index < this.props.artists.length - 2 ){
separator = ', ';
}
if( !artist.name ) artist.name = '-'
if( artist.uri ){
if (!artist.name){
var content = <span>-</span>
} else if (artist.uri){
var content = <Link className="artist" to={global.baseURL+'artist/'+artist.uri}>{ artist.name }</Link>
}else{
var content = <span>{ artist.name }</span>

View File

@ -30,9 +30,8 @@ class FollowButton extends React.Component{
render(){
if( !this.props.spotify_authorized || !this.props.uri ) return false
var item = this.props[helpers.uriType( this.props.uri )]
if( item.following === true ){
if( this.props.is_following === true ){
return <button className="tertiary large" onClick={ () => this.remove() }>{ this.props.removeText }</button>
}else{
return <button className="tertiary large" onClick={ () => this.add() }>{ this.props.addText }</button>
@ -42,11 +41,7 @@ class FollowButton extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
spotify_authorized: state.spotify.authorized,
artist: state.ui.artist,
user: state.ui.user,
album: state.ui.album,
playlist: state.ui.playlist
spotify_authorized: state.spotify.authorized
}
}

View File

@ -24,6 +24,32 @@ export default class GridItem extends React.Component{
return <Thumbnail size="medium" images={[]} />
}
renderSecondary(item){
var output = ''
switch (item.type){
case 'playlist':
return (
<span>
{ item.tracks_total ? item.tracks_total+' tracks' : null }
{ item.can_edit ? <FontAwesome name="edit" /> : null }
</span>
)
break
default:
return (
<span>
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null }
{ item.followers ? item.followers.total.toLocaleString()+' followers' : null }
</span>
)
}
return output
}
render(){
if( !this.props.item ) return null
@ -37,10 +63,7 @@ export default class GridItem extends React.Component{
{ this.renderThumbnail() }
<div className="name">{ item.name }</div>
<div className="secondary">
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null }
{ item.type == 'playlist' && item.tracks ? item.tracks.total+' tracks' : null }
{ item.followers ? item.followers.total.toLocaleString()+' followers' : null }
{ item.type == 'playlist' && item.can_edit ? <FontAwesome name="edit" /> : null }
{ this.renderSecondary(item) }
</div>
</div>
);

View File

@ -18,7 +18,7 @@ export default class List extends React.Component{
// make sure we haven't clicked a nested link (ie Artist name)
if( e.target.tagName.toLowerCase() !== 'a' ){
hashHistory.push( this.props.link_prefix + encodeURIComponent(uri) );
hashHistory.push( this.props.link_prefix + uri );
}
}

View File

@ -13,12 +13,7 @@ export default class Thumbnail extends React.Component{
shouldComponentUpdate(nextProps, nextState){
// no images at all, and we already know it
if(
typeof(nextProps.image) == 'undefined' &&
typeof(this.props.image) == 'undefined' &&
typeof(nextProps.images) == 'undefined' &&
typeof(this.props.images) == 'undefined'
) return false
if (!nextProps.image && !this.props.image && !nextProps.images && !this.props.images) return false
// image changed
if( !this.props.image && nextProps.image ) return true

View File

@ -245,7 +245,7 @@ class TrackList extends React.Component{
}
render(){
if( !this.state.tracks ) return null
if( !this.state.tracks || Object.prototype.toString.call(this.state.tracks) !== '[object Array]' ) return null
let self = this;
return (

View File

@ -93,6 +93,28 @@ export let getTrackIcon = function( track = false ){
}
/**
* Get a track's icon
* @param track object
* @return string
**/
export let flattenTracks = function( tracks ){
var flattened = []
for( var i = 0; i < tracks.length; i++ ){
flattened.push( Object.assign(
{},
tracks[i].track,
{
added_by: tracks[i].added_by,
added_at: tracks[i].added_at
}
))
}
return flattened
}
/**
@ -174,6 +196,18 @@ export let uriType = function( uri ){
}
/**
* Convert a raw URI into a object index-friendly format. Primarily used for loading local playlists
* @param $uri = string
* @return string
**/
export let indexFriendlyUri = function (uri){
var output = encodeURI(uri)
output = output.replace("'",'%27')
return output
}
/**
* Digest an array of objects, and pull into simple array of uris
*

View File

@ -27,11 +27,8 @@ const sendRequest = ( dispatch, getState, params ) => {
})
}
export function getArtist( artist, mbid = false ){
export function getArtist( uri, artist, mbid = false ){
return (dispatch, getState) => {
dispatch({ type: 'LASTFM_ARTIST_LOADED', data: false });
if( mbid ){
var params = 'method=artist.getInfo&mbid='+mbid
}else{
@ -43,8 +40,12 @@ export function getArtist( artist, mbid = false ){
response => {
if( response.artist ){
dispatch({
type: 'LASTFM_ARTIST_LOADED',
data: response.artist
type: 'ARTIST_LOADED',
uri: uri,
artist: {
images: response.artist.image,
bio: response.artist.bio
}
});
}
}

View File

@ -157,47 +157,47 @@ export function deletePlaylist( uri ){
}
}
export function getPlaylists(){
return { type: 'MOPIDY_PLAYLISTS' }
export function getLibraryPlaylists(){
return { type: 'MOPIDY_GET_LIBRARY_PLAYLISTS' }
}
export function getPlaylist( uri ){
return {
type: 'MOPIDY_PLAYLIST',
type: 'MOPIDY_GET_PLAYLIST',
data: { uri: uri }
}
}
export function getDirectory( uri ){
return {
type: 'MOPIDY_DIRECTORY',
data: { uri: uri }
}
}
export function getAlbum( uri ){
return {
type: 'MOPIDY_ALBUM',
type: 'MOPIDY_GET_DIRECTORY',
data: { uri: uri }
}
}
export function getArtist( uri ){
return {
type: 'MOPIDY_ARTIST',
type: 'MOPIDY_GET_ARTIST',
data: { uri: uri }
}
}
export function getArtists(){
return {
type: 'MOPIDY_ARTISTS'
type: 'MOPIDY_GET_ARTISTS'
}
}
export function getAlbum( uri ){
return {
type: 'MOPIDY_GET_ALBUM',
data: { uri: uri }
}
}
export function getAlbums(){
return {
type: 'MOPIDY_ALBUMS'
type: 'MOPIDY_GET_ALBUMS'
}
}

View File

@ -282,57 +282,75 @@ const MopidyMiddleware = (function(){
})
break;
case 'MOPIDY_PLAYLISTS':
/**
* =============================================================== PLAYLIST(S) ==========
* ======================================================================================
**/
case 'MOPIDY_GET_LIBRARY_PLAYLISTS':
instruct( socket, store, 'playlists.asList' )
.then( response => {
for( var i = 0; i < response.length; i++ ){
if( response[i].uri.startsWith('m3u:')) response[i] = Object.assign({}, response[i], { can_edit: true })
}
store.dispatch({ type: 'MOPIDY_PLAYLISTS_LOADED', data: response });
// TODO: dispatch an action to fetch extra data for each playlist
// this will then trigger an PLAYLIST_REFRESHED action to update a specific store item
// drop in our URI list
var playlist_uris = helpers.asURIs(response)
store.dispatch({ type: 'LIBRARY_PLAYLISTS_LOADED', uris: playlist_uris });
// get the full playlist objects
for (var i = 0; i < response.length; i++ ){
instruct( socket, store, 'playlists.lookup', { uri: response[i].uri })
.then( response => {
var playlist = Object.assign(
{},
{
type: 'playlist',
name: response.name,
uri: response.uri,
last_modified: response.last_modified,
can_edit: (response.uri.startsWith('m3u:')),
tracks_total: ( response.tracks ? response.tracks.length : 0 )
}
)
store.dispatch({
type: 'PLAYLIST_LOADED',
uri: playlist.uri,
playlist: playlist
})
})
}
})
break;
case 'MOPIDY_CREATE_PLAYLIST':
instruct( socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme })
.then( response => {
// re-load our global playlists
store.dispatch({ type: 'MOPIDY_PLAYLISTS' });
});
break
case 'MOPIDY_PLAYLIST':
case 'MOPIDY_GET_PLAYLIST':
store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: false });
instruct( socket, store, 'playlists.lookup', action.data )
.then( response => {
var playlist = Object.assign(
{},
{
images: []
},
response,
{
tracks: ( response.tracks ? response.tracks : [] ),
tracks_total: ( response.tracks ? response.tracks.length : 0 )
type: 'playlist',
tracks: ( response.tracks ? response.tracks : null ),
tracks_total: ( response.tracks ? response.tracks.length : null )
}
)
// tracks? get the full track objects
if( playlist.tracks.length > 0 ) store.dispatch({ type: 'MOPIDY_PLAYLIST_TRACKS', tracks: playlist.tracks })
if( playlist.tracks.length > 0 ) store.dispatch({ type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', tracks: playlist.tracks, uri: response.uri })
store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: playlist })
store.dispatch({
type: 'PLAYLIST_LOADED',
uri: playlist.uri,
playlist: playlist
})
})
break;
case 'MOPIDY_PLAYLIST_TRACKS':
case 'MOPIDY_RESOLVE_PLAYLIST_TRACKS':
var tracks = Object.assign([], action.tracks)
var uris = [];
for( var i = 0; i < tracks.length; i++ ){
uris.push( tracks[i].uri );
}
var uris = helpers.asURIs(tracks)
instruct( socket, store, 'library.lookup', { uris: uris } )
.then( response => {
@ -357,13 +375,17 @@ const MopidyMiddleware = (function(){
}
}
store.dispatch({ type: 'PLAYLIST_TRACKS_LOADED', tracks: tracks })
store.dispatch({
type: 'PLAYLIST_TRACKS',
tracks: tracks,
uri: action.uri
})
})
break
case 'MOPIDY_ADD_PLAYLIST_TRACKS':
instruct( socket, store, 'playlists.lookup', { uri: action.playlist_uri })
instruct( socket, store, 'playlists.lookup', { uri: action.uri })
.then( response => {
var tracks = [];
for( var i = 0; i < action.tracks_uris.length; i++ ){
@ -382,7 +404,7 @@ const MopidyMiddleware = (function(){
instruct( socket, store, 'playlists.save', { playlist: playlist } )
.then( response => {
store.dispatch({ type: 'PLAYLIST_TRACKS_ADDED', tracks_uris: action.tracks_uris });
store.dispatch({ type: 'PLAYLIST_TRACKS_ADDED', uri: action.uri, tracks_uris: action.tracks_uris });
})
});
break
@ -396,7 +418,7 @@ const MopidyMiddleware = (function(){
var indexes = Object.assign([], action.tracks_indexes)
indexes.sort(descending);
instruct( socket, store, 'playlists.lookup', { uri: action.playlist_uri })
instruct( socket, store, 'playlists.lookup', { uri: action.uri })
.then( response => {
var playlist = Object.assign({}, response)
for( var i = 0; i < indexes.length; i++ ){
@ -404,7 +426,7 @@ const MopidyMiddleware = (function(){
}
instruct( socket, store, 'playlists.save', { playlist: playlist } )
.then( response => {
store.dispatch({ type: 'PLAYLIST_TRACKS_REMOVED', tracks_indexes: action.tracks_indexes });
store.dispatch({ type: 'PLAYLIST_TRACKS_REMOVED', uri: action.uri, tracks_indexes: action.tracks_indexes });
})
});
break
@ -415,7 +437,11 @@ const MopidyMiddleware = (function(){
var playlist = Object.assign({}, response, { name: action.name })
instruct( socket, store, 'playlists.save', { playlist: playlist } )
.then( response => {
store.dispatch({ type: 'PLAYLIST_UPDATED', playlist: playlist })
store.dispatch({
type: 'PLAYLIST_UPDATED',
uri: action.uri,
playlist: playlist
})
})
});
break
@ -454,23 +480,50 @@ const MopidyMiddleware = (function(){
instruct( socket, store, 'playlists.save', { playlist: playlist } )
.then( response => {
// and now re-render our full track references
store.dispatch({ type: 'MOPIDY_PLAYLIST_TRACKS', tracks: playlist.tracks })
store.dispatch({ type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', tracks: playlist.tracks, uri: playlist.uri })
})
});
break
case 'MOPIDY_CREATE_PLAYLIST':
instruct( socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme })
.then( response => {
// re-load our global playlists
//store.dispatch({ type: 'MOPIDY_GET_PLAYLISTS' });
});
break
case 'MOPIDY_DELETE_PLAYLIST':
instruct( socket, store, 'playlists.delete', { uri: action.uri })
.then( response => {
// re-load our global playlists
store.dispatch({ type: 'MOPIDY_PLAYLISTS' });
// store.dispatch({ type: 'MOPIDY_PLAYLISTS' });
});
break
case 'MOPIDY_ALBUM':
//store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: false });
/**
* =============================================================== ALBUM(S) =============
* ======================================================================================
**/
case 'MOPIDY_GET_ALBUMS':
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=album' } )
.then( response => {
store.dispatch({
type: 'ALBUMS_LOADED',
albums: response
});
store.dispatch({
type: 'LOCAL_ALBUMS_LOADED',
uris: helpers.asURIs(response)
});
})
break;
case 'MOPIDY_GET_ALBUM':
instruct( socket, store, 'library.lookup', action.data )
.then( response => {
var album = Object.assign(
@ -522,45 +575,92 @@ const MopidyMiddleware = (function(){
}
}
store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: album });
store.dispatch({
type: 'ALBUM_LOADED',
uri: album.uri,
album: album
});
})
})
break;
case 'MOPIDY_ARTIST':
store.dispatch({ type: 'MOPIDY_ARTIST_LOADED', data: false });
instruct( socket, store, 'library.lookup', action.data )
.then( response => {
var artist = response[0].artists[0];
if( !artist.images ) artist.images = [];
if( !artist.albums ) artist.albums = [];
artist.tracks = response.slice(0,10);
for( var i = 0; i < response.length; i++ ){
var album = response[i].album;
/**
* =============================================================== ARTIST(S) ============
* ======================================================================================
**/
function getByURI( albumToCheck ){
return album.uri == albumToCheck.uri
}
var existingAlbum = artist.albums.find(getByURI);
if( !existingAlbum ){
artist.albums.push(album)
}
}
// load artwork from LastFM
if( artist.images.length <= 0 ){
if( artist.musicbrainz_id ){
store.dispatch( lastfmActions.getArtist( false, artist.musicbrainz_id ) )
}else{
store.dispatch( lastfmActions.getArtist( artist.name ) )
}
}
store.dispatch({ type: 'MOPIDY_ARTIST_LOADED', data: artist });
case 'MOPIDY_GET_ARTISTS':
store.dispatch({ type: 'LOCAL_ARTISTS_LOADED', data: false });
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
.then( response => {
store.dispatch({
type: 'ARTISTS_LOADED',
artists: response
});
store.dispatch({
type: 'LOCAL_ARTISTS_LOADED',
uris: helpers.asURIs(response)
});
})
break;
case 'MOPIDY_GET_ARTIST':
instruct( socket, store, 'library.lookup', action.data )
.then( response => {
var albums = []
for( var i = 0; i < response.length; i++ ){
var album = response[i].album;
if (album){
function getByURI( albumToCheck ){
return album.uri == albumToCheck.uri
}
var existingAlbum = albums.find(getByURI);
if( !existingAlbum ){
albums.push(album)
}
}
}
if (albums){
store.dispatch({
type: 'ALBUMS_LOADED',
albums: albums
})
}
var artist = Object.assign(
{},
response[0].artists[0],
{
albums_uris: helpers.asURIs(albums),
tracks: response.slice(0,10)
}
)
store.dispatch({
type: 'ARTIST_LOADED',
uri: artist.uri,
artist: artist
});
// load artwork from LastFM
if( !artist.images || artist.images.length <= 0 ){
if( artist.musicbrainz_id ){
store.dispatch( lastfmActions.getArtist( artist.uri, false, artist.musicbrainz_id ) )
}else{
store.dispatch( lastfmActions.getArtist( artist.uri, artist.name.replace('&','and') ) )
}
}
})
break;
/**
* =============================================================== LOCAL ================
* ======================================================================================
**/
case 'MOPIDY_DIRECTORY':
store.dispatch({ type: 'MOPIDY_DIRECTORY_LOADED', data: false });
instruct( socket, store, 'library.browse', action.data )
@ -569,22 +669,6 @@ const MopidyMiddleware = (function(){
})
break;
case 'MOPIDY_ARTISTS':
store.dispatch({ type: 'MOPIDY_ARTISTS_LOADED', data: false });
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
.then( response => {
store.dispatch({ type: 'MOPIDY_ARTISTS_LOADED', data: response });
})
break;
case 'MOPIDY_ALBUMS':
store.dispatch({ type: 'MOPIDY_ALBUMS_LOADED', data: false });
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=album' } )
.then( response => {
store.dispatch({ type: 'MOPIDY_ALBUMS_LOADED', data: response });
})
break;
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);

View File

@ -262,9 +262,29 @@ export function getFeaturedPlaylists(){
sendRequest( dispatch, getState, 'browse/featured-playlists?timestamp='+timestamp+'&country='+getState().spotify.country+'&limit=50&locale='+getState().spotify.locale )
.then( response => {
var playlists = []
for (var i = 0; i < response.playlists.items.length; i++){
playlists.push(Object.assign(
{},
response.playlists.items[i],
{
can_edit: (getState().spotify.me && response.playlists.items[i].owner.id == getState().spotify.me.id),
tracks_total: response.playlists.items[i].tracks.total
}
))
}
dispatch({
type: 'PLAYLISTS_LOADED',
playlists: playlists
});
dispatch({
type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED',
data: response
data: {
message: response.message,
playlists: helpers.asURIs(response.playlists.items)
}
});
});
}
@ -290,52 +310,69 @@ export function getCategory( id ){
dispatch({ type: 'SPOTIFY_CATEGORY_LOADED', data: false });
sendRequest( dispatch, getState, 'browse/categories/'+id+'?country='+getState().spotify.country+'&locale='+getState().spotify.locale )
$.when(
sendRequest( dispatch, getState, 'browse/categories/'+id+'?country='+getState().spotify.country+'&locale='+getState().spotify.locale ),
sendRequest( dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale )
).then( ( category_response, playlists_response ) => {
for (var i = 0; i < playlists_response.playlists.items.length; i++){
var playlist = Object.assign(
{},
playlists_response.playlists.items[i],
{
tracks: null,
tracks_more: null,
tracks_total: playlists_response.playlists.items[i].tracks.total
}
)
dispatch({
type: 'PLAYLIST_LOADED',
playlist: playlist
});
}
var category = Object.assign(
{},
category_response,
{
items: false,
playlists: helpers.asURIs(playlists_response.playlists.items),
playlists_more: playlists_response.playlists.next,
playlists_total: playlists_response.playlists.total
}
)
dispatch({
type: 'SPOTIFY_CATEGORY_LOADED',
data: category
});
});
}
}
export function getNewReleases(){
return (dispatch, getState) => {
sendRequest( dispatch, getState, 'browse/new-releases?country='+getState().spotify.country+'&limit=50' )
.then( response => {
dispatch({
type: 'SPOTIFY_CATEGORY_LOADED',
type: 'SPOTIFY_NEW_RELEASES_LOADED',
data: response
});
});
}
}
export function getCategoryPlaylists( id ){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', data: false });
sendRequest( dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale )
.then( response => {
dispatch({
type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED',
data: response.playlists
});
});
}
}
export function getNewReleases(){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_NEW_RELEASES_LOADED', data: false });
sendRequest( dispatch, getState, 'browse/new-releases?country='+getState().spotify.country+'&limit=50' )
.then( response => {
dispatch({
type: 'SPOTIFY_NEW_RELEASES_LOADED',
data: response.albums
});
});
}
}
export function getURL( url, action_name ){
export function getURL( url, action_name, uri = false ){
return (dispatch, getState) => {
sendRequest( dispatch, getState, url )
.then( response => {
dispatch({
type: action_name,
uri: uri,
data: response
});
});
@ -366,8 +403,6 @@ export function getSearchResults( query, type = 'album,artist,playlist,track', l
export function following(uri, method = 'GET'){
return (dispatch, getState) => {
dispatch({ type: 'FOLLOWING_LOADING' });
if( method == 'PUT' ) var is_following = true
if( method == 'DELETE' ) var is_following = false
@ -411,7 +446,8 @@ export function following(uri, method = 'GET'){
if( response ) is_following = response
if( typeof(is_following) === 'object' ) is_following = is_following[0]
dispatch({
type: asset_name.toUpperCase()+'_FOLLOWING_LOADED',
type: 'SPOTIFY_'+asset_name.toUpperCase()+'_FOLLOWING_LOADED',
uri: uri,
is_following: is_following
});
});
@ -547,9 +583,6 @@ export function getDiscover(){
export function getArtist( uri ){
return (dispatch, getState) => {
// flush out the previous store value
dispatch({ type: 'SPOTIFY_ARTIST_LOADED', data: false });
var artist = {};
// get both the artist and the top tracks
@ -567,19 +600,29 @@ export function getArtist( uri ){
sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/related-artists' )
.then( response => {
Object.assign(artist, { related_artists: response.artists });
}),
sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums' )
.then( response => {
Object.assign(artist, { albums: response.items, albums_more: response.next });
dispatch({
type: 'ARTISTS_LOADED',
artists: response.artists
});
Object.assign(artist, { related_artists_uris: helpers.asURIs(response.artists) });
})
).then( () => {
dispatch({
type: 'SPOTIFY_ARTIST_LOADED',
data: artist
type: 'ARTIST_LOADED',
uri: artist.uri,
artist: artist
});
// now go get our artist albums
sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums' )
.then( response => {
dispatch({
type: 'SPOTIFY_ARTIST_ALBUMS_LOADED',
data: response,
uri: uri
});
})
});
}
}
@ -587,9 +630,6 @@ export function getArtist( uri ){
export function getArtists( uris ){
return (dispatch, getState) => {
// flush out the previous store value
dispatch({ type: 'SPOTIFY_ARTISTS_LOADED', data: false });
// now get all the artists for this album (full objects)
var ids = '';
for( var i = 0; i < uris.length; i++ ){
@ -599,10 +639,21 @@ export function getArtists( uris ){
sendRequest( dispatch, getState, 'artists/?ids='+ids )
.then( response => {
dispatch({
type: 'SPOTIFY_ARTISTS_LOADED',
data: response
});
for (var i = i; i < response.length; i++){
var artist = response
for (var i = 0; i < artist.albums.length; i++){
dispatch({
type: 'ALBUM_LOADED',
album: artist.albums[i]
});
}
artist.albums = helpers.asURIs(artist.albums)
artist.albums_more = artist.albums.next
dispatch({
type: 'ARTIST_LOADED',
artist: artist
});
}
});
}
}
@ -610,15 +661,12 @@ export function getArtists( uris ){
export function getLibraryArtists(){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', data: false });
sendRequest( dispatch, getState, 'me/following?type=artist&limit=50' )
.then( response => {
dispatch({
type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED',
data: response
});
})
});
}
}
@ -675,29 +723,54 @@ export function getUser( uri ){
export function getAlbum( uri ){
return (dispatch, getState) => {
// flush out the previous store value
dispatch({ type: 'SPOTIFY_ALBUM_LOADED', data: false });
// get the album
sendRequest( dispatch, getState, 'albums/'+ helpers.getFromUri('albumid', uri) )
.then( response => {
var album = response
// dispatch our loaded artists (simple objects)
dispatch({
type: 'ARTISTS_LOADED',
artists: response.artists
});
var album = Object.assign(
{},
response,
{
artists_uris: helpers.asURIs(response.artists),
tracks: response.tracks.items,
tracks_more: response.tracks.next,
tracks_total: response.tracks.total
}
)
// add our album to all the tracks
for (var i = 0; i < album.tracks.length; i++){
album.tracks[i].album = {
name: album.name,
uri: album.uri
}
}
dispatch({
type: 'ALBUM_LOADED',
uri: album.uri,
album: album
});
// now get all the artists for this album (full objects)
// we do this to get the artist artwork
var artist_ids = [];
for( var i = 0; i < album.artists.length; i++ ){
artist_ids.push( helpers.getFromUri( 'artistid', album.artists[i].uri ) )
for( var i = 0; i < response.artists.length; i++ ){
artist_ids.push( helpers.getFromUri( 'artistid', response.artists[i].uri ) )
}
// get all album artists
// get all album artists as full objects
sendRequest( dispatch, getState, 'artists/?ids='+artist_ids )
.then( response => {
Object.assign(album, { artists: response.artists })
dispatch({
type: 'SPOTIFY_ALBUM_LOADED',
data: album
type: 'ARTISTS_LOADED',
artists: response.artists
});
});
@ -708,14 +781,14 @@ export function getAlbum( uri ){
export function getLibraryAlbums(){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED', data: false });
dispatch({ type: 'LIBRARY_ALBUMS_LOADED', uris: false });
sendRequest( dispatch, getState, 'me/albums?limit=40' )
.then( response => {
dispatch({
type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED',
data: response
});
})
});
}
}
@ -774,15 +847,25 @@ export function savePlaylist( uri, name, is_public ){
export function getPlaylist( uri ){
return (dispatch, getState) => {
// flush out the previous store value
dispatch({ type: 'SPOTIFY_PLAYLIST_LOADED', data: false });
// get the main playlist object
sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().spotify.country )
.then( response => {
var playlist = Object.assign(
{},
response,
{
can_edit: (getState().spotify.me && response.owner.id == getState().spotify.me.id),
tracks: helpers.flattenTracks(response.tracks.items),
tracks_more: response.tracks.next,
tracks_total: response.tracks.total
}
)
dispatch({
type: 'SPOTIFY_PLAYLIST_LOADED',
data: response
type: 'PLAYLIST_LOADED',
uri: playlist.uri,
playlist: playlist
})
})
}
@ -796,29 +879,15 @@ function loadNextPlaylistsBatch( dispatch, getState, playlists, lastResponse ){
loadNextPlaylistsBatch( dispatch, getState, playlists, response )
});
}else{
// check our editability of each playlist
// used to define what we can add tracks to
if( getState().spotify.authorized ){
for( var i = 0; i < playlists.length; i++ ){
if( playlists[i].owner.id == getState().spotify.me.id ){
playlists[i] = Object.assign({}, playlists[i], { can_edit: true })
}
}
}
dispatch({
type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED',
data: playlists
playlists: playlists
});
}
}
export function getAllLibraryPlaylists(){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', data: false });
sendRequest( dispatch, getState, 'me/playlists?limit=50' )
.then( response => {
loadNextPlaylistsBatch( dispatch, getState, response.items, response )
@ -842,12 +911,13 @@ export function toggleFollowingPlaylist( uri, method ){
}
}
export function addTracksToPlaylist( playlist_uri, tracks_uris ){
export function addTracksToPlaylist( uri, tracks_uris ){
return (dispatch, getState) => {
sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',playlist_uri) + '/playlists/'+ helpers.getFromUri('playlistid',playlist_uri) + '/tracks', 'POST', { uris: tracks_uris } )
sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) + '/playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'POST', { uris: tracks_uris } )
.then( response => {
dispatch({
type: 'PLAYLIST_TRACKS_ADDED',
uri: uri,
tracks_uris: tracks_uris,
snapshot_id: response.snapshot_id
});
@ -861,6 +931,7 @@ export function deleteTracksFromPlaylist( uri, snapshot_id, tracks_indexes ){
.then( response => {
dispatch({
type: 'PLAYLIST_TRACKS_REMOVED',
uri: uri,
tracks_indexes: tracks_indexes,
snapshot_id: response.snapshot_id
});
@ -874,6 +945,7 @@ export function reorderPlaylistTracks( uri, range_start, range_length, insert_be
.then( response => {
dispatch({
type: 'PLAYLIST_TRACKS_REORDERED',
uri: uri,
range_start: range_start,
range_length: range_length,
insert_before: insert_before,

View File

@ -18,8 +18,6 @@ const SpotifyMiddleware = (function(){
break
case 'SPOTIFY_CREATE_PLAYLIST':
var playlist = state.ui.playlist
if( !store.getState().spotify.authorized ){
store.dispatch( uiActions.createNotification( "Must be logged in to Spotify to do that", 'bad' ) )
return
@ -28,7 +26,7 @@ const SpotifyMiddleware = (function(){
break
case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS':
var playlist = state.ui.playlist
var playlist = state.ui.playlists[action.uri]
if( !store.getState().spotify.authorized ){
store.dispatch( uiActions.createNotification( "Must be logged in to Spotify to do that", 'bad' ) )
@ -95,6 +93,135 @@ const SpotifyMiddleware = (function(){
}
break
case 'SPOTIFY_PLAYLIST_FOLLOWING_LOADED':
store.dispatch({
type: 'PLAYLIST_LOADED',
uri: action.uri,
playlist: {
is_following: action.is_following
}
});
break
case 'SPOTIFY_ALBUM_FOLLOWING_LOADED':
store.dispatch({
type: 'ALBUM_LOADED',
uri: action.uri,
album: {
is_following: action.is_following
}
});
break
case 'SPOTIFY_ARTIST_FOLLOWING_LOADED':
store.dispatch({
type: 'ARTIST_LOADED',
uri: action.uri,
artist: {
is_following: action.is_following
}
});
break
case 'SPOTIFY_NEW_RELEASES_LOADED':
store.dispatch({
type: 'ALBUMS_LOADED',
albums: action.data.albums.items
});
store.dispatch({
type: 'NEW_RELEASES_LOADED',
uris: helpers.asURIs(action.data.albums.items),
more: action.data.albums.next,
total: action.data.albums.total
});
break
case 'SPOTIFY_ARTIST_ALBUMS_LOADED':
store.dispatch({
type: 'ALBUMS_LOADED',
albums: action.data.items
});
store.dispatch({
type: 'ARTIST_ALBUMS_LOADED',
uri: action.uri,
uris: helpers.asURIs(action.data.items),
more: action.data.next,
total: action.data.total
});
break
case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
var playlists = []
for( var i = 0; i < action.playlists.length; i++ ){
var playlist = Object.assign(
{},
action.playlists[i],
{
can_edit: (store.getState().spotify.authorized && store.getState().spotify.me && action.playlists[i].owner.id == store.getState().spotify.me.id),
tracks_total: action.playlists[i].tracks.total
}
)
// remove our tracklist. It'll overwrite any full records otherwise
delete playlist.tracks
playlists.push(playlist)
}
store.dispatch({
type: 'PLAYLISTS_LOADED',
playlists: playlists
});
store.dispatch({
type: 'LIBRARY_PLAYLISTS_LOADED',
uris: helpers.asURIs(playlists)
});
break
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED':
store.dispatch({
type: 'ARTISTS_LOADED',
artists: action.data.artists.items
});
store.dispatch({
type: 'LIBRARY_ARTISTS_LOADED',
uris: helpers.asURIs(action.data.artists.items),
more: action.data.artists.next,
total: action.data.artists.total
});
break
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
var albums = []
for (var i = 0; i < action.data.items.length; i++){
albums.push(
Object.assign(
{},
action.data.items[i].album,
{
added_at: action.data.items[i].added_at,
tracks: action.data.items[i].album.tracks.items,
tracks_next: action.data.items[i].album.tracks.tracks_next,
tracks_total: action.data.items[i].album.tracks.tracks_total
}
)
)
}
store.dispatch({
type: 'ALBUMS_LOADED',
albums: albums
});
store.dispatch({
type: 'LIBRARY_ALBUMS_LOADED',
uris: helpers.asURIs(albums),
more: action.data.next,
total: action.data.total
});
break
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);

View File

@ -60,7 +60,7 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_ME_LOADED':
return Object.assign({}, spotify, { me: action.data })
/*
case 'SPOTIFY_ARTISTS_LOADED':
if( !action.data ) return Object.assign({}, spotify)
return Object.assign({}, spotify, {
@ -79,7 +79,7 @@ export default function reducer(spotify = {}, action){
return Object.assign({}, spotify, {
library_artists: [ ...spotify.library_artists, ...action.data.artists.items ],
library_artists_more: action.data.artists.next
})
})*/
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
if( !action.data ) return Object.assign({}, spotify)
@ -156,9 +156,7 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_CATEGORY_LOADED':
return Object.assign({}, spotify, { category: action.data })
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED':
return Object.assign({}, spotify, { category_playlists: action.data })
// TODO: update this
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE':
return Object.assign({}, spotify, { category_playlists: {
href: action.data.href,

View File

@ -147,39 +147,39 @@ export function createPlaylist( scheme, name, is_public = false ){
return false
}
export function removeTracksFromPlaylist( playlist_uri, tracks_indexes ){
switch( helpers.uriSource( playlist_uri ) ){
export function removeTracksFromPlaylist( uri, tracks_indexes ){
switch( helpers.uriSource( uri ) ){
case 'spotify':
return {
type: 'SPOTIFY_REMOVE_PLAYLIST_TRACKS',
playlist_uri: playlist_uri,
uri: uri,
tracks_indexes: tracks_indexes
}
case 'm3u':
return {
type: 'MOPIDY_REMOVE_PLAYLIST_TRACKS',
playlist_uri: playlist_uri,
uri: uri,
tracks_indexes: tracks_indexes
}
}
}
export function addTracksToPlaylist( playlist_uri, tracks_uris ){
switch( helpers.uriSource( playlist_uri ) ){
export function addTracksToPlaylist( uri, tracks_uris ){
switch( helpers.uriSource( uri ) ){
case 'spotify':
return {
type: 'SPOTIFY_ADD_PLAYLIST_TRACKS',
playlist_uri: playlist_uri,
uri: uri,
tracks_uris: tracks_uris
}
case 'm3u':
return {
type: 'MOPIDY_ADD_PLAYLIST_TRACKS',
playlist_uri: playlist_uri,
uri: uri,
tracks_uris: tracks_uris
}
}

View File

@ -114,9 +114,6 @@ export default function reducer(ui = {}, action){
current_track: current_track
});
case 'FOLLOWING_LOADING':
return Object.assign({}, ui, { following_loading: true })
case 'RADIO':
case 'START_RADIO':
return Object.assign({}, ui, { seeds_resolved: false }, { radio: action.data.radio })
@ -132,36 +129,71 @@ export default function reducer(ui = {}, action){
* Albums
**/
case 'MOPIDY_ALBUM_LOADED':
if( !action.data ) return Object.assign({}, ui, { album: false })
return Object.assign({}, ui, { album: action.data });
case 'ALBUM_LOADED':
var albums = Object.assign([], ui.albums)
case 'LASTFM_ALBUM_LOADED':
if( !action.data.image ) return ui
if (albums[action.uri]){
var album = Object.assign({}, albums[action.uri], action.album)
}else{
var album = Object.assign({}, action.album)
}
var album = Object.assign({}, ui.album, { images: action.data.image })
return Object.assign({}, ui, { album: album });
albums[action.uri] = album
return Object.assign({}, ui, { albums: albums });
case 'SPOTIFY_ALBUM_LOADED':
if( !action.data ) return Object.assign({}, ui, { album: false })
case 'ALBUMS_LOADED':
console.log(action)
var albums = Object.assign([], ui.albums)
var album = Object.assign({}, { images: [] }, action.data, {
tracks: action.data.tracks.items,
tracks_total: action.data.tracks.total,
tracks_more: action.data.tracks.next
})
return Object.assign({}, ui, { album: album })
for (var i = 0; i < action.albums.length; i++){
var album = action.albums[i]
if (typeof(albums[album.uri]) !== 'undefined'){
artist = Object.assign({}, albums[album.uri], album)
}
albums[album.uri] = album
}
case 'SPOTIFY_ALBUM_LOADED_MORE':
var album = Object.assign({}, ui.album, {
tracks: [ ...ui.album.tracks, ...action.data.items ],
tracks_more: action.data.next
})
return Object.assign({}, ui, { album: album });
return Object.assign({}, ui, { albums: albums });
case 'ALBUM_FOLLOWING_LOADED':
var album = Object.assign({}, ui.album, { following: action.is_following })
return Object.assign({}, ui, { album: album, following_loading: false });
case 'LIBRARY_ALBUMS_LOADED':
if (!action.uris){
return Object.assign({}, ui, {
library_albums: null,
library_albums_more: null,
library_albums_total: null
});
}
var library_albums = []
if (ui.library_albums) library_albums = Object.assign([], ui.library_albums)
return Object.assign({}, ui, {
library_albums: [...library_albums, ...action.uris],
library_albums_more: action.more,
library_albums_total: action.total
});
case 'LOCAL_ALBUMS_LOADED':
if (!action.uris) return Object.assign({}, ui, { local_albums: null });
return Object.assign({}, ui, { local_albums: action.uris });
case 'NEW_RELEASES_LOADED':
if (!action.uris){
return Object.assign({}, ui, {
new_releases: null,
new_releases_more: null,
new_releases_total: null
});
}
var new_releases = []
if (ui.new_releases) new_releases = Object.assign([], ui.new_releases)
return Object.assign({}, ui, {
new_releases: [...new_releases, ...action.uris],
new_releases_more: action.more,
new_releases_total: action.total
});
@ -169,34 +201,73 @@ export default function reducer(ui = {}, action){
* Artists
**/
case 'MOPIDY_ARTIST_LOADED':
if( !action.data ) return Object.assign({}, ui, { artist: false })
return Object.assign({}, ui, { artist: action.data })
case 'ARTIST_LOADED':
console.log(action)
var artists = Object.assign([], ui.artists)
case 'LASTFM_ARTIST_LOADED':
if( !action.data.image ) return ui
if (artists[action.uri]){
// if we've already got images, delete our new ones
// this is to prevent LastFM overwriting Spotify images
if (artists[action.uri].images) delete action.artist.images
var artist = Object.assign({}, artists[action.uri], action.artist)
}else{
var artist = Object.assign({}, action.artist)
}
// if we already have images, don't overwrite them
var images = ui.artist.images
if( images.length <= 0 ) images = action.data.image
var artist = Object.assign({}, ui.artist, { images: images, bio: action.data.bio, listeners: parseInt(action.data.stats.listeners), on_tour: action.data.ontour }, )
return Object.assign({}, ui, { artist: artist });
artists[action.uri] = artist
return Object.assign({}, ui, { artists: artists });
case 'SPOTIFY_ARTIST_LOADED':
if( !action.data ) return Object.assign({}, ui, { artist: false })
return Object.assign({}, ui, { artist: action.data })
case 'ARTISTS_LOADED':
var artists = Object.assign([], ui.artists)
case 'SPOTIFY_ARTIST_ALBUMS_LOADED_MORE':
var artist = Object.assign({}, ui.artist, {
albums: [ ...ui.artist.albums, ...action.data.items ],
albums_more: action.data.next
})
return Object.assign({}, ui, { artist: artist });
for (var i = 0; i < action.artists.length; i++){
var artist = action.artists[i]
if (typeof(artists[artist.uri]) !== 'undefined'){
artist = Object.assign({}, artists[artist.uri], artist)
}
artists[artist.uri] = artist
}
case 'ARTIST_FOLLOWING_LOADED':
var artist = Object.assign({}, ui.artist, { following: action.is_following })
return Object.assign({}, ui, { artist: artist, following_loading: false });
return Object.assign({}, ui, { artists: artists });
case 'ARTIST_ALBUMS_LOADED':
var artists = Object.assign([], ui.artists)
var albums_uris = []
if (artists[action.uri].albums_uris) albums_uris = artists[action.uri].albums_uris
var artist = Object.assign(
{},
artists[action.uri],
{
albums_uris: [...albums_uris, ...action.uris],
albums_more: action.more,
albums_total: action.total
}
)
artists[action.uri] = artist
return Object.assign({}, ui, { artists: artists });
case 'LIBRARY_ARTISTS_LOADED':
if (!action.uris){
return Object.assign({}, ui, {
library_artists: null,
library_artists_more: null,
library_artists_total: null
});
}
var library_artists = []
if (ui.library_artists) library_artists = Object.assign([], ui.library_artists)
return Object.assign({}, ui, {
library_artists: [...library_artists, ...action.uris],
library_artists_more: action.more,
library_artists_total: action.total
});
case 'LOCAL_ARTISTS_LOADED':
if (!action.uris) return Object.assign({}, ui, { local_artists: null });
return Object.assign({}, ui, { local_artists: action.uris });
/**
@ -223,74 +294,76 @@ export default function reducer(ui = {}, action){
* Playlists
**/
case 'PLAYLIST_LOADED':
case 'PLAYLIST_UPDATED':
var playlist = Object.assign({}, ui.playlist, action.playlist)
return Object.assign({}, ui, { playlist: playlist })
var playlists = Object.assign([], ui.playlists)
if (playlists[action.uri]){
var playlist = Object.assign({}, playlists[action.uri], action.playlist)
}else{
var playlist = Object.assign({}, action.playlist)
}
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
case 'PLAYLISTS_LOADED':
var playlists = Object.assign([], ui.playlists)
for (var i = 0; i < action.playlists.length; i++){
var playlist = action.playlists[i]
if (typeof(playlists[playlist.uri]) !== 'undefined'){
artist = Object.assign({}, playlists[playlist.uri], playlist)
}
playlists[playlist.uri] = playlist
}
return Object.assign({}, ui, { playlists: playlists });
case 'MOPIDY_PLAYLIST_LOADED':
if( !action.data ) return Object.assign({}, ui, { playlist: false })
return Object.assign({}, ui, { playlist: action.data })
case 'SPOTIFY_PLAYLIST_LOADED':
if( !action.data ) return Object.assign({}, ui, { playlist: false })
case 'PLAYLIST_LOADED_MORE_TRACKS':
var playlists = Object.assign([], ui.playlists)
var playlist = Object.assign(
{},
playlists[action.uri],
{
tracks: [...playlists[action.uri].tracks, ...helpers.flattenTracks(action.data.items)],
tracks_more: action.data.next,
tracks_total: action.data.total
}
)
var tracks = []
for( var i = 0; i < action.data.tracks.items.length; i++ ){
tracks.push( Object.assign(
{},
action.data.tracks.items[i].track,
{
added_by: action.data.tracks.items[i].added_by,
added_at: action.data.tracks.items[i].added_at
}
))
}
var playlist = Object.assign({}, action.data, {
tracks: tracks,
tracks_more: action.data.tracks.next,
tracks_total: action.data.tracks.total
})
return Object.assign({}, ui, { playlist: playlist });
case 'SPOTIFY_PLAYLIST_LOADED_MORE':
var tracks = []
for( var i = 0; i < action.data.items.length; i++ ){
tracks.push( Object.assign(
{},
action.data.items[i].track,
{
added_by: action.data.items[i].added_by,
added_at: action.data.items[i].added_at
}
))
}
var playlist = Object.assign({}, ui.playlist, {
tracks: [...ui.playlist.tracks, ...tracks],
tracks_more: action.data.next
})
return Object.assign({}, ui, { playlist: playlist });
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
case 'PLAYLIST_TRACKS_REMOVED':
var tracks = Object.assign([], ui.playlist.tracks)
var playlists = Object.assign([], ui.playlists)
var playlist = Object.assign({}, playlists[action.uri])
var tracks = Object.assign([], playlist.tracks)
var indexes = action.tracks_indexes.reverse()
for( var i = 0; i < indexes.length; i++ ){
tracks.splice( indexes[i], 1 )
}
var snapshot_id = null
if( action.snapshot_id ) snapshot_id = action.snapshot_id
var playlist = Object.assign({}, ui.playlist, { tracks: tracks, snapshot_id: snapshot_id })
return Object.assign({}, ui, { playlist: playlist });
Object.assign(playlist, { tracks: tracks, snapshot_id: snapshot_id })
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
case 'PLAYLIST_TRACKS_LOADED':
var playlist = Object.assign({}, ui.playlist, { tracks: action.tracks })
return Object.assign({}, ui, { playlist: playlist });
case 'PLAYLIST_TRACKS':
var playlists = Object.assign([], ui.playlists)
var playlist = Object.assign({}, playlists[action.uri], { tracks: action.tracks })
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
case 'PLAYLIST_TRACKS_REORDERED':
var snapshot_id = null
if( action.snapshot_id ) snapshot_id = action.snapshot_id
var tracks = Object.assign([], ui.playlist.tracks)
var playlists = Object.assign([], ui.playlists)
var playlist = Object.assign({}, playlists[action.uri])
var tracks = Object.assign([], playlist.tracks)
// handle insert_before offset if we're moving BENEATH where we're slicing tracks
var insert_before = action.insert_before
@ -304,26 +377,25 @@ export default function reducer(ui = {}, action){
tracks.splice(insert_before, 0, tracks_to_move[i])
}
var playlist = Object.assign({}, ui.playlist, { snapshot_id: snapshot_id, tracks: tracks })
return Object.assign({}, ui, { playlist: playlist });
var snapshot_id = null
if( action.snapshot_id ) snapshot_id = action.snapshot_id
Object.assign(playlist, { tracks: tracks, snapshot_id: snapshot_id })
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
case 'PLAYLIST_FOLLOWING_LOADED':
var playlist = Object.assign({}, ui.playlist, { following: action.is_following })
return Object.assign({}, ui, { playlist: playlist, following_loading: false });
var playlists = Object.assign([], ui.playlists)
var playlist = Object.assign({}, playlists[action.uri], { following: action.is_following })
playlists[action.uri] = playlist
return Object.assign({}, ui, { playlists: playlists });
/**
* Library Playlists
**/
case 'LIBRARY_PLAYLISTS_LOADED':
var library_playlists = []
if (ui.library_playlists) library_playlists = ui.library_playlists
case 'MOPIDY_PLAYLISTS_LOADED':
case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
if( !action.data ) return ui
var playlists = [...ui.playlists, ...action.data]
playlists = helpers.mergeDuplicates(playlists, 'uri')
helpers.sortItems(playlists, 'name')
return Object.assign({}, ui, {
playlists: playlists
library_playlists: [...library_playlists, ...action.uris]
});

View File

@ -25,7 +25,7 @@ class Album extends React.Component{
}
componentDidMount(){
this.loadAlbum();
this.loadAlbum()
}
componentWillReceiveProps( nextProps ){
@ -46,11 +46,21 @@ class Album extends React.Component{
switch( helpers.uriSource( props.params.uri ) ){
case 'spotify':
this.props.spotifyActions.getAlbum( props.params.uri );
if (props.album && props.album.tracks && props.album.artists_uris){
console.info('Loading album from index')
}else{
this.props.spotifyActions.getAlbum( props.params.uri );
}
break;
case 'local':
if( props.mopidy_connected ) this.props.mopidyActions.getAlbum( props.params.uri );
if (props.mopidy_connected){
if (props.album && props.album.tracks){
console.info('Loading album from index')
} else {
this.props.mopidyActions.getAlbum( props.params.uri );
}
}
break;
}
}
@ -68,16 +78,26 @@ class Album extends React.Component{
render(){
if( !this.props.album ) return null
var artists = []
if (this.props.album.artists_uris && this.props.artists){
for (var i = 0; i < this.props.album.artists_uris.length; i++){
var uri = this.props.album.artists_uris[i]
if (this.props.artists.hasOwnProperty(uri)){
artists.push(this.props.artists[uri])
}
}
}
return (
<div className="view album-view">
<SidebarToggleButton />
<div className="intro">
<Thumbnail size="large" images={ this.props.album.images } />
<ArtistGrid artists={ this.props.album.artists } />
<ArtistGrid artists={artists} />
<div className="actions">
<button className="large primary" onClick={ e => this.play() }>Play</button>
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" /> : null }
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.props.album.is_following} /> : null }
</div>
<ul className="details">
@ -93,7 +113,7 @@ class Album extends React.Component{
<div className="title">
<h1>{ this.props.album.name }</h1>
<h3><ArtistSentence artists={ this.props.album.artists } /></h3>
<h3><ArtistSentence artists={artists} /></h3>
</div>
<section className="list-wrapper">
@ -116,7 +136,9 @@ class Album extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
album: state.ui.album,
artists: state.ui.artists,
album: (state.ui.albums && typeof(state.ui.albums[ownProps.params.uri]) !== 'undefined' ? state.ui.albums[ownProps.params.uri] : false ),
albums: state.ui.albums,
spotify_authorized: state.spotify.authorized,
mopidy_connected: state.mopidy.connected
};

View File

@ -53,7 +53,7 @@ class App extends React.Component{
// mopidy comes online
if( !this.props.mopidy_connected && nextProps.mopidy_connected ){
this.props.mopidyActions.getPlaylists();
this.props.mopidyActions.getLibraryPlaylists();
}
// spotify authorized

View File

@ -49,17 +49,31 @@ class Artist extends React.Component{
componentWillUpdate( nextProps, nextState ){
if( nextState.sub_view != this.state.sub_view && nextState.sub_view == 'biography' ){
if( this.props.artist && !this.props.artist.bio ){
this.props.lastfmActions.getArtist( this.props.artist.name.replace('&','and') )
this.props.lastfmActions.getArtist( this.props.params.uri, this.props.artist.name.replace('&','and') )
}
}
}
loadArtist( props = this.props ){
var source = helpers.uriSource( props.params.uri );
if( source == 'spotify' ){
this.props.spotifyActions.getArtist( props.params.uri );
}else if( source == 'local' && props.mopidy_connected ){
this.props.mopidyActions.getArtist( props.params.uri );
switch( helpers.uriSource( props.params.uri ) ){
case 'spotify':
if (props.artist && props.artist.albums_uris && props.artist.related_artists_uris){
console.info('Loading spotify artist from index')
}else{
this.props.spotifyActions.getArtist( props.params.uri );
}
break
case 'local':
if (props.mopidy_connected){
if (props.artist && props.artist.images){
console.info('Loading local artist from index')
} else {
this.props.mopidyActions.getArtist( props.params.uri );
}
}
break
}
// go back to overview
@ -68,7 +82,7 @@ class Artist extends React.Component{
loadMore(){
if( !this.props.artist.albums_more ) return
this.props.spotifyActions.getURL( this.props.artist.albums_more, 'SPOTIFY_ARTIST_ALBUMS_LOADED_MORE' );
this.props.spotifyActions.getURL( this.props.artist.albums_more, 'SPOTIFY_ARTIST_ALBUMS_LOADED', this.props.params.uri );
}
play(){
@ -83,9 +97,7 @@ class Artist extends React.Component{
<span className={'option '+( this.state.sub_view == 'overview' ? 'active' : null)} onClick={() => this.setState({ sub_view: 'overview' })}>
Overview
</span>
<span className={'option '+( this.state.sub_view == 'related_artists' ? 'active' : null)} onClick={() => this.setState({ sub_view: 'related_artists' })}>
Related artists
</span>
{this.props.artist.related_artists_uris ? <span className={'option '+( this.state.sub_view == 'related_artists' ? 'active' : null)} onClick={() => this.setState({ sub_view: 'related_artists' })}>Related artists</span> : null}
<span className={'option '+( this.state.sub_view == 'biography' ? 'active' : null)} onClick={() => this.setState({ sub_view: 'biography' })}>
Biography
</span>
@ -94,13 +106,32 @@ class Artist extends React.Component{
}
renderBody(){
var related_artists = []
if (this.props.artist.related_artists_uris){
for (var i = 0; i < this.props.artist.related_artists_uris.length; i++){
var uri = this.props.artist.related_artists_uris[i]
if (this.props.artists.hasOwnProperty(uri)){
related_artists.push(this.props.artists[uri])
}
}
}
var albums = []
if (this.props.artist.albums_uris){
for (var i = 0; i < this.props.artist.albums_uris.length; i++){
var uri = this.props.artist.albums_uris[i]
if (this.props.albums.hasOwnProperty(uri)){
albums.push(this.props.albums[uri])
}
}
}
if( this.state.sub_view == 'related_artists' ){
return (
<div className="body related-artists">
<h4 className="left-padding">Related artists</h4>
<section className="grid-wrapper no-top-padding">
{ this.props.artist.related_artists ? <ArtistGrid artists={ this.props.artist.related_artists } /> : null }
<ArtistGrid artists={related_artists} />
</section>
</div>
)
@ -117,26 +148,22 @@ class Artist extends React.Component{
)
}
// default body
return (
<div className="body overview">
<div className="col w70">
<div className={related_artists.length > 0 ? "col w70" : "col w100"}>
<h4 className="left-padding">Top tracks</h4>
{ this.props.artist.tracks ? <TrackList tracks={ this.props.artist.tracks } /> : null }
</div>
<div className="col w5"></div>
<div className="col w25 related-artists">
<h4>Related artists</h4>
{ this.props.artist.related_artists ? <ArtistList artists={ this.props.artist.related_artists.slice(0,6) } /> : null }
</div>
{related_artists.length > 0 ? <div className="col w25 related-artists"><h4>Related artists</h4><ArtistList artists={related_artists.slice(0,6)} /></div> : null}
<div className="cf"></div>
<h4 className="left-padding">Albums</h4>
<section className="grid-wrapper no-top-padding">
{ this.props.artist.albums ? <AlbumGrid albums={ this.props.artist.albums } /> : null }
<AlbumGrid albums={albums} />
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</section>
</div>
@ -147,7 +174,7 @@ class Artist extends React.Component{
if( !this.props.artist ) return null
var scheme = helpers.uriSource( this.props.params.uri );
var image = false
var image = null
if( this.props.artist.images ) image = helpers.sizedImages( this.props.artist.images ).huge
return (
@ -171,7 +198,7 @@ class Artist extends React.Component{
<div className="actions">
<button className="large primary" onClick={ e => this.play() }>Start radio</button>
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton uri={this.props.params.uri} removeText="Unfollow" addText="Follow" /> : null }
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton uri={this.props.params.uri} removeText="Unfollow" addText="Follow" is_following={this.props.artist.is_following} /> : null }
</div>
<ul className="details">
@ -203,7 +230,9 @@ class Artist extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
artist: state.ui.artist,
artist: (state.ui.artists && typeof(state.ui.artists[ownProps.params.uri]) !== 'undefined' ? state.ui.artists[ownProps.params.uri] : false ),
artists: state.ui.artists,
albums: state.ui.albums,
spotify_authorized: state.spotify.authorized,
mopidy_connected: state.mopidy.connected
}

View File

@ -55,7 +55,7 @@ class Playlist extends React.Component{
loadMore(){
if( !this.props.playlist.tracks_more ) return
this.props.spotifyActions.getURL( this.props.playlist.tracks_more, 'SPOTIFY_PLAYLIST_LOADED_MORE' );
this.props.spotifyActions.getURL( this.props.playlist.tracks_more, 'PLAYLIST_LOADED_MORE_TRACKS', this.props.playlist.uri );
}
play(){
@ -90,19 +90,6 @@ class Playlist extends React.Component{
this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, tracks_indexes )
}
isEditable(){
if( helpers.uriSource( this.props.playlist.uri ) == 'spotify' ){
if( !this.props.spotify_authorized ) return false
return ( this.props.playlist &&
this.props.playlist.owner &&
this.props.playlist.owner.id == this.props.spotify_userid
)
}else{
return true
}
}
renderExtraButtons(){
switch( helpers.uriSource( this.props.playlist.uri ) ){
@ -115,7 +102,7 @@ class Playlist extends React.Component{
)
case 'spotify':
if( this.isEditable() ){
if( this.props.playlist.can_edit ){
return (
<span>
<button className="large tertiary" onClick={ e => this.props.uiActions.openModal('edit_playlist', { uri: this.props.playlist.uri, name: this.props.playlist.name, is_public: this.props.playlist.public }) }>Edit</button>
@ -123,7 +110,7 @@ class Playlist extends React.Component{
</span>
)
}
return <FollowButton uri={this.props.playlist.uri} addText="Add to library" removeText="Remove from library" />
return <FollowButton uri={this.props.playlist.uri} addText="Add to library" removeText="Remove from library" is_following={this.props.playlist.is_following} />
}
}
@ -167,7 +154,7 @@ class Playlist extends React.Component{
</div>
<section className="list-wrapper">
{ this.props.playlist.tracks ? <TrackList context={ this.isEditable() ? 'editable-playlist' : 'playlist'} tracks={ this.props.playlist.tracks } removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
{ this.props.playlist.tracks ? <TrackList context={ this.props.playlist.can_edit ? 'editable-playlist' : 'playlist'} tracks={ this.props.playlist.tracks } removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</section>
@ -186,7 +173,7 @@ class Playlist extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
playlist: state.ui.playlist,
playlist: state.ui.playlists[helpers.indexFriendlyUri(ownProps.params.uri)],
mopidy_connected: state.mopidy.connected,
spotify_authorized: state.spotify.authorized,
spotify_userid: state.spotify.me.id

View File

@ -17,29 +17,35 @@ class DiscoverCategory extends React.Component{
componentDidMount(){
this.props.spotifyActions.getCategory( this.props.params.id );
this.props.spotifyActions.getCategoryPlaylists( this.props.params.id );
}
componentWillReceiveProps( nextProps ){
if( nextProps.params.id != this.props.params.id ){
this.props.spotifyActions.getCategory( nextProps.params.id );
this.props.spotifyActions.getCategoryPlaylists( nextProps.params.id );
}
}
loadMore(){
if( !this.props.spotify.new_releases || !this.props.spotify.new_releases.next ) return
this.props.spotifyActions.getURL( this.props.spotify.new_releases.next, 'SPOTIFY_NEW_RELEASES_LOADED_MORE' );
if( !this.props.playlists_next || !this.props.playlists_next ) return
this.props.spotifyActions.getURL( this.props.playlists_next, 'SPOTIFY_NEW_RELEASES_LOADED_MORE' );
}
render(){
if( !this.props.spotify.category ) return null;
if (!this.props.category) return null
var playlists = []
for (var i = 0; i < this.props.category.playlists.length; i++){
var uri = this.props.category.playlists[i]
if (this.props.playlists.hasOwnProperty(uri)){
playlists.push(this.props.playlists[uri])
}
}
return (
<div className="view discover-categories-view">
<Header icon="grid" title={this.props.spotify.category.name} />
<Header icon="grid" title={this.props.category.name} />
<section className="grid-wrapper">
{ this.props.spotify.category_playlists ? <PlaylistGrid playlists={this.props.spotify.category_playlists.items} /> : null }
<PlaylistGrid playlists={playlists} />
</section>
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
@ -55,7 +61,10 @@ class DiscoverCategory extends React.Component{
**/
const mapStateToProps = (state, ownProps) => {
return state;
return {
playlists: state.ui.playlists,
category: state.spotify.category
}
}
const mapDispatchToProps = (dispatch) => {

View File

@ -20,11 +20,22 @@ class DiscoverFeatured extends React.Component{
}
render(){
var playlists = []
if (this.props.featured_playlists){
for (var i = 0; i < this.props.featured_playlists.playlists.length; i++){
var uri = this.props.featured_playlists.playlists[i]
if (this.props.playlists.hasOwnProperty(uri)){
playlists.push(this.props.playlists[uri])
}
}
}
return (
<div className="view discover-featured-view">
<Header icon="star" title="Featured playlists" />
<section className="grid-wrapper">
{ this.props.spotify.featured_playlists ? <PlaylistGrid playlists={this.props.spotify.featured_playlists.playlists.items} /> : null }
{ playlists ? <PlaylistGrid playlists={playlists} /> : null }
</section>
</div>
);
@ -39,7 +50,10 @@ class DiscoverFeatured extends React.Component{
**/
const mapStateToProps = (state, ownProps) => {
return state;
return {
featured_playlists: state.spotify.featured_playlists,
playlists: state.ui.playlists
}
}
const mapDispatchToProps = (dispatch) => {

View File

@ -17,20 +17,31 @@ class DiscoverNewReleases extends React.Component{
}
componentDidMount(){
this.props.spotifyActions.getNewReleases();
if (!this.props.new_releases) this.props.spotifyActions.getNewReleases();
}
loadMore(){
if( !this.props.spotify.new_releases || !this.props.spotify.new_releases.next ) return
this.props.spotifyActions.getURL( this.props.spotify.new_releases.next, 'SPOTIFY_NEW_RELEASES_LOADED_MORE' );
if (!this.props.new_releases_more) return
this.props.spotifyActions.getURL(this.props.new_releases_more, 'SPOTIFY_NEW_RELEASES_LOADED');
}
render(){
var albums = []
if (this.props.new_releases){
for (var i = 0; i < this.props.new_releases.length; i++){
var uri = this.props.new_releases[i]
if (this.props.albums.hasOwnProperty(uri)){
albums.push(this.props.albums[uri])
}
}
}
return (
<div className="view discover-new-releases-view">
<Header icon="leaf" title="New Releases" />
<section className="grid-wrapper">
{ this.props.spotify.new_releases ? <AlbumGrid albums={this.props.spotify.new_releases.items} /> : null }
<AlbumGrid albums={albums} />
</section>
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
@ -46,7 +57,12 @@ class DiscoverNewReleases extends React.Component{
**/
const mapStateToProps = (state, ownProps) => {
return state;
return {
albums: state.ui.albums,
new_releases: state.ui.new_releases,
new_releases_more: state.ui.new_releases_more,
new_releases_total: state.ui.new_releases_total
}
}
const mapDispatchToProps = (dispatch) => {

View File

@ -24,16 +24,16 @@ class LibraryAlbums extends React.Component{
}
componentDidMount(){
this.props.spotifyActions.getLibraryAlbums();
if (!this.props.library_albums) this.props.spotifyActions.getLibraryAlbums();
}
loadMore(){
if( !this.props.albums_more ) return
this.props.spotifyActions.getURL( this.props.albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED_MORE' );
if( !this.props.library_albums_more ) return
this.props.spotifyActions.getURL( this.props.library_albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED' );
}
renderView(){
if( !this.props.albums ) return null
renderView(albums){
if (!albums || albums.length <= 0) return null
if( this.props.view == 'list' ){
var columns = [
@ -60,20 +60,20 @@ class LibraryAlbums extends React.Component{
]
return (
<section className="list-wrapper">
<List rows={this.props.albums} columns={columns} link_prefix={global.baseURL+"album/"} />
<List rows={albums} columns={columns} link_prefix={global.baseURL+"album/"} />
</section>
)
}else if( this.props.view == 'thumbnails' ){
return (
<section className="grid-wrapper">
{ this.props.albums ? <AlbumGrid albums={this.props.albums} /> : null }
<AlbumGrid albums={albums} />
</section>
)
}else{
return (
<section className="grid-wrapper albums-detail-subview">
{
this.props.albums.map( album => {
albums.map( album => {
return (
<div className="album" key={album.uri}>
<Link to={global.baseURL+'album/'+album.uri}>
@ -85,7 +85,7 @@ class LibraryAlbums extends React.Component{
</Link>
<h4><ArtistSentence className="grey-text" artists={album.artists} /></h4>
</div>
<TrackList tracks={album.tracks.items} />
<TrackList tracks={album.tracks} />
</div>
)
})
@ -96,6 +96,16 @@ class LibraryAlbums extends React.Component{
}
render(){
var albums = []
if (this.props.library_albums && this.props.albums){
for (var i = 0; i < this.props.library_albums.length; i++){
var uri = this.props.library_albums[i]
if (this.props.albums.hasOwnProperty(uri)){
albums.push(this.props.albums[uri])
}
}
}
var view_options = [
{
@ -119,7 +129,7 @@ class LibraryAlbums extends React.Component{
return (
<div className="view library-albums-view">
<Header icon="cd" title="My albums" actions={actions} />
{ this.renderView() }
{ this.renderView(albums) }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
@ -136,8 +146,9 @@ class LibraryAlbums extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
view: state.ui.library_albums_view,
albums: state.spotify.library_albums,
albums_more: state.spotify.library_albums_more,
albums: state.ui.albums,
library_albums: state.ui.library_albums,
library_albums_more: state.ui.library_albums_more,
}
}

View File

@ -20,19 +20,16 @@ class LibraryArtists extends React.Component{
super(props);
}
// on render
componentDidMount(){
this.props.spotifyActions.getLibraryArtists();
if (!this.props.library_artists) this.props.spotifyActions.getLibraryArtists();
}
loadMore(){
if( !this.props.artists_more ) return
this.props.spotifyActions.getURL( this.props.artists_more, 'SPOTIFY_LIBRARY_ARTISTS_LOADED_MORE' );
if( !this.props.library_artists_more ) return
this.props.spotifyActions.getURL( this.props.library_artists_more, 'SPOTIFY_LIBRARY_ARTISTS_LOADED' );
}
renderView(){
if( !this.props.artists ) return null
renderView(artists){
if( this.props.view == 'list' ){
var columns = [
{
@ -48,13 +45,13 @@ class LibraryArtists extends React.Component{
]
return (
<section className="list-wrapper">
<List rows={this.props.artists} columns={columns} link_prefix={global.baseURL+"artist/"} show_source_icon={true} />
<List rows={artists} columns={columns} link_prefix={global.baseURL+"artist/"} show_source_icon={true} />
</section>
)
}else{
return (
<section className="grid-wrapper">
<ArtistGrid artists={this.props.artists} />
<ArtistGrid artists={artists} />
</section>
)
}
@ -62,6 +59,16 @@ class LibraryArtists extends React.Component{
render(){
var artists = []
if (this.props.library_artists && this.props.artists){
for (var i = 0; i < this.props.library_artists.length; i++){
var uri = this.props.library_artists[i]
if (this.props.artists.hasOwnProperty(uri)){
artists.push(this.props.artists[uri])
}
}
}
var view_options = [
{
value: 'thumbnails',
@ -80,7 +87,7 @@ class LibraryArtists extends React.Component{
return (
<div className="view library-artists-view">
<Header icon="mic" title="My artists" actions={actions} />
{ this.renderView() }
{ this.renderView(artists) }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
@ -96,9 +103,10 @@ class LibraryArtists extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
view: state.ui.library_artists_view,
artists: state.spotify.library_artists,
artists_more: state.spotify.library_artists_more
artists: state.ui.artists,
library_artists: state.ui.library_artists,
library_artists_more: state.ui.library_artists_more,
view: state.ui.library_artists_view
}
}

View File

@ -15,7 +15,6 @@ class LibraryLocalAlbums extends React.Component{
super(props);
}
// on render
componentDidMount(){
this.loadAlbums()
}
@ -33,13 +32,21 @@ class LibraryLocalAlbums extends React.Component{
}
render(){
if( !this.props.albums ) return null
var albums = []
if (this.props.albums && this.props.local_albums){
for (var i = 0; i < this.props.local_albums.length; i++){
var uri = this.props.local_albums[i]
if (this.props.albums.hasOwnProperty(uri)){
albums.push(this.props.albums[uri])
}
}
}
return (
<div className="view library-local-view">
<Header icon="music" title="Local albums" />
<div>
<List columns={[{ name: 'name', width: '100'}]} rows={this.props.albums} link_prefix={global.baseURL+"album/"} />
<List columns={[{ name: 'name', width: '100'}]} rows={albums} link_prefix={global.baseURL+"album/"} />
</div>
</div>
);
@ -56,7 +63,8 @@ class LibraryLocalAlbums extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
mopidy_connected: state.mopidy.connected,
albums: state.mopidy.albums
local_albums: state.ui.local_albums,
albums: state.ui.albums
}
}

View File

@ -27,19 +27,27 @@ class LibraryLocalArtists extends React.Component{
}
loadArtists(props = this.props){
if( props.mopidy_connected ){
if( props.mopidy_connected && !props.local_artists ){
this.props.mopidyActions.getArtists();
}
}
render(){
if( !this.props.artists ) return null
var artists = []
if (this.props.artists && this.props.local_artists){
for (var i = 0; i < this.props.local_artists.length; i++){
var uri = this.props.local_artists[i]
if (this.props.artists.hasOwnProperty(uri)){
artists.push(this.props.artists[uri])
}
}
}
return (
<div className="view library-local-view">
<Header icon="music" title="Local artists" />
<section className="list-wrapper">
<List columns={[{ name: 'name', width: '100'}]} rows={this.props.artists} link_prefix={global.baseURL+"artist/"} />
<List columns={[{ name: 'name', width: '100'}]} rows={artists} link_prefix={global.baseURL+"artist/"} />
</section>
</div>
);
@ -56,7 +64,8 @@ class LibraryLocalArtists extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
mopidy_connected: state.mopidy.connected,
artists: state.mopidy.artists
local_artists: state.ui.local_artists,
artists: state.ui.artists
}
}

View File

@ -33,9 +33,16 @@ class LibraryPlaylists extends React.Component{
}
renderView(){
if( !this.props.playlists ) return null
if( !this.props.library_playlists || !this.props.playlists ) return null
var playlists = []
for (var i = 0; i < this.props.library_playlists.length; i++){
var uri = this.props.library_playlists[i]
if (this.props.playlists.hasOwnProperty(uri)){
playlists.push(this.props.playlists[uri])
}
}
var playlists = Object.assign([], this.props.playlists)
if( this.props.sort ){
playlists = helpers.sortItems(playlists, this.props.sort, this.props.sort_reverse)
}
@ -60,7 +67,7 @@ class LibraryPlaylists extends React.Component{
{
width: 10,
label: 'Tracks',
name: 'tracks.total'
name: 'tracks_total'
}
]
return (
@ -141,6 +148,7 @@ const mapStateToProps = (state, ownProps) => {
view: state.ui.library_playlists_view,
sort: state.ui.library_playlists_sort,
sort_reverse: state.ui.library_playlists_sort_reverse,
library_playlists: state.ui.library_playlists,
playlists: state.ui.playlists
}
}