diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js index ad58a8d4..f38007b5 100755 --- a/src/js/components/DebugInfo.js +++ b/src/js/components/DebugInfo.js @@ -13,6 +13,41 @@ class DebugInfo extends React.Component{ super(props); } + renderLoadQueue(){ + if (!this.props.ui.load_queue){ + return null + } + + var load_queue = this.props.ui.load_queue + var queue = [] + + return ( +
+ {queue} +
+ ) + } + + renderLoadQueue(){ + if (!this.props.ui.load_queue){ + return null + } + + var load_queue = this.props.ui.load_queue + var queue = [] + for (var key in load_queue){ + if (load_queue.hasOwnProperty(key)){ + queue.push(
{load_queue[key]}
) + } + } + + return ( +
+ {queue} +
+ ) + } + render(){ var touch_state = 'no touch' if (this.props.ui.emulate_touch){ @@ -41,6 +76,7 @@ class DebugInfo extends React.Component{
{touch_state}
+ {this.renderLoadQueue()} ); } diff --git a/src/js/components/Loader.js b/src/js/components/Loader.js new file mode 100755 index 00000000..e5659129 --- /dev/null +++ b/src/js/components/Loader.js @@ -0,0 +1,35 @@ + +import React, { PropTypes } from 'react' +import FontAwesome from 'react-fontawesome' + +export default class Loader extends React.Component{ + + constructor(props) { + super(props) + } + + render(){ + if (!this.props.load_queue){ + return null + } + + var load_queue = this.props.load_queue + var is_loading = false + for (var key in load_queue){ + if (load_queue.hasOwnProperty(key)){ + is_loading = true + break + } + } + + if (is_loading){ + return ( +
+ ) + } else { + return ( +
+ ) + } + } +} \ No newline at end of file diff --git a/src/js/services/lastfm/actions.js b/src/js/services/lastfm/actions.js index ca43ba06..4f3425dd 100755 --- a/src/js/services/lastfm/actions.js +++ b/src/js/services/lastfm/actions.js @@ -1,5 +1,6 @@ -var helpers = require('../../helpers.js') +var uiActions = require('../ui/actions') +var helpers = require('../../helpers') /** * Send an ajax request to the Spotify API @@ -13,13 +14,20 @@ const sendRequest = ( dispatch, getState, params ) => { var url = '//ws.audioscrobbler.com/2.0/?format=json&api_key=4320a3ef51c9b3d69de552ac083c55e3&'+params + var loader_key = helpers.generateGuid() + dispatch(uiActions.startLoading(loader_key, 'lastfm_'+params)) + $.ajax({ method: 'GET', cache: true, url: url }).then( - response => resolve(response), + response => { + dispatch(uiActions.stopLoading(loader_key)) + resolve(response) + }, (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)) console.error( params+' failed', xhr.responseText) reject(error) } diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 1bd6f11d..5dc8a0c9 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -115,15 +115,20 @@ const MopidyMiddleware = (function(){ property = property.replace('get',''); property = property.replace('set',''); + var loader_key = helpers.generateGuid() + store.dispatch(uiActions.startLoading(loader_key, 'mopidy_'+property)) + mopidyObject( value ) .then( response => { - store.dispatch({ type: 'MOPIDY_'+property.toUpperCase(), call: call, data: response }); - resolve(response); + store.dispatch(uiActions.stopLoading(loader_key)) + store.dispatch({ type: 'MOPIDY_'+property.toUpperCase(), call: call, data: response }) + resolve(response) }, error => { + store.dispatch(uiActions.stopLoading(loader_key)) console.error(error) - reject(error); + reject(error) } ); }); diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index b90871a3..64990a5b 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -6,19 +6,11 @@ var spotifyActions = require('../spotify/actions.js') const PusherMiddleware = (function(){ - // container for the actual Mopidy socket - var socket = null; - var deferredRequests = []; + // container for the actual websocket + var socket = null - const resolveRequest = (requestId, message ) => { - var response = JSON.parse( message ); - deferredRequests[request_id].resolve( response ); - delete deferredRequests[request_id]; - } - - const rejectRequest = (requestId, message) => { - deferredRequests[requestId].reject( message ); - } + // requests pending + var deferredRequests = [] // handle all manner of socket messages const handleMessage = (ws, store, message) => { @@ -29,6 +21,7 @@ const PusherMiddleware = (function(){ // response to a request [we] made if (typeof(message.request_id) !== 'undefined' && message.request_id){ if (typeof( deferredRequests[ message.request_id ]) !== 'undefined' ){ + store.dispatch(uiActions.stopLoading(message.request_id)) deferredRequests[ message.request_id ].resolve( message ) } else { console.error('Pusher: Response with no matching request', message); @@ -42,7 +35,7 @@ const PusherMiddleware = (function(){ } } - const request = (method, data = {}) => { + const request = (store, method, data = {}) => { return new Promise( (resolve, reject) => { var request_id = helpers.generateGuid() var message = { @@ -51,6 +44,8 @@ const PusherMiddleware = (function(){ request_id: request_id } socket.send( JSON.stringify(message) ) + + store.dispatch(uiActions.startLoading(request_id, 'pusher_'+method)) // add query to our deferred responses deferredRequests[request_id] = { @@ -101,7 +96,7 @@ const PusherMiddleware = (function(){ break; case 'PUSHER_CONNECTED': - request('get_config') + request(store, 'get_config') .then( response => { if (response.error){ @@ -120,7 +115,7 @@ const PusherMiddleware = (function(){ } } ) - request('get_version') + request(store, 'get_version') .then( response => { if (response.error){ @@ -131,7 +126,7 @@ const PusherMiddleware = (function(){ store.dispatch(response) } ) - request('get_radio') + request(store, 'get_radio') .then( response => { if (response.error){ @@ -159,7 +154,7 @@ const PusherMiddleware = (function(){ break case 'PUSHER_DELIVER_MESSAGE': - request('deliver_message', action.data) + request(store, 'deliver_message', action.data) .then( response => { store.dispatch( uiActions.createNotification('Message delivered') ) @@ -168,11 +163,11 @@ const PusherMiddleware = (function(){ break case 'PUSHER_DELIVER_BROADCAST': - request('broadcast', action.data) + request(store, 'broadcast', action.data) break case 'PUSHER_GET_QUEUE_METADATA': - request('get_queue_metadata') + request(store, 'get_queue_metadata') .then( response => { response.type = 'PUSHER_QUEUE_METADATA' @@ -182,7 +177,7 @@ const PusherMiddleware = (function(){ break; case 'PUSHER_ADD_QUEUE_METADATA': - request('add_queue_metadata', { + request(store, 'add_queue_metadata', { tlids: action.tlids, added_from: action.from_uri, added_by: store.getState().pusher.username @@ -190,7 +185,7 @@ const PusherMiddleware = (function(){ break; case 'PUSHER_START_UPGRADE': - request('upgrade') + request(store, 'upgrade') .then( response => { if (response.error){ @@ -212,7 +207,7 @@ const PusherMiddleware = (function(){ break; case 'PUSHER_SET_USERNAME': - request('set_username', { + request(store, 'set_username', { username: action.username }) .then( @@ -229,7 +224,7 @@ const PusherMiddleware = (function(){ break; case 'PUSHER_GET_CONNECTIONS': - request('get_connections') + request(store, 'get_connections') .then( response => { if (response.error){ @@ -281,7 +276,7 @@ const PusherMiddleware = (function(){ } // we don't need to wait for response, as change will be broadcast - request( 'start_radio', data ) + request(store, 'start_radio', data) break case 'PUSHER_RADIO_STARTED': @@ -304,7 +299,7 @@ const PusherMiddleware = (function(){ } // we don't need to wait for response, as change will be broadcast - request( 'stop_radio', data ) + request(store, 'stop_radio', data) break case 'PUSHER_RADIO_STOPPED': @@ -329,7 +324,7 @@ const PusherMiddleware = (function(){ break case 'PUSHER_DEBUG': - request( action.message.method, action.message.data ) + request(store, action.message.method, action.message.data ) .then( response => { store.dispatch({type: 'DEBUG', response: response}) diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 25e0cb40..ba3a2ff3 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -13,6 +13,7 @@ var helpers = require('../../helpers.js') * @return Promise **/ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false) => { + return new Promise( (resolve, reject) => { getToken( dispatch, getState ) .then( response => { @@ -34,11 +35,18 @@ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false // only if we've got data do we add it to the request (this prevents appending of "&false" to the URL) if (data) config.data = JSON.stringify(data) + // add reference to loader queue + var loader_key = helpers.generateGuid() + dispatch(uiActions.startLoading(loader_key, 'spotify_'+endpoint)) + $.ajax(config).then( response => { + dispatch(uiActions.stopLoading(loader_key)) resolve(response) }, (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)) + var message = xhr.responseText var response = JSON.parse(xhr.responseText) if (response.error && response.error.message) message = response.error.message diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 20beda49..85ba9a82 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -244,4 +244,21 @@ export function removeNotification( id ){ } +/** + * Loaders + **/ +export function startLoading(key,source){ + return { + type: 'START_LOADING', + source: source, + key: key + } +} + +export function stopLoading(key){ + return { + type: 'STOP_LOADING', + key: key + } +} diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index 61dfd73b..5950a4a1 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -725,6 +725,24 @@ export default function reducer(ui = {}, action){ return Object.assign({}, ui, { notifications: notifications }) + + /** + * Loader + **/ + + case 'START_LOADING': + var load_queue = Object.assign({}, (ui.load_queue ? ui.load_queue : {})) + load_queue[action.key] = action.source + return Object.assign({}, ui, {load_queue: load_queue}) + + case 'STOP_LOADING': + var load_queue = Object.assign({}, (ui.load_queue ? ui.load_queue : {})) + if (load_queue[action.key]){ + delete load_queue[action.key] + } + return Object.assign({}, ui, {load_queue: load_queue}) + + default: return ui } diff --git a/src/js/views/App.js b/src/js/views/App.js index 01b61921..b5c133db 100755 --- a/src/js/views/App.js +++ b/src/js/views/App.js @@ -12,6 +12,7 @@ import ContextMenu from '../components/ContextMenu' import Dragger from '../components/Dragger' import Modal from '../components/Modal/Modal' import Notifications from '../components/Notifications' +import Loader from '../components/Loader' import DebugInfo from '../components/DebugInfo' import * as helpers from '../helpers' @@ -157,6 +158,7 @@ class App extends React.Component{ {this.props.debug_info ? : null} + ); } @@ -170,6 +172,7 @@ class App extends React.Component{ const mapStateToProps = (state, ownProps) => { return { + load_queue: (state.ui.load_queue ? state.ui.load_queue : {}), mopidy_connected: state.mopidy.connected, spotify_authorized: state.spotify.authorized, play_state: state.mopidy.play_state, diff --git a/src/scss/components/_debug.scss b/src/scss/components/_debug.scss index 8e682756..4b11cc90 100755 --- a/src/scss/components/_debug.scss +++ b/src/scss/components/_debug.scss @@ -8,4 +8,5 @@ background: rgba(0,0,0,0.5); color: #FFFFFF; pointer-events: none; + width: 200px; } \ No newline at end of file diff --git a/src/scss/components/_loader.scss b/src/scss/components/_loader.scss index b50200e9..c75bf283 100755 --- a/src/scss/components/_loader.scss +++ b/src/scss/components/_loader.scss @@ -1,24 +1,28 @@ +.loader { + opacity: 0; + position: fixed; + bottom: 12px; + right: 12px; + margin: 0 auto; + width: 16px; + height: 16px; + border: { + radius: 100%; + style: solid; + color: $blue; + width: 2px; + } + border-top-color: transparent; + pointer-events: none; + + &.active { + opacity: 1; + @include animate(); + @include spin(); + } +} + .lazy-loader { padding: 20px 0 40px; - - .loader { - @include animate(); - opacity: 0; - margin: 0 auto; - width: 30px; - height: 30px; - border: { - radius: 100%; - style: solid; - color: $mid_grey; - width: 2px; - } - border-top-color: transparent; - } - - &.loading .loader { - @include spin(); - opacity: 1; - } } \ No newline at end of file