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 ( - + ); }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