diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js
index fcd4052b..6072c350 100755
--- a/src/js/bootstrap.js
+++ b/src/js/bootstrap.js
@@ -30,12 +30,17 @@ let reducers = combineReducers({
var initialState = {
core: {
current_tracklist: [],
- current_tltrack: false
+ current_tltrack: false,
+ albums: {},
+ users: {},
+ playlists: {},
+ artists: {}
},
ui: {
slim_mode: false,
selected_tracks: [],
- notifications: []
+ notifications: [],
+ processes: {}
},
mopidy: {
connected: false,
diff --git a/src/js/components/Notifications.js b/src/js/components/Notifications.js
index f0b0bf89..0c2ef5f7 100755
--- a/src/js/components/Notifications.js
+++ b/src/js/components/Notifications.js
@@ -48,14 +48,16 @@ export default class Notifications extends React.Component{
if (processes[key].cancelling){
items.push(
)
} else {
items.push(
-
this.props.uiActions.cancelProcess(key) } />
+
{ processes[key].content }
+ this.props.uiActions.cancelProcess(key) } />
)
}
diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js
index d85fe1eb..0d8c9f71 100755
--- a/src/js/components/Thumbnail.js
+++ b/src/js/components/Thumbnail.js
@@ -13,7 +13,7 @@ class Thumbnail extends React.Component{
constructor(props) {
super(props);
}
-
+/*
// TODO: ascertain whether this is improving or hindering performance
// The UI appears to work perfectly fine without this
shouldComponentUpdate(nextProps, nextState){
@@ -22,23 +22,24 @@ class Thumbnail extends React.Component{
if (!nextProps.image && !this.props.image && !nextProps.images && !this.props.images) return false
// 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
+ if (!this.props.image && nextProps.image) return true
+ if (this.props.image && nextProps.image) return true
+ if (this.props.image != nextProps.image) return true
// images array changed
- if( typeof(this.props.images) === 'undefined' && nextProps.images ) return true
- if( this.props.images && typeof(nextProps.images) === 'undefined' ) return true
- if( this.props.images.length != nextProps.images.length ) return true
+ if (this.props.images === undefined && nextProps.images ) return true
+ if (this.props.images && nextProps.images === undefined) return true
+ if (this.props.images && !nextProps.images || 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
+ if (this.props.size ) size = this.props.size
+ if (this.props.images[size] != images[size]) return true
return false
}
+ */
mapImageSizes( props = this.props ){
@@ -79,7 +80,7 @@ class Thumbnail extends React.Component{
return (
-
}>
+ }>
diff --git a/src/js/index.js b/src/js/index.js
index 1ca63716..3d07b0f5 100755
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -35,7 +35,6 @@ import LibraryTracks from './views/library/LibraryTracks'
import LibraryPlaylists from './views/library/LibraryPlaylists'
import LibraryBrowse from './views/library/LibraryBrowse'
import LibraryLocalArtists from './views/library/LibraryLocalArtists'
-import LibraryLocalAlbums from './views/library/LibraryLocalAlbums'
// setup our analytics tracking
ReactGA.initialize('UA-64701652-3');
@@ -76,7 +75,6 @@ ReactDOM.render(
-
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
index 8e3337bc..15d79d83 100755
--- a/src/js/services/mopidy/actions.js
+++ b/src/js/services/mopidy/actions.js
@@ -217,10 +217,11 @@ export function getAlbum( uri ){
}
}
-export function getAlbums( uris ){
+export function getAlbums(uris, processor = null){
return {
type: 'MOPIDY_GET_ALBUMS',
- uris: uris
+ uris: uris,
+ processor: processor
}
}
@@ -230,6 +231,18 @@ export function getLibraryAlbums(){
}
}
+export function runProcessor(processor){
+ return {
+ type: processor
+ }
+}
+
+export function cancelProcessor(processor){
+ return {
+ type: processor+'_CANCEL'
+ }
+}
+
/**
* Searching
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js
index 3b21f9bc..872638b7 100755
--- a/src/js/services/mopidy/middleware.js
+++ b/src/js/services/mopidy/middleware.js
@@ -840,22 +840,47 @@ const MopidyMiddleware = (function(){
.then( response => {
if (response.length <= 0) return
- var uris = helpers.arrayOf('uri',response)
-
- /*
- store.dispatch({
- type: 'MOPIDY_GET_ALBUMS',
- uris: uris//.slice(0,50) THIS PAGINATES REQUESTS. Slow servers might need this...
- });
- */
-
store.dispatch({
type: 'LIBRARY_ALBUMS_LOADED',
- uris: uris
+ uris: helpers.arrayOf('uri',response)
});
+
+ // Start our process to load the full album objects
+ store.dispatch(mopidyActions.runProcessor('MOPIDY_LIBRARY_ALBUMS_PROCESSOR'));
})
break;
+ case 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR':
+
+ if (store.getState().ui.processes['MOPIDY_LIBRARY_ALBUMS'] !== undefined){
+ var processor = store.getState().ui.processes['MOPIDY_LIBRARY_ALBUMS']
+
+ if (processor.cancelling){
+ store.dispatch(uiActions.stopProcess('MOPIDY_LIBRARY_ALBUMS'))
+ return false
+ }
+ }
+
+ store.dispatch(uiActions.startProcess('MOPIDY_LIBRARY_ALBUMS', 'Loading album library'))
+
+ // Figure out the remaining items
+ var library_uris = store.getState().core.library_albums
+ var to_load = []
+ for (var i = 0; i < library_uris.length && to_load.length < 50; i++){
+ var uri = library_uris[i]
+ if (helpers.uriSource(uri) == 'local' && store.getState().core.albums[uri] === undefined){
+ to_load.push(uri)
+ }
+ }
+
+ if (to_load.length > 0){
+ store.dispatch(mopidyActions.getAlbums(to_load, 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR'))
+ } else {
+ store.dispatch(uiActions.stopProcess('MOPIDY_LIBRARY_ALBUMS'))
+ }
+
+ break
+
case 'MOPIDY_GET_ALBUMS':
instruct( socket, store, 'library.lookup', { uris: action.uris } )
.then( response => {
@@ -883,7 +908,19 @@ const MopidyMiddleware = (function(){
store.dispatch({
type: 'ALBUMS_LOADED',
albums: albums
- });
+ })
+
+ // Re-run any consequential processes in 100ms. This allows a small window for other
+ // server requests before our next batch. It's a little crude but it means the server isn't
+ // locked until we're completely done.
+ if (action.processor){
+ setTimeout(
+ function(){
+ store.dispatch(mopidyActions.runProcessor(action.processor))
+ },
+ 100
+ )
+ }
})
break;
diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js
index 66ce6e40..a86195a0 100755
--- a/src/js/services/ui/middleware.js
+++ b/src/js/services/ui/middleware.js
@@ -126,6 +126,9 @@ const UIMiddleware = (function(){
if (action.key == 'MOPIDY_ENQUEUE_URIS'){
store.dispatch(mopidyActions.enqueueURIsCancel())
}
+ if (action.key == 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR'){
+ store.dispatch(mopidyActions.cancelProcessor(action.key))
+ }
next(action)
break
diff --git a/src/js/views/library/LibraryLocalAlbums.js b/src/js/views/library/LibraryLocalAlbums.js
deleted file mode 100755
index 3ad190ad..00000000
--- a/src/js/views/library/LibraryLocalAlbums.js
+++ /dev/null
@@ -1,203 +0,0 @@
-
-import React, { PropTypes } from 'react'
-import { connect } from 'react-redux'
-import { bindActionCreators } from 'redux'
-
-import AlbumGrid from '../../components/AlbumGrid'
-import Header from '../../components/Header'
-import DropdownField from '../../components/DropdownField'
-import List from '../../components/List'
-import LazyLoadListener from '../../components/LazyLoadListener'
-
-import * as helpers from '../../helpers'
-import * as uiActions from '../../services/ui/actions'
-import * as mopidyActions from '../../services/mopidy/actions'
-import * as spotifyActions from '../../services/spotify/actions'
-
-class LibraryLocalAlbums extends React.Component{
-
- constructor(props) {
- super(props);
- }
-
- componentDidMount(){
- this.loadAlbums()
- }
-
- componentWillReceiveProps( nextProps ){
- if (!this.props.mopidy_connected && nextProps.mopidy_connected){
- this.loadAlbums(nextProps);
- }
- }
-
- loadAlbums(props = this.props){
- if (props.mopidy_connected && !this.props.local_albums){
- this.props.mopidyActions.getLibraryAlbums();
- }
- }
-
- moreURIsToLoad(){
- var uris = []
- if (this.props.albums && this.props.local_albums){
- for (var i = 0; i < this.props.local_albums.length; i++){
- var uri = this.props.local_albums[i]
- if (!this.props.albums.hasOwnProperty(uri)){
- uris.push(uri)
- }
-
- // limit each lookup to 50 URIs
- if (uris.length >= 50) break
- }
- }
-
- return uris
- }
-
- loadMore(){
- var uris = this.moreURIsToLoad()
- this.props.mopidyActions.getAlbums(uris)
- }
-
- setSort(value){
- var reverse = false
- if( this.props.sort == value ) reverse = !this.props.sort_reverse
-
- var data = {
- library_local_albums_sort_reverse: reverse,
- library_local_albums_sort: value
- }
- this.props.uiActions.set(data)
- }
-
- renderView(albums){
- if( this.props.view == 'list' ){
-
- var columns = [
- {
- label: 'Name',
- name: 'name',
- width: 40
- },
- {
- label: 'Artists',
- name: 'artists',
- width: 30
- },
- {
- label: 'Tracks',
- name: 'tracks_total',
- width: 15
- }
- ]
-
- return (
-
- )
- }else{
- return (
-
- )
- }
- }
-
- render(){
- var albums = []
- if (this.props.albums && this.props.local_albums){
- for (var i = 0; i < this.props.local_albums.length; i++){
- var uri = this.props.local_albums[i]
- if (this.props.albums.hasOwnProperty(uri)){
- albums.push(this.props.albums[uri])
- }
- }
-
- if( this.props.sort ){
- albums = helpers.sortItems(albums, this.props.sort, this.props.sort_reverse)
- }
- }
-
- var view_options = [
- {
- value: 'thumbnails',
- label: 'Thumbnails'
- },
- {
- value: 'list',
- label: 'List'
- }
- ]
-
- var sort_options = [
- {
- value: 'name',
- label: 'Name'
- },
- {
- value: 'tracks_total',
- label: 'Tracks'
- }
- ]
-
- var options = (
-
- {this.setSort(val); this.props.uiActions.hideContextMenu() }} />
- {this.props.uiActions.set({ library_local_albums_view: val }); this.props.uiActions.hideContextMenu() }} />
-
- )
-
- if (albums.length <= 0 && helpers.isLoading(this.props.load_queue,['mopidy_lookup','mopidy_browse'])){
- return (
-
- )
- }
-
- return (
-
-
- {this.renderView(albums)}
- 0)} loadMore={ () => this.loadMore() }/>
-
- )
- }
-}
-
-
-/**
- * Export our component
- *
- * We also integrate our global store, using connect()
- **/
-
-const mapStateToProps = (state, ownProps) => {
- return {
- load_queue: state.ui.load_queue,
- mopidy_connected: state.mopidy.connected,
- albums: state.core.albums,
- local_albums: state.core.local_albums,
- view: state.ui.library_local_albums_view,
- sort: (state.ui.library_local_albums_sort ? state.ui.library_local_albums_sort : 'name'),
- sort_reverse: (state.ui.library_local_albums_sort_reverse ? true : false)
- }
-}
-
-const mapDispatchToProps = (dispatch) => {
- return {
- uiActions: bindActionCreators(uiActions, dispatch),
- mopidyActions: bindActionCreators(mopidyActions, dispatch),
- spotifyActions: bindActionCreators(spotifyActions, dispatch)
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(LibraryLocalAlbums)
\ No newline at end of file
diff --git a/src/scss/components/_loader.scss b/src/scss/components/_loader.scss
index 77b0b4e2..861a24b1 100755
--- a/src/scss/components/_loader.scss
+++ b/src/scss/components/_loader.scss
@@ -1,4 +1,44 @@
+.loader {
+ height: 120px;
+ width: 120px;
+ margin: 0 auto;
+ position: relative;
+ opacity: 0;
+
+ &:before,
+ &:after {
+ content: '';
+ display: block;
+ margin: 0 auto;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ border: {
+ radius: 100%;
+ style: solid;
+ width: 2px;
+ }
+ }
+
+ &:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ border: {
+ color: lighten($dark_grey, 5%);
+ }
+ }
+
+ &:after {
+ @include spin();
+ border-top-color: transparent;
+ border-left-color: transparent;
+ border-bottom-color: transparent;
+ border-right-color: $blue;
+ }
+}
+
.body-loader {
padding: 35vh 0;
@@ -8,46 +48,6 @@
}
}
- .loader {
- height: 120px;
- width: 120px;
- margin: 0 auto;
- position: relative;
- opacity: 0;
-
- &:before,
- &:after {
- content: '';
- display: block;
- margin: 0 auto;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- border: {
- radius: 100%;
- style: solid;
- width: 2px;
- }
- }
-
- &:before {
- position: absolute;
- top: 0;
- left: 0;
- border: {
- color: lighten($dark_grey, 5%);
- }
- }
-
- &:after {
- @include spin();
- border-top-color: transparent;
- border-left-color: transparent;
- border-bottom-color: transparent;
- border-right-color: $blue;
- }
- }
-
&.lazy-loader {
padding: 20px;
height: 40px;
diff --git a/src/scss/components/_notifications.scss b/src/scss/components/_notifications.scss
index 475df911..9bdc802d 100755
--- a/src/scss/components/_notifications.scss
+++ b/src/scss/components/_notifications.scss
@@ -64,25 +64,27 @@
&.process {
position: relative;
padding-right: 18px;
+ padding-left: 50px;
&.cancelling {
color: rgba(255,255,255,0.5);
}
- &:after {
- pointer-events: none;
+ .loader {
+ width: 20px;
+ height: 20px;
position: absolute;
- display: block;
- content: '';
- background: $white;
- opacity: 0.15;
- animation: slideloader 1s infinite;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 1;
- border-radius: 0 0 3px 3px;
+ top: 16px;
+ left: 18px;
+ opacity: 1;
+
+ &:before {
+ border-color: transparent;
+ }
+
+ &:after {
+ border-right-color: $white;
+ }
}
}
}
diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss
index 5c9230a5..182b85be 100755
--- a/src/scss/global/_variables.scss
+++ b/src/scss/global/_variables.scss
@@ -116,9 +116,15 @@ $bp_shallow: 650px;
}
@keyframes slideloader {
- 0% { left: 0%; right: 100%; }
- 50% { left: 0%; right: 0%; }
- 100% { left: 100%; right: 0%; }
+ 0% {
+ transform: translateX(-100%);
+ }
+ 50% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(+100%);
+ }
}