diff --git a/src/js/components/Parallax.js b/src/js/components/Parallax.js index 662b26cf..af94cd06 100755 --- a/src/js/components/Parallax.js +++ b/src/js/components/Parallax.js @@ -56,7 +56,7 @@ export default class Parallax extends React.Component{ } componentWillReceiveProps( nextProps ){ - if( ( !this.props.url || nextProps.image != this.props.url ) && !this._loading ){ + if( ( !this.state.url || nextProps.image != this.state.url ) && !this._loading ){ this._loading = true this.setState({ url: nextProps.image, image: false, loading: true }) this.loadImage( nextProps.image ) diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js index 66dc631c..fc9e6c2f 100755 --- a/src/js/components/Thumbnail.js +++ b/src/js/components/Thumbnail.js @@ -6,41 +6,61 @@ export default class Thumbnail extends React.Component{ constructor(props) { super(props); + } - this.state = { - url: require('../../assets/no-image.svg') + // TODO: ascertain whether this is improving or hindering performance + // The UI appears to work perfectly fine without this + shouldComponentUpdate(nextProps, nextState){ + + // no images at all, and we already know it + if( !this.props.image && + !this.props.images && + !nextProps.image && + !nextProps.images ){ + return false } - } - componentDidMount(){ - this.mapImageSizes(); - } + // image changed + if( this.props.image && !nextProps.image ) return true + if( !this.props.image && nextProps.image ) return true + if( this.props.image != nextProps.image ) return true - componentWillReceiveProps( nextProps ){ - this.mapImageSizes( nextProps ); + // images array changed + if( this.props.images && !nextProps.images ) return true + if( !this.props.images && nextProps.images ) return true + if( this.props.images.length != nextProps.images.length ) return true + + // image item changed + var size = 'medium' + var images = helpers.sizedImages( nextProps.images ) + if( this.props.size ) size = this.props.size + if( this.props.images[size] != images[size] ) return true + + return false } mapImageSizes( props = this.props ){ // no images if( !this.props.image && !this.props.images ){ - this.setState({ url: require('../../assets/no-image.svg') }) + return require('../../assets/no-image.svg') // single image }else if( this.props.image ){ - this.setState({ url: this.props.image }) + return this.props.image // multiple images }else if( this.props.images && this.props.images.length > 0 ){ var images = helpers.sizedImages( this.props.images ) var size = 'medium' if( this.props.size ) size = this.props.size - this.setState({ url: images[size] }) + return images[size] } } render(){ - var style = { backgroundImage: 'url("'+this.state.url+'")' } + var image = this.mapImageSizes() + var style = { backgroundImage: 'url("'+image+'")' } var className = 'thumbnail '+this.props.size; if( this.props.circle ) className += ' circle'; diff --git a/src/js/components/Track.js b/src/js/components/Track.js index 8962878b..856f327a 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -20,6 +20,13 @@ export default class Track extends React.Component{ } } + handleTouchEnd(e){ + var target = $(e.target); + if( !target.is('a') && target.closest('a').length <= 0 ){ + this.props.handleTouchEnd(e); + } + } + handleContextMenu(e){ e.preventDefault(); this.props.handleContextMenu(e); @@ -46,7 +53,7 @@ export default class Track extends React.Component{
this.props.handleTouchStart(e) } - onTouchEnd={ e => this.props.handleTouchEnd(e) } + onTouchEnd={ e => this.handleTouchEnd(e) } onMouseDown={ e => this.handleMouseDown(e) } onMouseUp={ e => this.props.handleMouseUp(e) } onDoubleClick={ e => this.props.handleDoubleClick(e) } diff --git a/src/js/helpers.js b/src/js/helpers.js index 8d13767a..5307132d 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -257,24 +257,43 @@ export let createRange = function (indexes){ export let sortItems = function (array, property, reverse = false){ function compare(a,b) { - switch( typeof(a[property]) ){ + var a_value = a + var a_property_split = property.split('.') + for( var i = 0; i < a_property_split.length; i++ ){ + if( typeof(a_value[a_property_split[i]]) === 'undefined' ) return -1 + a_value = a_value[a_property_split[i]] + } - case 'boolean': - return a[property] - break + var b_value = b + var b_property_split = property.split('.') + for( var i = 0; i < b_property_split.length; i++ ){ + if( typeof(b_value[b_property_split[i]]) === 'undefined' ) return -1 + b_value = b_value[b_property_split[i]] + } - default: + if( typeof(a_value) === 'boolean'){ + return a_value - // both objects must have this property - if( typeof(a[property]) === 'undefined' || typeof(b[property]) === 'undefined' ) return 0 + }else if( typeof(a_value) === 'string'){ + if(a_value.toLowerCase() > b_value.toLowerCase()) return 1 + return -1 - if(a[property] > b[property]) return 1 - if(a[property] < b[property]) return -1 - return 0 + }else{ + if( parseInt(a_value) > parseInt(b_value) ) return 1 + return -1 } } var sorted = array.sort(compare) if( reverse ) sorted.reverse() return sorted +} + +/** + * Figure out if a value is a number + * @param data = mixed + * @return boolean + **/ +export let isNumeric = function (data) { + return !isNaN(parseFloat(data)) && isFinite(data) } \ No newline at end of file diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index fd329779..17bbf5b2 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -52,7 +52,7 @@ function getToken( dispatch, getState ){ // token is okay for now, so just resolve with the current token if( new Date().getTime() < getState().spotify.token_expiry ){ - resolve(getState().spotify.access_token); + resolve(getState().spotify.access_token) return } @@ -81,16 +81,17 @@ function refreshToken( dispatch, getState ){ }) .then( response => { - response.token_expiry = new Date().getTime() + ( response.expires_in * 1000 ); - response.source = 'spotify'; + response.token_expiry = new Date().getTime() + ( response.expires_in * 1000 ) + response.source = 'spotify' dispatch({ type: 'SPOTIFY_TOKEN_REFRESHED', provider: 'spotify-http-api', data: response - }); - resolve(response); + }) + resolve(response) }, error => { + dispatch({ type: 'SPOTIFY_DISCONNECTED' }) console.error('Could not refresh token', error) reject(error) } @@ -116,6 +117,7 @@ function refreshToken( dispatch, getState ){ resolve(response); }, error => { + dispatch({ type: 'SPOTIFY_DISCONNECTED' }) console.error('Could not refresh token', error) reject(error) } diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js index b30fa2ed..49feb114 100755 --- a/src/js/services/spotify/reducer.js +++ b/src/js/services/spotify/reducer.js @@ -4,10 +4,13 @@ export default function reducer(spotify = {}, action){ case 'SPOTIFY_CONNECT': case 'SPOTIFY_CONNECTING': - return Object.assign({}, spotify, { connected: false, connecting: true }); + return Object.assign({}, spotify, { connected: false, connecting: true }) case 'SPOTIFY_CONNECTED': - return Object.assign({}, spotify, { connected: true, connecting: false }); + return Object.assign({}, spotify, { connected: true, connecting: false }) + + case 'SPOTIFY_DISCONNECTED': + return Object.assign({}, spotify, { connected: false, connecting: false }) case 'PUSHER_SPOTIFY_TOKEN': if( spotify.authorized ) return spotify; @@ -17,7 +20,7 @@ export default function reducer(spotify = {}, action){ authorization: false, access_token: action.data.access_token, token_expiry: action.data.token_expiry - }); + }) case 'SPOTIFY_AUTHORIZATION_GRANTED': return Object.assign({}, spotify, { @@ -27,7 +30,7 @@ export default function reducer(spotify = {}, action){ access_token: action.data.access_token, refresh_token: action.data.refresh_token, token_expiry: action.data.token_expiry - }); + }) case 'SPOTIFY_AUTHORIZATION_REVOKED': return Object.assign({}, spotify, { @@ -38,45 +41,46 @@ export default function reducer(spotify = {}, action){ refresh_token: false, token_expiry: 0, me: false - }); + }) case 'SPOTIFY_TOKEN_REFRESHING': - return Object.assign({}, spotify, { refreshing_token: true }); + return Object.assign({}, spotify, { refreshing_token: true }) case 'SPOTIFY_TOKEN_REFRESHED': return Object.assign({}, spotify, { + connected: true, refreshing_token: false, authorization: action.data, access_token: action.data.access_token, token_expiry: action.data.token_expiry, provider: action.provider - }); + }) case 'SPOTIFY_DISCONNECTED': - return Object.assign({}, spotify, { connected: false, connecting: false }); + return Object.assign({}, spotify, { connected: false, connecting: false }) case 'SPOTIFY_ME_LOADED': - return Object.assign({}, spotify, { me: action.data }); + return Object.assign({}, spotify, { me: action.data }) case 'SPOTIFY_ARTISTS_LOADED': if( !action.data ) return Object.assign({}, spotify) return Object.assign({}, spotify, { artists: action.data.artists.items, artists_more: action.data.artists.next - }); + }) case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': if( !action.data ) return Object.assign({}, spotify) return Object.assign({}, spotify, { library_artists: action.data.artists.items, library_artists_more: action.data.artists.next - }); + }) case 'SPOTIFY_LIBRARY_ARTISTS_LOADED_MORE': 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) @@ -93,7 +97,7 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { library_albums: albums, library_albums_more: action.data.next - }); + }) case 'SPOTIFY_LIBRARY_ALBUMS_LOADED_MORE': var albums = [] @@ -109,7 +113,7 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { library_albums: [...spotify.library_albums, ...albums ], library_albums_more: action.data.next - }); + }) case 'SPOTIFY_LIBRARY_TRACKS_LOADED': if( !action.data ) return Object.assign({}, spotify) @@ -126,7 +130,7 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { library_tracks: tracks, library_tracks_more: action.data.next - }); + }) case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE': var tracks = Object.assign([], action.data.items) @@ -142,19 +146,19 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { library_tracks: [...spotify.library_tracks, ...tracks], library_tracks_more: action.data.next - }); + }) case 'SPOTIFY_FEATURED_PLAYLISTS_LOADED': - return Object.assign({}, spotify, { featured_playlists: action.data }); + return Object.assign({}, spotify, { featured_playlists: action.data }) case 'SPOTIFY_CATEGORIES_LOADED': - return Object.assign({}, spotify, { categories: action.data }); + return Object.assign({}, spotify, { categories: action.data }) case 'SPOTIFY_CATEGORY_LOADED': - return Object.assign({}, spotify, { category: action.data }); + return Object.assign({}, spotify, { category: action.data }) case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': - return Object.assign({}, spotify, { category_playlists: action.data }); + return Object.assign({}, spotify, { category_playlists: action.data }) case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE': return Object.assign({}, spotify, { category_playlists: { @@ -162,7 +166,7 @@ export default function reducer(spotify = {}, action){ next: action.data.next, previous: action.data.previous, items: [ ...spotify.category_playlists.items, ...action.data.items ] - }}); + }}) case 'SPOTIFY_NEW_RELEASES_LOADED': return Object.assign({}, spotify, { new_releases: action.data }); @@ -173,7 +177,7 @@ export default function reducer(spotify = {}, action){ next: action.data.albums.next, previous: action.data.albums.previous, items: [ ...spotify.new_releases.items, ...action.data.albums.items ] - }}); + }}) default: return spotify diff --git a/src/js/views/library/LibraryPlaylists.js b/src/js/views/library/LibraryPlaylists.js index 5522c425..589b42e8 100755 --- a/src/js/views/library/LibraryPlaylists.js +++ b/src/js/views/library/LibraryPlaylists.js @@ -21,12 +21,23 @@ class LibraryPlaylists extends React.Component{ super(props); } + setSort(value){ + var reverse = false + if( this.props.sort == value ) reverse = !this.props.sort_reverse + + var data = { + library_playlists_sort_reverse: reverse, + library_playlists_sort: value + } + this.props.uiActions.set(data) + } + renderView(){ if( !this.props.playlists ) return null var playlists = this.props.playlists if( this.props.sort ){ - playlists = helpers.sortItems(playlists, this.props.sort) + playlists = helpers.sortItems(playlists, this.props.sort, this.props.sort_reverse) } if( this.props.view == 'list' ){ @@ -96,7 +107,7 @@ class LibraryPlaylists extends React.Component{ var actions = (
- this.props.uiActions.set({ library_playlists_sort: value }) } /> + this.setSort(value) } /> this.props.uiActions.set({ library_playlists_view: value }) } />