From fd46929ac3995039c870646d9a23e24183206251 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Thu, 10 Aug 2017 12:00:08 +1200 Subject: [PATCH] Loading library albums as process; Processes loader for less jammy animation --- src/js/bootstrap.js | 9 +- src/js/components/Notifications.js | 4 +- src/js/components/Thumbnail.js | 21 ++- src/js/index.js | 2 - src/js/services/mopidy/actions.js | 17 +- src/js/services/mopidy/middleware.js | 59 ++++-- src/js/services/ui/middleware.js | 3 + src/js/views/library/LibraryLocalAlbums.js | 203 --------------------- src/scss/components/_loader.scss | 80 ++++---- src/scss/components/_notifications.scss | 28 +-- src/scss/global/_variables.scss | 12 +- 11 files changed, 151 insertions(+), 287 deletions(-) delete mode 100755 src/js/views/library/LibraryLocalAlbums.js 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(
+
Cancelling
) } 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%); + } }