UI loaders where possible

This commit is contained in:
James Barnsley
2017-05-22 04:19:44 +12:00
parent ca2fbe7183
commit b3ab3c3a5c
18 changed files with 239 additions and 107 deletions

View File

@ -455,11 +455,19 @@ export let setWindowTitle = function (track = false, play_state = false){
* @param key = string (the string to lookup)
* @return boolean
**/
export let isLoading = function(load_queue = [], key = ''){
export let isLoading = function(load_queue = [], keys = []){
// Loop all of our load queue items
for (var load_queue_key in load_queue) {
// Make sure it's not a root object method
if (load_queue.hasOwnProperty(load_queue_key)){
if (load_queue[load_queue_key] == key){
return true
// Loop all the keys we're looking for
for (var i = 0; i < keys.length; i++){
if (load_queue[load_queue_key].startsWith(keys[i])){
return true
}
}
}
}

View File

@ -53,10 +53,17 @@ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false
},
(xhr, status, error) => {
dispatch(uiActions.stopLoading(loader_key))
// TODO: Catch 403 token_expired and force renewal
// Android Chrome mini-app doesn't seem to check date properly
// alert(error)
// Get the error message, jsson decode if necessary
var message = xhr.responseText
var response = JSON.parse(xhr.responseText)
if (response.error && response.error.message) message = response.error.message
if (response.error && response.error.message){
message = response.error.message
}
dispatch(uiActions.createNotification('Spotify: '+message,'bad'))
console.error( endpoint+' failed', response)
@ -78,15 +85,17 @@ function getToken( dispatch, getState ){
return new Promise( (resolve, reject) => {
// token is okay for now, so just resolve with the current token
if( getState().spotify.token_expiry && new Date().getTime() < getState().spotify.token_expiry ){
if (getState().spotify.token_expiry && new Date().getTime() < getState().spotify.token_expiry){
resolve(getState().spotify.access_token)
return
}
// token is expiring/expired, so go get a new one and resolve that
refreshToken( dispatch, getState )
refreshToken(dispatch, getState)
.then(
response => resolve(response.access_token),
response => {
resolve(response.access_token)
},
error => {
dispatch({ type: 'SPOTIFY_DISCONNECTED' })
reject(error)
@ -98,7 +107,7 @@ function getToken( dispatch, getState ){
function refreshToken( dispatch, getState ){
return new Promise( (resolve, reject) => {
if( getState().spotify.authorized ){
if (getState().spotify.authorized){
$.ajax({
method: 'GET',
@ -690,12 +699,13 @@ export function getRecommendations(uris = [], limit = 20){
}
// construct our endpoint URL with all the appropriate arguments
var data = 'seed_artists='+artists_ids.join(',')
data += '&seed_tracks='+tracks_ids.join(',')
data += '&seed_genres='+genres.join(',')
data += '&limit='+limit
var endpoint = 'recommendations'
endpoint += '?seed_artists='+artists_ids.join(',')
endpoint += '&seed_tracks='+tracks_ids.join(',')
endpoint += '&seed_genres='+genres.join(',')
endpoint += '&limit='+limit
sendRequest(dispatch, getState, 'recommendations', 'GET', data)
sendRequest(dispatch, getState, endpoint)
.then( response => {
// We only get simple artist objects, so we need to

View File

@ -96,81 +96,66 @@ class Album extends React.Component{
}
render(){
if (this.props.album){
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])
}
if (helpers.isLoading(this.props.load_queue,['spotify_albums/'+helpers.getFromUri('albumid',this.props.params.uri)])){
return (
<div className="body-loader">
<div className="loader"></div>
</div>
)
}
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 />
<Thumbnail size="large" canZoom images={ this.props.album.images } />
<div className="title">
<div className="source grey-text">
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} />&nbsp;
{helpers.uriSource( this.props.params.uri )}&nbsp;
{this.props.album.album_type ? this.props.album.album_type : 'album'}
</div>
<h1>{ this.props.album.name }</h1>
<ul className="details">
{ artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
{ this.props.album.release_date ? <li><Dater type="date" data={ this.props.album.release_date } /></li> : null }
<li>
{ this.props.album.tracks_total ? this.props.album.tracks_total : '0' } tracks,&nbsp;
{ this.props.album.tracks ? <Dater type="total-time" data={this.props.album.tracks} /> : '0 mins' }
</li>
</ul>
</div>
<div className="actions">
<button className="primary" onClick={e => this.play()}>Play</button>
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton className="secondary" uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.inLibrary()} /> : null }
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
</div>
<section className="list-wrapper">
{ this.props.album.tracks ? <TrackList className="album-track-list" tracks={ this.props.album.tracks } uri={this.props.params.uri} /> : null }
<LazyLoadListener enabled={this.props.album.tracks_more} loadMore={ () => this.loadMore() }/>
</section>
</div>
);
} else {
return (
<div className="view album-view">
<SidebarToggleButton />
<Thumbnail size="large" />
<div className="title">
<div className="source grey-text">
Album
</div>
<h1>
<span className="placeholder"></span>
</h1>
<ul className="details">
<li>
<span className="placeholder"></span>
</li>
</ul>
</div>
<div className="actions">
<button className="placeholder">&nbsp;</button>
</div>
</div>
);
}
return (
<div className="view album-view">
<SidebarToggleButton />
<Thumbnail size="large" canZoom images={ this.props.album.images } />
<div className="title">
<div className="source grey-text">
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} />&nbsp;
{helpers.uriSource( this.props.params.uri )}&nbsp;
{this.props.album.album_type ? this.props.album.album_type : 'album'}
</div>
<h1>{ this.props.album.name }</h1>
<ul className="details">
{ artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
{ this.props.album.release_date ? <li><Dater type="date" data={ this.props.album.release_date } /></li> : null }
<li>
{ this.props.album.tracks_total ? this.props.album.tracks_total : '0' } tracks,&nbsp;
{ this.props.album.tracks ? <Dater type="total-time" data={this.props.album.tracks} /> : '0 mins' }
</li>
</ul>
</div>
<div className="actions">
<button className="primary" onClick={e => this.play()}>Play</button>
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton className="secondary" uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.inLibrary()} /> : null }
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
</div>
<section className="list-wrapper">
{ this.props.album.tracks ? <TrackList className="album-track-list" tracks={ this.props.album.tracks } uri={this.props.params.uri} /> : null }
<LazyLoadListener enabled={this.props.album.tracks_more} loadMore={ () => this.loadMore() }/>
</section>
</div>
)
}
}
@ -183,6 +168,7 @@ class Album extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
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,

View File

@ -96,6 +96,14 @@ class Artist extends React.Component{
}
renderBody(){
if (helpers.isLoading(this.props.load_queue,['spotify_artists/'+helpers.getFromUri('artistid',this.props.params.uri), 'lastfm_method=artist.getInfo'])){
return (
<div className="body-loader">
<div className="loader"></div>
</div>
)
}
var scheme = helpers.uriSource( this.props.params.uri );
var related_artists = []
@ -179,7 +187,8 @@ class Artist extends React.Component{
}
render(){
var scheme = helpers.uriSource( this.props.params.uri );
var scheme = helpers.uriSource( this.props.params.uri )
if ( this.props.artist && this.props.artist.images ){
var image = helpers.sizedImages( this.props.artist.images ).huge
@ -211,9 +220,7 @@ class Artist extends React.Component{
{ this.renderSubViewMenu() }
</div>
</div>
{this.props.artist ? this.renderBody() : null}
{this.renderBody()}
</div>
);
@ -235,6 +242,7 @@ class Artist extends React.Component{
{ this.renderSubViewMenu() }
</div>
</div>
{this.renderBody()}
</div>
);
}
@ -250,6 +258,7 @@ class Artist extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
artist: (state.ui.artists && typeof(state.ui.artists[ownProps.params.uri]) !== 'undefined' ? state.ui.artists[ownProps.params.uri] : false ),
artists: (state.ui.artists ? state.ui.artists : []),
library_artists: (state.ui.library_artists ? state.ui.library_artists : []),

View File

@ -77,7 +77,7 @@ class Debug extends React.Component{
<label>
<input
type="checkbox"
name="emulate_touch"
name="log_actions"
checked={ this.props.log_actions }
onChange={ e => this.props.uiActions.set({ log_actions: !this.props.log_actions })} />
<span className="label">Log actions</span>
@ -85,7 +85,7 @@ class Debug extends React.Component{
<label>
<input
type="checkbox"
name="emulate_touch"
name="log_mopidy"
checked={ this.props.log_mopidy }
onChange={ e => this.props.uiActions.set({ log_mopidy: !this.props.log_mopidy })} />
<span className="label">Log Mopidy</span>
@ -93,7 +93,7 @@ class Debug extends React.Component{
<label>
<input
type="checkbox"
name="emulate_touch"
name="log_pusher"
checked={ this.props.log_pusher }
onChange={ e => this.props.uiActions.set({ log_pusher: !this.props.log_pusher })} />
<span className="label">Log Pusher</span>
@ -110,6 +110,17 @@ class Debug extends React.Component{
</div>
</form>
<h4 className="underline">Spotify</h4>
<div className="field">
<div className="name">Access token</div>
<div className="input">
<input
type="text"
readOnly
value={ this.props.access_token } />
</div>
</div>
<h4 className="underline">Mopidy</h4>
<form onSubmit={(e) => this.callMopidy(e)}>
<div className="field">
@ -178,7 +189,7 @@ class Debug extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
connection_id: state.pusher.connection_id,
emulate_touch: (state.ui.emulate_touch ? state.ui.emulate_touch : false),
access_token: (state.spotify.access_token ? state.spotify.access_token : ''),
log_actions: (state.ui.log_actions ? state.ui.log_actions : false),
log_pusher: (state.ui.log_pusher ? state.ui.log_pusher : false),
log_mopidy: (state.ui.log_mopidy ? state.ui.log_mopidy : false),

View File

@ -145,6 +145,16 @@ class Playlist extends React.Component{
}
render(){
/*
if (helpers.isLoading(this.props.load_queue,['spotify_users/'+ helpers.getFromUri('userid',this.props.params.uri)+'/playlists?'])){
return (
<div className="body-loader">
<div className="loader"></div>
</div>
)
}
*/
var scheme = helpers.uriSource( this.props.params.uri )
var context = 'playlist'
@ -224,6 +234,7 @@ const mapStateToProps = (state, ownProps) => {
var uri = ownProps.params.uri
uri = uri.replace(' ','%20')
return {
load_queue: state.ui.load_queue,
playlist: (state.ui.playlists && typeof(state.ui.playlists[uri]) !== 'undefined' ? state.ui.playlists[uri] : false ),
library_playlists: state.ui.library_playlists,
mopidy_connected: state.mopidy.connected,

View File

@ -11,7 +11,6 @@ import AlbumGrid from '../../components/AlbumGrid'
import TrackList from '../../components/TrackList'
import Parallax from '../../components/Parallax'
import AddSeedField from '../../components/AddSeedField'
import * as helpers from '../../helpers'
import * as spotifyActions from '../../services/spotify/actions'
@ -100,8 +99,8 @@ class Discover extends React.Component{
)
}
renderResults(){
if (helpers.isLoading(this.props.load_queue, 'spotify_recommendations')){
renderResults(){
if (helpers.isLoading(this.props.load_queue,['spotify_browse/recommendations'])){
return (
<div className="body-loader">
<div className="loader"></div>

View File

@ -5,7 +5,7 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import CategoryGrid from '../../components/CategoryGrid'
import * as helpers from '../../helpers'
import * as spotifyActions from '../../services/spotify/actions'
class DiscoverCategories extends React.Component{
@ -19,13 +19,24 @@ class DiscoverCategories extends React.Component{
}
render(){
if (!this.props.categories) return null
if (helpers.isLoading(this.props.load_queue,['spotify_browse/categories'])){
return (
<div className="view discover-categories-view">
<Header icon="grid" title="Genre / Mood" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
// convert categories object into simple array
var categories = []
for (var key in this.props.categories){
if (this.props.categories.hasOwnProperty(key)){
categories.push(this.props.categories[key])
if (this.props.categories){
for (var key in this.props.categories){
if (this.props.categories.hasOwnProperty(key)){
categories.push(this.props.categories[key])
}
}
}
@ -49,7 +60,8 @@ class DiscoverCategories extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
categories: state.ui.categories
categories: state.ui.categories,
load_queue: state.ui.load_queue
}
}

View File

@ -6,7 +6,7 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import PlaylistGrid from '../../components/PlaylistGrid'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as helpers from '../../helpers'
import * as spotifyActions from '../../services/spotify/actions'
class DiscoverCategory extends React.Component{
@ -34,7 +34,20 @@ class DiscoverCategory extends React.Component{
}
render(){
if (!this.props.category) return null
if (helpers.isLoading(this.props.load_queue,['spotify_browse/categories/'])){
return (
<div className="view discover-categories-view">
<Header icon="grid" title={(this.props.category ? this.props.category.name : 'Category')} />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
if (!this.props.category){
return null
}
var playlists = []
if (this.props.category.playlists_uris){
@ -67,6 +80,7 @@ class DiscoverCategory extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
playlists: state.ui.playlists,
category: (state.ui.categories && typeof(state.ui.categories['category:'+ownProps.params.id]) !== 'undefined' ? state.ui.categories['category:'+ownProps.params.id] : false )
}

View File

@ -5,7 +5,7 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import PlaylistGrid from '../../components/PlaylistGrid'
import * as helpers from '../../helpers'
import * as spotifyActions from '../../services/spotify/actions'
class DiscoverFeatured extends React.Component{
@ -20,6 +20,16 @@ class DiscoverFeatured extends React.Component{
}
render(){
if (helpers.isLoading(this.props.load_queue,['spotify_browse/featured-playlists'])){
return (
<div className="view discover-featured-view">
<Header icon="star" title="Featured playlists" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
var playlists = []
if (this.props.featured_playlists){
@ -51,6 +61,7 @@ class DiscoverFeatured extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
featured_playlists: state.spotify.featured_playlists,
playlists: state.ui.playlists
}

View File

@ -6,7 +6,7 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import AlbumGrid from '../../components/AlbumGrid'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as helpers from '../../helpers'
import * as uiActions from '../../services/ui/actions'
import * as spotifyActions from '../../services/spotify/actions'
@ -25,6 +25,16 @@ class DiscoverNewReleases extends React.Component{
}
render(){
if (helpers.isLoading(this.props.load_queue,['spotify_browse/new-releases'])){
return (
<div className="view discover-new-releases-view">
<Header icon="leaf" title="New Releases" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
var albums = []
if (this.props.new_releases){
@ -57,6 +67,7 @@ class DiscoverNewReleases extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
albums: state.ui.albums,
new_releases: state.ui.new_releases,
new_releases_more: state.ui.new_releases_more,

View File

@ -128,6 +128,17 @@ class LibraryAlbums extends React.Component{
}
render(){
if (helpers.isLoading(this.props.load_queue,['spotify_me/albums'])){
return (
<div className="view library-albums-view">
<Header icon="cd" title="My albums" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
var albums = []
if (this.props.library_albums && this.props.albums){
@ -207,6 +218,7 @@ class LibraryAlbums extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
view: state.ui.library_albums_view,
albums: state.ui.albums,
sort: (state.ui.library_albums_sort ? state.ui.library_albums_sort : 'name'),

View File

@ -90,6 +90,16 @@ class LibraryArtists extends React.Component{
}
render(){
if (helpers.isLoading(this.props.load_queue,['spotify_me/following?type=artist'])){
return (
<div className="view library-albums-view">
<Header icon="cd" title="My albums" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
var artists = []
if (this.props.library_artists && this.props.artists){
@ -157,6 +167,7 @@ class LibraryArtists extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
artists: state.ui.artists,
sort: (state.ui.library_artists_sort ? state.ui.library_artists_sort : 'name'),
sort_reverse: (state.ui.library_artists_sort_reverse ? true : false),

View File

@ -171,6 +171,7 @@ class LibraryLocalAlbums extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
mopidy_connected: state.mopidy.connected,
albums: state.ui.albums,
local_albums: state.ui.local_albums,

View File

@ -80,6 +80,7 @@ class LibraryLocalArtists extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
mopidy_connected: state.mopidy.connected,
local_artists: state.ui.local_artists,
artists: state.ui.artists

View File

@ -102,6 +102,7 @@ class LibraryLocalDirectory extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
mopidy_connected: state.mopidy.connected,
directory: state.mopidy.directory
}

View File

@ -43,7 +43,17 @@ class LibraryPlaylists extends React.Component{
}
renderView(){
if( !this.props.library_playlists || !this.props.playlists ) return null
if (helpers.isLoading(this.props.load_queue,['spotify_me/playlists'])){
return (
<div className="body-loader">
<div className="loader"></div>
</div>
)
}
if (!this.props.library_playlists || !this.props.playlists ){
return null
}
var playlists = []
for (var i = 0; i < this.props.library_playlists.length; i++){
@ -166,6 +176,7 @@ class LibraryPlaylists extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
view: state.ui.library_playlists_view,
sort: (state.ui.library_playlists_sort ? state.ui.library_playlists_sort : 'name'),
sort_reverse: (state.ui.library_playlists_sort_reverse ? true : false),

View File

@ -8,6 +8,7 @@ import TrackList from '../../components/TrackList'
import Header from '../../components/Header'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as helpers from '../../helpers'
import * as mopidyActions from '../../services/mopidy/actions'
import * as spotifyActions from '../../services/spotify/actions'
@ -27,6 +28,17 @@ class LibraryTracks extends React.Component{
}
render(){
if (helpers.isLoading(this.props.load_queue,['spotify_me/tracks'])){
return (
<div className="view library-tracks-view">
<Header icon="music" title="My tracks" />
<div className="body-loader">
<div className="loader"></div>
</div>
</div>
)
}
return (
<div className="view library-tracks-view">
<Header icon="music" title="My tracks" />
@ -48,6 +60,7 @@ class LibraryTracks extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
load_queue: state.ui.load_queue,
tracks: state.spotify.library_tracks,
tracks_more: state.spotify.library_tracks_more
}