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