diff --git a/src/index.html b/src/index.html
index 1b436eb0..8d840930 100755
--- a/src/index.html
+++ b/src/index.html
@@ -4,6 +4,7 @@
Iris
+
diff --git a/src/js/components/SpotifyAuthenticationFrame.js b/src/js/components/SpotifyAuthenticationFrame.js
index ce6a45b4..cecf202b 100755
--- a/src/js/components/SpotifyAuthenticationFrame.js
+++ b/src/js/components/SpotifyAuthenticationFrame.js
@@ -10,6 +10,11 @@ class SpotifyAuthenticationFrame extends React.Component{
constructor(props) {
super(props);
+
+ this.state = {
+ frameUrl: '//jamesbarnsley.co.nz/spotmop.php?action=frame',
+ authorizing: false
+ }
}
componentDidMount(){
@@ -25,28 +30,41 @@ class SpotifyAuthenticationFrame extends React.Component{
var data = JSON.parse(event.data);
self.props.actions.authorizationGranted( data );
+ self.props.actions.getMe();
+
+ // and turn off our authorizing switch
+ self.setState({
+ frameUrl: '//jamesbarnsley.co.nz/spotmop.php?action=frame',
+ authorizing: false
+ })
}, false);
}
+ startAuthorization(){
+ this.setState({
+ frameUrl: '//jamesbarnsley.co.nz/spotmop.php?action=authorize&app='+location.protocol+'//'+window.location.host,
+ authorizing: true
+ })
+ }
+
renderMe(){
- if( this.props.spotify.me ){
- if( this.props.spotify.me.display_name ){
- return Logged in as { this.props.spotify.me.display_name }
- }else{
- return Logged in as { this.props.spotify.me.id }
- }
+ if( !this.props.spotify.me ) return null;
+
+ if( this.props.spotify.me.display_name ){
+ return Logged in as { this.props.spotify.me.display_name }
+ }else{
+ return Logged in as { this.props.spotify.me.id }
}
- return null;
}
renderAuthorizeButton(){
- if( this.props.spotify.authorizing ){
+ if( this.state.authorizing ){
return (
-
+
+ Authorizing...
+
);
}else if( this.props.spotify.authorized ){
return (
@@ -57,20 +75,34 @@ class SpotifyAuthenticationFrame extends React.Component{
);
}else{
return (
-
+
+ );
+ }
+ }
+
+ renderRefreshButton(){
+ if( !this.props.spotify.authorized ) return null;
+
+ if( this.props.spotify.refreshing_token ){
+ return (
+
+ );
+ }else{
+ return (
+
);
}
}
render(){
- var src = '//jamesbarnsley.co.nz/spotmop.php?action=frame';
- if( this.props.spotify.authorizing ){
- src = '//jamesbarnsley.co.nz/spotmop.php?action=authorize&app='+location.protocol+'//'+window.location.host;
- }
return (
{ this.renderAuthorizeButton() }
-
+ { this.renderRefreshButton() }
+
);
}
diff --git a/src/js/components/Track.js b/src/js/components/Track.js
index 26e68dec..c6e97007 100755
--- a/src/js/components/Track.js
+++ b/src/js/components/Track.js
@@ -39,27 +39,33 @@ export default class Track extends React.Component{
render(){
+ var className = 'track';
var selectedIcon = 'square-o';
if( typeof(this.props.track.selected) !== 'undefined' && this.props.track.selected ){
selectedIcon = 'check-square-o';
+ className += ' selected';
+ }
+
+ if( this.props.track.playing ){
+ className += ' playing';
}
return (
this.handleDoubleClick(e) }
onClick={ (e) => this.handleClick(e) }>
-
-
+
+
{this.props.track.name}
-
+
-
+
{ this.props.track.album ? : null }
-
+
{ this.formatDuration() }
diff --git a/src/js/index.js b/src/js/index.js
index a0441b1d..336043b2 100755
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -15,11 +15,14 @@ require('../scss/app.scss');
import App from './views/App'
import Album from './views/Album'
import Artist from './views/Artist'
+import Playlist from './views/Playlist'
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'
+
+import LibraryArtists from './views/library/LibraryArtists'
+import LibraryAlbums from './views/library/LibraryAlbums'
+import LibraryTracks from './views/library/LibraryTracks'
+import LibraryPlaylists from './views/library/LibraryPlaylists'
ReactDOM.render(
@@ -29,8 +32,11 @@ ReactDOM.render(
+
+
+
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
index 3f6fc59f..f8c88de5 100755
--- a/src/js/services/mopidy/actions.js
+++ b/src/js/services/mopidy/actions.js
@@ -24,7 +24,7 @@ export function disconnect(){
export function changeTrack( tlid ){
return {
- type: 'MOPIDY_CHANGE_TRACK',
+ type: 'MOPIDY_INSTRUCT',
call: 'playback.play',
value: { tlid: tlid }
}
@@ -38,11 +38,11 @@ export function playTracks( uris ){
}
export function enqueueTracks( uris, at_position = false ){
- if( typeof(uris) !== 'array' ) uris = [uris];
+ if( typeof(uris) !== 'object' ) uris = [uris];
var value = { uris: uris };
if( at_position ) value.at_position = at_position;
return {
- type: 'MOPIDY_ENQUEUE_TRACKS',
+ type: 'MOPIDY_INSTRUCT',
call: 'tracklist.add',
value: value
}
@@ -50,7 +50,7 @@ export function enqueueTracks( uris, at_position = false ){
export function removeTracks( tlids ){
return {
- type: 'MOPIDY_REMOVE_TRACKS',
+ type: 'MOPIDY_INSTRUCT',
call: 'tracklist.remove',
value: { tlid: tlids }
}
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js
index 3623f954..503fcc58 100755
--- a/src/js/services/mopidy/middleware.js
+++ b/src/js/services/mopidy/middleware.js
@@ -19,7 +19,10 @@ const MopidyMiddleware = (function(){
instruct( ws, store, 'tracklist.getRandom' );
instruct( ws, store, 'tracklist.getRepeat' );
instruct( ws, store, 'tracklist.getTlTracks' );
- instruct( ws, store, 'playback.getCurrentTlTrack' );
+ instruct( ws, store, 'playback.getCurrentTlTrack' )
+ .then( currentTlTrack => {
+ store.dispatch({ type: 'MOPIDY_HIGHLIGHT_CURRENT_TLTRACK', data: currentTlTrack })
+ });
break;
case 'state:offline':
@@ -27,14 +30,20 @@ const MopidyMiddleware = (function(){
break;
case 'event:tracklistChanged':
- instruct( ws, store, 'tracklist.getTlTracks' );
+ instruct( ws, store, 'tracklist.getTlTracks' )
+ .then( currentTlTrack => {
+ store.dispatch({ type: 'MOPIDY_HIGHLIGHT_CURRENT_TLTRACK', data: currentTlTrack })
+ });
break;
//case 'event:trackPlaybackEnded':
case 'event:playbackStateChanged':
case 'event:trackPlaybackStarted':
instruct( ws, store, 'playback.getState' );
- instruct( ws, store, 'playback.getCurrentTlTrack' );
+ instruct( ws, store, 'playback.getCurrentTlTrack' )
+ .then( currentTlTrack => {
+ store.dispatch({ type: 'MOPIDY_HIGHLIGHT_CURRENT_TLTRACK', data: currentTlTrack })
+ });
break;
case 'event:volumeChanged':
@@ -122,8 +131,6 @@ const MopidyMiddleware = (function(){
// send an instruction to the websocket
case 'MOPIDY_INSTRUCT':
- case 'MOPIDY_CHANGE_TRACK':
- case 'MOPIDY_REMOVE_TRACKS':
instruct( socket, store, action.call, action.value )
break;
diff --git a/src/js/services/mopidy/reducer.js b/src/js/services/mopidy/reducer.js
index 78f39c81..c47e1025 100755
--- a/src/js/services/mopidy/reducer.js
+++ b/src/js/services/mopidy/reducer.js
@@ -16,9 +16,20 @@ export default function reducer(mopidy = {}, action){
case 'MOPIDY_CHANGE_TRACK':
return Object.assign({}, mopidy, {
- tlid: action.tlid
+ tlid: action.tlid
});
+ case 'MOPIDY_HIGHLIGHT_CURRENT_TLTRACK':
+ for( var i = 0; i < mopidy.tracks.length; i++ ){
+ if( mopidy.tracks[i].tlid == action.data.tlid ){
+ action.data.track.playing = true;
+ Object.assign(mopidy.tracks[i], action.data);
+ }else{
+ mopidy.tracks[i].track.playing = false;
+ }
+ }
+ return Object.assign({}, mopidy, { tracks: mopidy.tracks });
+
/**
* Websocket-initiated actions
**/
diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js
index ef3dc6f4..b6eff556 100755
--- a/src/js/services/spotify/actions.js
+++ b/src/js/services/spotify/actions.js
@@ -74,6 +74,49 @@ export function removeAuthorization(){
return { type: 'SPOTIFY_REMOVE_AUTHORIZATION' }
}
+export function refreshToken(){
+ return (dispatch, getState) => {
+
+ dispatch({ type: 'SPOTIFY_TOKEN_REFRESHING' });
+
+ $.ajax({
+ method: 'GET',
+ url: '//jamesbarnsley.co.nz/spotmop.php?action=refresh&refresh_token='+getState().spotify.refresh_token,
+ dataType: "json",
+ timeout: 10000
+ })
+ .then( response => {
+
+ response.expires = new Date().getTime() + 3600000;
+
+ dispatch({
+ type: 'SPOTIFY_TOKEN_REFRESHED',
+ data: response
+ });
+ });
+ }
+}
+
+
+/**
+ * Get current user
+ **/
+export function getMe(){
+ return (dispatch, getState) => {
+
+ // flush out the previous store value
+ dispatch({ type: 'SPOTIFY_ME_LOADED', data: false });
+
+ sendRequest( getState().spotify.access_token, 'me' )
+ .then( response => {
+ dispatch({
+ type: 'SPOTIFY_ME_LOADED',
+ data: response
+ });
+ });
+ }
+}
+
/**
* Get a single artist
@@ -86,7 +129,6 @@ export function getArtist( uri ){
// 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
@@ -95,12 +137,12 @@ export function getArtist( uri ){
// get both the artist and the top tracks
$.when(
- sendRequest( false, 'artists/'+id )
+ sendRequest( false, 'artists/'+ getFromUri('artistid', uri) )
.then( response => {
Object.assign(artist, response);
}),
- sendRequest( false, 'artists/'+id+'/top-tracks?country='+getState().spotify.country )
+ sendRequest( false, 'artists/'+ getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country )
.then( response => {
Object.assign(artist, response);
})
@@ -116,7 +158,7 @@ export function getArtist( uri ){
export function getArtistAlbums( uri ){
return dispatch => {
- sendRequest( false, 'artists/'+ uri.replace('spotify:artist:','') +'/albums' )
+ sendRequest( false, 'artists/'+ getFromUri('artistid', uri) +'/albums' )
.then( response => {
dispatch({
type: 'SPOTIFY_ARTIST_ALBUMS_LOADED',
@@ -132,15 +174,15 @@ export function getArtistAlbums( uri ){
* @oaram uri string
**/
export function getAlbum( uri ){
- return dispatch => {
+ return dispatch => {
- // flush out the previous store value
+ // flush out the previous store value
dispatch({ type: 'SPOTIFY_ALBUM_LOADED', data: false });
- sendRequest( false, 'albums/'+ uri.replace('spotify:album:','') )
+ sendRequest( false, 'albums/'+ getFromUri('albumid', uri) )
.then( response => {
- // inject the parent album object into each track for consistent track objects
+ // 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,
@@ -149,7 +191,28 @@ export function getAlbum( uri ){
}
dispatch({
- type: 'SPOTIFY_ALBUM_LOADED',
+ type: 'SPOTIFY_ALBUM_LOADED',
+ data: response
+ });
+ });
+ }
+}
+
+/**
+ * Single playlist
+ *
+ * @oaram uri string
+ **/
+export function getPlaylist( uri ){
+ return (dispatch, getState) => {
+
+ // flush out the previous store value
+ dispatch({ type: 'SPOTIFY_PLAYLIST_LOADED', data: false });
+
+ sendRequest( getState().spotify.access_token, 'users/'+ getFromUri('userid',uri) +'/playlists/'+ getFromUri('playlistid',uri) +'?market='+getState().spotify.country )
+ .then( response => {
+ dispatch({
+ type: 'SPOTIFY_PLAYLIST_LOADED',
data: response
});
});
@@ -157,6 +220,22 @@ export function getAlbum( uri ){
}
+export function getLibraryPlaylists(){
+ return (dispatch, getState) => {
+
+ dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', data: false });
+
+ sendRequest( getState().spotify.access_token, 'me/playlists' )
+ .then( response => {
+ dispatch({
+ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED',
+ data: response
+ });
+ });
+ }
+}
+
+
export function getLibraryArtists(){
return (dispatch, getState) => {
@@ -193,7 +272,7 @@ export function getLibraryTracks(){
dispatch({ type: 'SPOTIFY_LIBRARY_TRACKS_LOADED', data: false });
- sendRequest( getState().spotify.access_token, 'me/tracks' )
+ sendRequest( getState().spotify.access_token, 'me/tracks?limit=50' )
.then( response => {
dispatch({
type: 'SPOTIFY_LIBRARY_TRACKS_LOADED',
@@ -202,3 +281,47 @@ export function getLibraryTracks(){
});
}
}
+
+
+
+
+
+/**
+ * Get an element from a URI
+ * @param element = string, the element we wish to extract
+ * @param uri = string
+ **/
+function getFromUri( element, uri ){
+ var exploded = uri.split(':');
+ if( element == 'userid' && exploded[1] == 'user' )
+ return exploded[2];
+ if( element == 'playlistid' && exploded[3] == 'playlist' )
+ return exploded[4];
+ if( element == 'artistid' && exploded[1] == 'artist' )
+ return exploded[2];
+ if( element == 'albumid' && exploded[1] == 'album' )
+ return exploded[2];
+ if( element == 'trackid' && exploded[1] == 'track' )
+ return exploded[2];
+ return null;
+}
+
+/**
+ * Identify what kind of asset a URI is (playlist, album, etc)
+ * @param uri = string
+ * @return string
+ **/
+function uriType( uri ){
+ var exploded = uri.split(':');
+ if( exploded[0] == 'spotify' && exploded[1] == 'track' )
+ return 'track';
+ if( exploded[0] == 'spotify' && exploded[1] == 'artist' )
+ return 'artist';
+ if( exploded[0] == 'spotify' && exploded[1] == 'album' )
+ return 'album';
+ if( exploded[0] == 'spotify' && exploded[1] == 'user' && exploded[3] == 'playlist' )
+ return 'playlist';
+ if( exploded[0] == 'spotify' && exploded[1] == 'user' && exploded.length == 3 )
+ return 'user';
+ return null;
+}
\ No newline at end of file
diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js
index 3559f597..9ec1ac35 100755
--- a/src/js/services/spotify/middleware.js
+++ b/src/js/services/spotify/middleware.js
@@ -3,54 +3,30 @@ import actions from './actions'
const SpotifyMiddleware = (function(){
- /**
- * 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 );
- }
-
/**
* The actual middleware inteceptor
**/
return store => next => action => {
-
+ return next(action);
+ /*
var state = store.getState();
switch(action.type){
- case 'SPOTIFY_AUTHORIZATION_GRANTED':
- sendRequest( action.data.access_token, 'me' )
- .then( (response) => {
- var data = action.data;
- data.me = response;
- store.dispatch({ type: 'SPOTIFY_AUTHORIZATION_COMPLETE', data: data })
- });
+ case 'SPOTIFY_AUTHORIZATION_GRANTEDXXX':
+
+ // proceed as usual
+ next(action)
+
+ // now we've been granted,
+ store.dispatch({ type: 'SPOTIFY_AUTHORIZATION_COMPLETE', data: data })
break;
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);
}
+ */
}
})();
diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js
index 21034d98..d5f2144e 100755
--- a/src/js/services/spotify/reducer.js
+++ b/src/js/services/spotify/reducer.js
@@ -5,16 +5,12 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_CONNECTING':
return Object.assign({}, spotify, { connected: false, connecting: true });
- case 'SPOTIFY_START_AUTHORIZATION':
- return Object.assign({}, spotify, { authorizing: true });
-
- case 'SPOTIFY_AUTHORIZATION_COMPLETE':
+ case 'SPOTIFY_AUTHORIZATION_GRANTED':
return Object.assign({}, spotify, {
authorizing: false,
authorized: true,
authorization: action.data,
- me: action.data.me,
- token: action.data.access_token
+ access_token: action.data.access_token
});
case 'SPOTIFY_REMOVE_AUTHORIZATION':
@@ -22,16 +18,32 @@ export default function reducer(spotify = {}, action){
authorizing: false,
authorization: false,
authorized: false,
- token: false,
+ access_token: false,
me: false
});
case 'SPOTIFY_CONNECTED':
return Object.assign({}, spotify, { connected: true, connecting: false });
+ case 'SPOTIFY_TOKEN_REFRESHING':
+ return Object.assign({}, spotify, { refreshing_token: true });
+
+ case 'SPOTIFY_TOKEN_REFRESHED':
+ return Object.assign({}, spotify, {
+ refreshing_token: false,
+ authorization: action.data,
+ access_token: action.data.access_token
+ });
+
case 'SPOTIFY_DISCONNECTED':
return Object.assign({}, spotify, { connected: false, connecting: false });
+ case 'SPOTIFY_ME_LOADED':
+ return Object.assign({}, spotify, { me: action.data });
+
+ case 'SPOTIFY_PLAYLIST_LOADED':
+ return Object.assign({}, spotify, { playlist: action.data });
+
case 'SPOTIFY_ALBUM_LOADED':
return Object.assign({}, spotify, { album: action.data });
@@ -41,12 +53,18 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_ARTIST_ALBUMS_LOADED':
return Object.assign({}, spotify, { artist_albums: action.data });
+ case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
+ return Object.assign({}, spotify, { libraryPlaylists: 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 });
+ case 'SPOTIFY_LIBRARY_TRACKS_LOADED':
+ return Object.assign({}, spotify, { libraryTracks: action.data });
+
default:
return spotify
}
diff --git a/src/js/views/App.js b/src/js/views/App.js
index c7ea001f..42e05609 100755
--- a/src/js/views/App.js
+++ b/src/js/views/App.js
@@ -32,6 +32,7 @@ class App extends React.Component{
Now playing
Library: My artists
Library: My albums
+ Library: My tracks
Library: My playlists
Settings
diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js
new file mode 100755
index 00000000..893a7d4c
--- /dev/null
+++ b/src/js/views/Playlist.js
@@ -0,0 +1,59 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import TrackList from '../components/TrackList'
+import * as spotifyActions from '../services/spotify/actions'
+import * as mopidyActions from '../services/mopidy/actions'
+
+class Playlist extends React.Component{
+
+ constructor(props) {
+ super(props);
+ }
+
+ // on render
+ componentDidMount(){
+ this.props.spotifyActions.getPlaylist( this.props.params.uri );
+ }
+
+ // when props changed
+ componentWillReceiveProps( nextProps ){
+ if( nextProps.params.uri != this.props.params.uri ){
+ this.props.spotifyActions.getPlaylist( nextProps.params.uri );
+ }
+ }
+
+ render(){
+ if( this.props.spotify.playlist ){
+ return (
+
+
{ this.props.spotify.playlist.name }
+
+
+ );
+ }
+ 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)(Playlist)
\ No newline at end of file
diff --git a/src/js/views/LibraryAlbums.js b/src/js/views/library/LibraryAlbums.js
similarity index 80%
rename from src/js/views/LibraryAlbums.js
rename to src/js/views/library/LibraryAlbums.js
index 714f8ae4..c29a137a 100755
--- a/src/js/views/LibraryAlbums.js
+++ b/src/js/views/library/LibraryAlbums.js
@@ -4,10 +4,10 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
-import AlbumGrid from '../components/AlbumGrid'
+import AlbumGrid from '../../components/AlbumGrid'
-import * as mopidyActions from '../services/mopidy/actions'
-import * as spotifyActions from '../services/spotify/actions'
+import * as mopidyActions from '../../services/mopidy/actions'
+import * as spotifyActions from '../../services/spotify/actions'
class LibraryAlbums extends React.Component{
diff --git a/src/js/views/LibraryArtists.js b/src/js/views/library/LibraryArtists.js
similarity index 86%
rename from src/js/views/LibraryArtists.js
rename to src/js/views/library/LibraryArtists.js
index 13090caf..70b789f0 100755
--- a/src/js/views/LibraryArtists.js
+++ b/src/js/views/library/LibraryArtists.js
@@ -4,8 +4,8 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
-import * as mopidyActions from '../services/mopidy/actions'
-import * as spotifyActions from '../services/spotify/actions'
+import * as mopidyActions from '../../services/mopidy/actions'
+import * as spotifyActions from '../../services/spotify/actions'
class LibraryArtists extends React.Component{
diff --git a/src/js/views/library/LibraryPlaylists.js b/src/js/views/library/LibraryPlaylists.js
new file mode 100755
index 00000000..41890ddb
--- /dev/null
+++ b/src/js/views/library/LibraryPlaylists.js
@@ -0,0 +1,59 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+import { Link } from 'react-router'
+
+import * as mopidyActions from '../../services/mopidy/actions'
+import * as spotifyActions from '../../services/spotify/actions'
+
+class LibraryPlaylists extends React.Component{
+
+ constructor(props) {
+ super(props);
+ }
+
+ // on render
+ componentDidMount(){
+ this.props.spotifyActions.getLibraryPlaylists();
+ }
+
+ render(){
+ if( this.props.spotify.libraryPlaylists ){
+ return (
+
+
My playlists
+
+ {
+ this.props.spotify.libraryPlaylists.items.map( (playlist, index) => {
+ var link = '/playlist/' + playlist.uri;
+ return - { playlist.name }
+ })
+ }
+
+
+ );
+ }
+ 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)(LibraryPlaylists)
\ No newline at end of file
diff --git a/src/js/views/LibraryTracks.js b/src/js/views/library/LibraryTracks.js
similarity index 71%
rename from src/js/views/LibraryTracks.js
rename to src/js/views/library/LibraryTracks.js
index b78ea278..2c58db9c 100755
--- a/src/js/views/LibraryTracks.js
+++ b/src/js/views/library/LibraryTracks.js
@@ -4,10 +4,10 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
-import TrackList from '../components/TrackList'
+import TrackList from '../../components/TrackList'
-import * as mopidyActions from '../services/mopidy/actions'
-import * as spotifyActions from '../services/spotify/actions'
+import * as mopidyActions from '../../services/mopidy/actions'
+import * as spotifyActions from '../../services/spotify/actions'
class LibraryTracks extends React.Component{
@@ -21,11 +21,11 @@ class LibraryTracks extends React.Component{
}
render(){
- if( this.props.spotify.libraryAlbums ){
+ if( this.props.spotify.libraryTracks ){
return (
My tracks
-
+
);
}
diff --git a/src/scss/app.scss b/src/scss/app.scss
index 7aa20635..7a480bca 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -1,6 +1,7 @@
@import 'vendor/font-awesome/index';
+@import 'global/core';
@import 'global/reset';
@import 'global/forms';
diff --git a/src/scss/components/_track.scss b/src/scss/components/_track.scss
index 3f54a363..3b05b48b 100755
--- a/src/scss/components/_track.scss
+++ b/src/scss/components/_track.scss
@@ -8,13 +8,28 @@
-ms-user-select: none;
user-select: none;
- .name,
- .artists,
- .album,
- .duration {
- width: 20%;
+ position: relative;
+ padding: 8px 10px 8px 30px;
+ border-top: 1px solid #EEEEEE;
+ border-bottom: 1px solid #EEEEEE;
+ margin-bottom: -1px;
+
+ .col {
+ width: 10%;
min-height: 1px;
float: left;
+
+ &.name,
+ &.artists,
+ &.album {
+ width: 30%;
+ }
+ }
+
+ .select-state {
+ position: absolute;
+ top: 10px;
+ left: 10px;
}
&::after {
@@ -22,4 +37,17 @@
clear: both;
display: block;
}
+
+ &.playing {
+ font-weight: bold;
+ }
+
+ &.selected,
+ &.selected:hover {
+ background: #FFF39C;
+ }
+
+ &:hover{
+ background: #F4F4F4;
+ }
}
\ No newline at end of file
diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss
new file mode 100755
index 00000000..bcb5b722
--- /dev/null
+++ b/src/scss/global/_core.scss
@@ -0,0 +1,14 @@
+
+
+body,
+html {
+ background: #FFFFFF;
+ color: #000000;
+ font-family: 'Roboto Condensed', Helvetica, Arial, sans-serif;
+ font-size: 14px;
+}
+
+body {
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
\ No newline at end of file