Fiddling with thunk; More library endpoints
This commit is contained in:
@ -10,10 +10,15 @@ export default class AlbumGridItem extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
var link = '/album/' + this.props.item.uri;
|
||||
var item = this.props.item;
|
||||
if( typeof(item.album) !== 'undefined' ){
|
||||
item.album.added_at = item.added_at;
|
||||
item = item.album;
|
||||
}
|
||||
var link = '/album/' + item.uri;
|
||||
return (
|
||||
<Link to={link} className="grid-item album-grid-item">
|
||||
<Thumbnail size="medium" images={this.props.item.images} />
|
||||
{ item.images ? <Thumbnail size="medium" images={item.images} /> : item.name }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,11 +13,22 @@ class Player extends React.Component{
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderTrackInFocus(){
|
||||
if( this.props.mopidy && this.props.mopidy.trackInFocus ){
|
||||
return (
|
||||
<div>
|
||||
<div>{ this.props.mopidy.trackInFocus.track.name }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderControls(){
|
||||
|
||||
var playButton = <a onClick={() => this.props.actions.instruct('playback.play')}><FontAwesome name="play" /> </a>
|
||||
var playButton = <a onClick={() => this.props.actions.play()}><FontAwesome name="play" /> </a>
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
playButton = <a onClick={() => this.props.actions.instruct('playback.pause')}><FontAwesome name="pause" /> </a>
|
||||
playButton = <a onClick={() => this.props.actions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
|
||||
var consumeButton = <a onClick={() => this.props.actions.instruct('tracklist.setConsume', [true])}>Consume </a>
|
||||
@ -37,11 +48,12 @@ class Player extends React.Component{
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ this.renderTrackInFocus() }
|
||||
{ playButton }
|
||||
<a onClick={() => this.props.actions.instruct('playback.previous')}>
|
||||
<a onClick={() => this.props.actions.previous()}>
|
||||
<FontAwesome name="step-backward" />
|
||||
</a>
|
||||
<a onClick={() => this.props.actions.instruct('playback.next')}>
|
||||
<a onClick={() => this.props.actions.next()}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</a>
|
||||
{ consumeButton }
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import * as actions from '../services/mopidy/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
|
||||
import Track from './Track'
|
||||
|
||||
@ -17,6 +17,10 @@ class TrackList extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
this.setState({ tracks: nextProps.tracks });
|
||||
}
|
||||
|
||||
handleClick( e, index ){
|
||||
var tracks = this.state.tracks;
|
||||
|
||||
@ -49,6 +53,11 @@ class TrackList extends React.Component{
|
||||
this.setState({ tracks: tracks, lastSelectedTrack: index });
|
||||
}
|
||||
|
||||
handleDoubleClick( e, index ){
|
||||
var tracks = this.state.tracks;
|
||||
this.playTrack( tracks[index] )
|
||||
}
|
||||
|
||||
selectedTracks(){
|
||||
function isSelected( track ){
|
||||
return ( typeof(track.selected) !== 'undefined' && track.selected );
|
||||
@ -56,21 +65,38 @@ class TrackList extends React.Component{
|
||||
return this.state.tracks.filter(isSelected)
|
||||
}
|
||||
|
||||
handleDoubleClick( e, index ){
|
||||
var tracks = this.state.tracks;
|
||||
this.props.playTrack( tracks[index] )
|
||||
playTracks(){
|
||||
|
||||
var tracks = this.selectedTracks();
|
||||
|
||||
if( typeof(this.props.playTracks) !== 'undefined' ){
|
||||
return this.props.playTracks( tracks );
|
||||
}
|
||||
|
||||
var uris = [];
|
||||
for( var i = 0; i < tracks.length; i++ ){
|
||||
uris.push( tracks[i].uri )
|
||||
}
|
||||
return this.props.mopidyActions.playTracks( uris )
|
||||
}
|
||||
|
||||
playTracks(){
|
||||
this.props.playTracks( this.selectedTracks() )
|
||||
playTrack( track ){
|
||||
|
||||
if( typeof(this.props.playTrack) !== 'undefined' ){
|
||||
return this.props.playTrack( this.selectedTracks() );
|
||||
}
|
||||
|
||||
var uris = [track.uri];
|
||||
this.props.mopidyActions.playTracks( uris )
|
||||
}
|
||||
|
||||
removeTracks(){
|
||||
this.props.removeTracks( this.selectedTracks() )
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
this.setState({ tracks: nextProps.tracks });
|
||||
var tracks = this.selectedTracks();
|
||||
|
||||
if( typeof(this.props.removeTracks) !== 'undefined' ){
|
||||
return this.props.removeTracks( tracks );
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
@ -122,7 +148,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch)
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,8 @@ import Artist from './views/Artist'
|
||||
import Queue from './views/Queue'
|
||||
import Settings from './views/Settings'
|
||||
import LibraryArtists from './views/LibraryArtists'
|
||||
import LibraryAlbums from './views/LibraryAlbums'
|
||||
import LibraryTracks from './views/LibraryTracks'
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
@ -25,6 +27,8 @@ ReactDOM.render(
|
||||
<Route path="/" component={App}>
|
||||
|
||||
<Route path="library/artists" component={LibraryArtists} />
|
||||
<Route path="library/albums" component={LibraryAlbums} />
|
||||
<Route path="library/tracks" component={LibraryTracks} />
|
||||
<Route path="album/:uri" component={Album} />
|
||||
<Route path="artist/:uri" component={Artist} />
|
||||
<Route path="queue" component={Queue} />
|
||||
|
||||
@ -33,8 +33,18 @@ export function changeTrack( tlid ){
|
||||
export function playTracks( uris ){
|
||||
return {
|
||||
type: 'MOPIDY_PLAY_TRACKS',
|
||||
uris: uris
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueTracks( uris, at_position = false ){
|
||||
if( typeof(uris) !== 'array' ) uris = [uris];
|
||||
var value = { uris: uris };
|
||||
if( at_position ) value.at_position = at_position;
|
||||
return {
|
||||
type: 'MOPIDY_ENQUEUE_TRACKS',
|
||||
call: 'tracklist.add',
|
||||
value: { at_position: 0, uris: uris }
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,3 +64,18 @@ export function instruct( call, value ){
|
||||
}
|
||||
}
|
||||
|
||||
export function play(){
|
||||
return instruct('playback.play');
|
||||
}
|
||||
|
||||
export function pause(){
|
||||
return instruct('playback.pause');
|
||||
}
|
||||
|
||||
export function next(){
|
||||
return instruct('playback.next');
|
||||
}
|
||||
|
||||
export function previous(){
|
||||
return instruct('playback.previous');
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import Mopidy from 'mopidy'
|
||||
import actions from './actions'
|
||||
var actions = require('./actions.js')
|
||||
|
||||
const MopidyMiddleware = (function(){
|
||||
|
||||
@ -38,7 +38,7 @@ const MopidyMiddleware = (function(){
|
||||
break;
|
||||
|
||||
case 'event:volumeChanged':
|
||||
store.dispatch({ type: 'MOPIDY_Volume', data: data.volume });
|
||||
store.dispatch({ type: 'MOPIDY_VOLUME', data: data.volume });
|
||||
break;
|
||||
|
||||
case 'event:optionsChanged':
|
||||
@ -61,8 +61,9 @@ const MopidyMiddleware = (function(){
|
||||
* @param string model = which Mopidy model (playback, tracklist, etc)
|
||||
* @param string property = TlTracks, Consume, etc
|
||||
* @param string value (optional) = value of the property to pass
|
||||
* @return promise
|
||||
**/
|
||||
const instruct = ( ws, store, call, value = false ) => {
|
||||
const instruct = ( ws, store, call, value = {} ) => {
|
||||
|
||||
var callParts = call.split('.');
|
||||
var model = callParts[0];
|
||||
@ -71,22 +72,19 @@ const MopidyMiddleware = (function(){
|
||||
property = property.replace('get','');
|
||||
property = property.replace('set','');
|
||||
|
||||
// if we have a value, we need to include in our payload
|
||||
if( value ){
|
||||
return new Promise( (resolve, reject) => {
|
||||
ws[model][method]( value )
|
||||
.then(
|
||||
response => store.dispatch({ type: 'MOPIDY_'+property, model: model, data: response }),
|
||||
error => console.error( error )
|
||||
response => {
|
||||
store.dispatch({ type: 'MOPIDY_'+property.toUpperCase(), model: model, data: response });
|
||||
resolve(response);
|
||||
},
|
||||
error => {
|
||||
console.error(error)
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// no value, so omit the payload
|
||||
}else{
|
||||
ws[model][method]()
|
||||
.then(
|
||||
response => store.dispatch({ type: 'MOPIDY_'+property, model: model, data: response }),
|
||||
error => console.error( error )
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,6 +95,7 @@ const MopidyMiddleware = (function(){
|
||||
* it just proceeds to the next middleware, or default functionality
|
||||
**/
|
||||
return store => next => action => {
|
||||
|
||||
switch(action.type) {
|
||||
|
||||
case 'MOPIDY_CONNECT':
|
||||
@ -129,10 +128,20 @@ const MopidyMiddleware = (function(){
|
||||
break;
|
||||
|
||||
case 'MOPIDY_PLAY_TRACKS':
|
||||
var callback = function(){
|
||||
store.dispatch({ type: 'MOPIDY_TEST', model: 'yeah', data: {} })
|
||||
}
|
||||
instruct( socket, store, action.call, action.value, callback )
|
||||
|
||||
// add our first track
|
||||
instruct( socket, store, 'tracklist.add', { uri: action.uris[0], at_position: 0 } )
|
||||
.then( response => {
|
||||
|
||||
// play it
|
||||
store.dispatch( actions.changeTrack( response[0].tlid ) );
|
||||
|
||||
// add the rest of our uris (if any)
|
||||
action.uris.shift();
|
||||
if( action.uris.length > 0 ){
|
||||
store.dispatch( actions.enqueueTracks( action.uris, 1 ) )
|
||||
}
|
||||
})
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
|
||||
@ -2,9 +2,6 @@
|
||||
export default function reducer(mopidy = {}, action){
|
||||
switch (action.type) {
|
||||
|
||||
case 'MOPIDY_CONNECTING':
|
||||
return Object.assign({}, mopidy, { connected: false, connecting: true });
|
||||
|
||||
case 'MOPIDY_CONNECTED':
|
||||
return Object.assign({}, mopidy, { connected: true, connecting: false });
|
||||
|
||||
@ -25,37 +22,37 @@ export default function reducer(mopidy = {}, action){
|
||||
/**
|
||||
* Websocket-initiated actions
|
||||
**/
|
||||
case 'MOPIDY_State':
|
||||
case 'MOPIDY_STATE':
|
||||
return Object.assign({}, mopidy, {
|
||||
state: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_Consume':
|
||||
case 'MOPIDY_CONSUME':
|
||||
return Object.assign({}, mopidy, {
|
||||
consume: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_Random':
|
||||
case 'MOPIDY_RANDOM':
|
||||
return Object.assign({}, mopidy, {
|
||||
random: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_Repeat':
|
||||
case 'MOPIDY_REPEAT':
|
||||
return Object.assign({}, mopidy, {
|
||||
repeat: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_TlTracks':
|
||||
case 'MOPIDY_TLTRACKS':
|
||||
return Object.assign({}, mopidy, {
|
||||
tracks: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_CurrentTlTrack':
|
||||
case 'MOPIDY_CURRENTTLTRACK':
|
||||
return Object.assign({}, mopidy, {
|
||||
trackInFocus: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_Volume':
|
||||
case 'MOPIDY_VOLUME':
|
||||
return Object.assign({}, mopidy, {
|
||||
volume: action.data
|
||||
});
|
||||
|
||||
@ -10,25 +10,6 @@ export function setConfig( config ){
|
||||
}
|
||||
}
|
||||
|
||||
export function startAuthorization(){
|
||||
return {
|
||||
type: 'SPOTIFY_START_AUTHORIZATION'
|
||||
}
|
||||
}
|
||||
|
||||
export function removeAuthorization(){
|
||||
return {
|
||||
type: 'SPOTIFY_REMOVE_AUTHORIZATION'
|
||||
}
|
||||
}
|
||||
|
||||
export function authorizationGranted( data ){
|
||||
return {
|
||||
type: 'SPOTIFY_AUTHORIZATION_GRANTED',
|
||||
data: data
|
||||
}
|
||||
}
|
||||
|
||||
export function connect(){
|
||||
return {
|
||||
type: 'SPOTIFY_CONNECTING'
|
||||
@ -48,16 +29,176 @@ export function loadAlbum( uri ){
|
||||
}
|
||||
}
|
||||
|
||||
export function loadArtist( uri ){
|
||||
return {
|
||||
type: 'SPOTIFY_LOAD_ARTIST',
|
||||
uri: uri
|
||||
|
||||
|
||||
/**
|
||||
* Send an ajax request to the Spotify API
|
||||
*
|
||||
* @param endpoint string = the url to query (ie /albums/:uri)
|
||||
* @param method string
|
||||
* @param data mixed = request payload
|
||||
* @return ajax promise
|
||||
**/
|
||||
const sendRequest = (token, endpoint, method = 'GET', data = false) => {
|
||||
|
||||
var options = {
|
||||
method: method,
|
||||
cache: true,
|
||||
url: 'https://api.spotify.com/v1/'+endpoint,
|
||||
data: data
|
||||
};
|
||||
|
||||
if( token ){
|
||||
options.headers = {
|
||||
Authorization: 'Bearer '+ token
|
||||
}
|
||||
}
|
||||
|
||||
return $.ajax( options );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle authorization process
|
||||
**/
|
||||
|
||||
export function startAuthorization(){
|
||||
return { type: 'SPOTIFY_START_AUTHORIZATION' }
|
||||
}
|
||||
|
||||
export function authorizationGranted( data ){
|
||||
return { type: 'SPOTIFY_AUTHORIZATION_GRANTED', data: data }
|
||||
}
|
||||
|
||||
export function removeAuthorization(){
|
||||
return { type: 'SPOTIFY_REMOVE_AUTHORIZATION' }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a single artist
|
||||
*
|
||||
* @param uri string
|
||||
**/
|
||||
export function getArtist( uri ){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
// flush out the previous store value
|
||||
dispatch({ type: 'SPOTIFY_ARTIST_LOADED', data: false });
|
||||
|
||||
var id = uri.replace('spotify:artist:','');
|
||||
var artist = {};
|
||||
|
||||
// while we're fiddling about, go get the albums
|
||||
dispatch(getArtistAlbums(uri));
|
||||
|
||||
// get both the artist and the top tracks
|
||||
$.when(
|
||||
|
||||
sendRequest( false, 'artists/'+id )
|
||||
.then( response => {
|
||||
Object.assign(artist, response);
|
||||
}),
|
||||
|
||||
sendRequest( false, 'artists/'+id+'/top-tracks?country='+getState().spotify.country )
|
||||
.then( response => {
|
||||
Object.assign(artist, response);
|
||||
})
|
||||
|
||||
).then( () => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_ARTIST_LOADED',
|
||||
data: artist
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function loadLibraryArtists(){
|
||||
return {
|
||||
type: 'SPOTIFY_LOAD_LIBRARY_ARTISTS'
|
||||
export function getArtistAlbums( uri ){
|
||||
return dispatch => {
|
||||
sendRequest( false, 'artists/'+ uri.replace('spotify:artist:','') +'/albums' )
|
||||
.then( response => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_ARTIST_ALBUMS_LOADED',
|
||||
data: response
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single album
|
||||
*
|
||||
* @oaram uri string
|
||||
**/
|
||||
export function getAlbum( uri ){
|
||||
return dispatch => {
|
||||
|
||||
// flush out the previous store value
|
||||
dispatch({ type: 'SPOTIFY_ALBUM_LOADED', data: false });
|
||||
|
||||
sendRequest( false, 'albums/'+ uri.replace('spotify:album:','') )
|
||||
.then( response => {
|
||||
|
||||
// inject the parent album object into each track for consistent track objects
|
||||
for( var i = 0; i < response.tracks.items.length; i++ ){
|
||||
response.tracks.items[i].album = {
|
||||
name: response.name,
|
||||
uri: response.uri
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'SPOTIFY_ALBUM_LOADED',
|
||||
data: response
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getLibraryArtists(){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', data: false });
|
||||
|
||||
sendRequest( getState().spotify.access_token, 'me/following?type=artist' )
|
||||
.then( response => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED',
|
||||
data: response
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getLibraryAlbums(){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED', data: false });
|
||||
|
||||
sendRequest( getState().spotify.access_token, 'me/albums' )
|
||||
.then( response => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED',
|
||||
data: response
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getLibraryTracks(){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'SPOTIFY_LIBRARY_TRACKS_LOADED', data: false });
|
||||
|
||||
sendRequest( getState().spotify.access_token, 'me/tracks' )
|
||||
.then( response => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_LIBRARY_TRACKS_LOADED',
|
||||
data: response
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,67 +47,6 @@ const SpotifyMiddleware = (function(){
|
||||
});
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_LOAD_ALBUM':
|
||||
|
||||
store.dispatch({ type: 'SPOTIFY_ALBUM_LOADED', data: false })
|
||||
var id = action.uri.replace('spotify:album:','');
|
||||
|
||||
sendRequest( false, 'albums/'+id )
|
||||
.then( (response) => {
|
||||
for( var i = 0; i < response.tracks.items.length; i++ ){
|
||||
response.tracks.items[i].album = {
|
||||
name: response.name,
|
||||
uri: response.uri
|
||||
}
|
||||
}
|
||||
store.dispatch({ type: 'SPOTIFY_ALBUM_LOADED', data: response })
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
case 'SPOTIFY_LOAD_ARTIST':
|
||||
|
||||
store.dispatch({ type: 'SPOTIFY_ARTIST_LOADED', data: false })
|
||||
store.dispatch({ type: 'SPOTIFY_ARTIST_ALBUMS_LOADED', data: false })
|
||||
|
||||
var id = action.uri.replace('spotify:artist:','');
|
||||
|
||||
sendRequest( false, 'artists/'+id )
|
||||
.then( (response) => {
|
||||
store.dispatch({ type: 'SPOTIFY_ARTIST_LOADED', data: response })
|
||||
});
|
||||
|
||||
sendRequest( false, 'artists/'+id+'/albums' )
|
||||
.then( (response) => {
|
||||
store.dispatch({ type: 'SPOTIFY_ARTIST_ALBUMS_LOADED', data: response })
|
||||
});
|
||||
|
||||
sendRequest( false, 'artists/'+id+'/top-tracks?country='+state.spotify.country )
|
||||
.then( (response) => {
|
||||
store.dispatch({ type: 'SPOTIFY_ARTIST_TRACKS_LOADED', data: response })
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
case 'SPOTIFY_LOAD_LIBRARY_ARTISTS':
|
||||
|
||||
store.dispatch({ type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', data: false })
|
||||
|
||||
sendRequest( state.spotify.access_token, 'me/following?type=artist' )
|
||||
.then( (response) => {
|
||||
store.dispatch({ type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', data: response })
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
case 'SPOTIFY_CONNECT':
|
||||
console.log('Spotify wants to connect')
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_DISCONNECT':
|
||||
console.log('Spotify wants to DISconnect')
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
|
||||
@ -41,12 +41,12 @@ export default function reducer(spotify = {}, action){
|
||||
case 'SPOTIFY_ARTIST_ALBUMS_LOADED':
|
||||
return Object.assign({}, spotify, { artist_albums: action.data });
|
||||
|
||||
case 'SPOTIFY_ARTIST_TRACKS_LOADED':
|
||||
return Object.assign({}, spotify, { artist_top_tracks: action.data });
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED':
|
||||
return Object.assign({}, spotify, { libraryArtists: action.data });
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
|
||||
return Object.assign({}, spotify, { libraryAlbums: action.data });
|
||||
|
||||
default:
|
||||
return spotify
|
||||
}
|
||||
|
||||
@ -15,40 +15,22 @@ class Album extends React.Component{
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.loadAlbum( this.props.params.uri );
|
||||
this.props.spotifyActions.getAlbum( this.props.params.uri );
|
||||
}
|
||||
|
||||
// when props changed
|
||||
componentWillReceiveProps( nextProps ){
|
||||
if( nextProps.params.uri != this.props.params.uri ){
|
||||
this.props.spotifyActions.loadAlbum( nextProps.params.uri );
|
||||
this.props.spotifyActions.getAlbum( nextProps.params.uri );
|
||||
}
|
||||
}
|
||||
|
||||
playTracks( tracks ){
|
||||
var uris = [];
|
||||
for( var i = 0; i < tracks.length; i++ ){
|
||||
uris.push( tracks[i].uri )
|
||||
}
|
||||
this.props.mopidyActions.playTracks( uris )
|
||||
}
|
||||
|
||||
playTrack( track ){
|
||||
var uris = [track.uri];
|
||||
this.props.mopidyActions.playTracks( uris )
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.spotify.album ){
|
||||
return (
|
||||
<div>
|
||||
<h3>{ this.props.spotify.album.name }</h3>
|
||||
<TrackList
|
||||
tracks={this.props.spotify.album.tracks.items}
|
||||
removeTracks={ null }
|
||||
playTracks={ tracks => this.playTracks( tracks ) }
|
||||
playTrack={ track => this.playTrack( track ) }
|
||||
/>
|
||||
<TrackList tracks={this.props.spotify.album.tracks.items} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -31,6 +31,8 @@ class App extends React.Component{
|
||||
<ul role="nav">
|
||||
<li><Link to="/queue">Now playing</Link></li>
|
||||
<li><Link to="/library/artists">Library: My artists</Link></li>
|
||||
<li><Link to="/library/albums">Library: My albums</Link></li>
|
||||
<li><Link to="/library/playlists">Library: My playlists</Link></li>
|
||||
<li><Link to="/settings">Settings</Link></li>
|
||||
</ul>
|
||||
{this.props.children}
|
||||
|
||||
@ -16,13 +16,13 @@ class Artist extends React.Component{
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.loadArtist( this.props.params.uri );
|
||||
this.props.spotifyActions.getArtist( this.props.params.uri );
|
||||
}
|
||||
|
||||
// when props changed
|
||||
componentWillReceiveProps( nextProps ){
|
||||
if( nextProps.params.uri != this.props.params.uri ){
|
||||
this.props.spotifyActions.loadArtist( nextProps.params.uri );
|
||||
this.props.spotifyActions.getArtist( nextProps.params.uri );
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ class Artist extends React.Component{
|
||||
<div>
|
||||
<h3>{ this.props.spotify.artist.name }</h3>
|
||||
<p>{ this.props.spotify.artist.followers.total.toLocaleString() } followers</p>
|
||||
{ this.props.spotify.artist_top_tracks ? <TrackList tracks={ this.props.spotify.artist_top_tracks.tracks } /> : null }
|
||||
{ this.props.spotify.artist.tracks ? <TrackList tracks={ this.props.spotify.artist.tracks } /> : null }
|
||||
{ this.props.spotify.artist_albums ? <AlbumGrid items={ this.props.spotify.artist_albums } /> : null }
|
||||
</div>
|
||||
);
|
||||
|
||||
54
src/js/views/LibraryAlbums.js
Executable file
54
src/js/views/LibraryAlbums.js
Executable file
@ -0,0 +1,54 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import AlbumGrid from '../components/AlbumGrid'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
class LibraryAlbums extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.getLibraryAlbums();
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.spotify.libraryAlbums ){
|
||||
return (
|
||||
<div>
|
||||
<h3>My albums</h3>
|
||||
<AlbumGrid items={this.props.spotify.libraryAlbums} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LibraryAlbums)
|
||||
@ -15,7 +15,7 @@ class LibraryArtists extends React.Component{
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.loadLibraryArtists();
|
||||
this.props.spotifyActions.getLibraryArtists();
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
54
src/js/views/LibraryTracks.js
Executable file
54
src/js/views/LibraryTracks.js
Executable file
@ -0,0 +1,54 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import TrackList from '../components/TrackList'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
class LibraryTracks extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.getLibraryTracks();
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.spotify.libraryAlbums ){
|
||||
return (
|
||||
<div>
|
||||
<h3>My tracks</h3>
|
||||
<TrackList tracks={this.props.spotify.libraryTracks.tracks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LibraryTracks)
|
||||
Reference in New Issue
Block a user