Lazy loading via awesome component

This commit is contained in:
James Barnsley
2016-11-01 08:57:28 +13:00
parent 25c973205a
commit b302cd2ac0
17 changed files with 496 additions and 115 deletions

View File

@ -0,0 +1,38 @@
import React, { PropTypes } from 'react'
export default class LazyLoadListener extends React.Component{
constructor(props) {
super(props);
this.state = { loading: false }
this.handleScroll = this.handleScroll.bind(this);
}
componentWillMount(){
window.addEventListener("scroll", this.handleScroll, false);
}
componentWillUnmount(){
window.removeEventListener("scroll", this.handleScroll, false);
}
handleScroll(e){
if( (window.innerHeight + window.scrollY) >= document.body.offsetHeight ){
if( !this.state.loading ){
this.setState({ loading: true })
this.props.loadMore();
}
}else if( this.state.loading ){
this.setState({ loading: false })
}
}
render(){
if( this.loading ){
return <div className='lazy-loader loading'><div className="content">LOADING</div></div>
}else{
return <div className='lazy-loader'></div>
}
}
}

View File

@ -20,11 +20,10 @@ export default class Thumbnail extends React.Component{
}
componentWillReceiveProps( nextProps ){
this.mapImageSizes();
this.mapImageSizes( nextProps.images );
}
mapImageSizes(){
var images = this.props.images;
mapImageSizes( images = this.props.images ){
var state = this.state;
if( images.length <= 0 ){

View File

@ -1,28 +1,4 @@
/**
* Actions and Action Creators
**/
export function setConfig( config ){
return {
type: 'SPOTIFY_SET_CONFIG',
config: config
}
}
export function connect(){
return {
type: 'SPOTIFY_CONNECTING'
}
}
export function disconnect(){
return {
type: 'SPOTIFY_DISCONNECT'
}
}
/**
* Send an ajax request to the Spotify API
*
@ -37,18 +13,22 @@ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false
return new Promise( (resolve, reject) => {
getToken( dispatch, getState )
.then( response => {
var url = 'https://api.spotify.com/v1/'+endpoint
if( endpoint.startsWith('https://api.spotify.com/') ) url = endpoint;
$.ajax({
method: method,
cache: true,
url: 'https://api.spotify.com/v1/'+endpoint,
url: url,
headers: {
Authorization: 'Bearer '+ response
},
data: data
}).then(
response => resolve(response),
error => {
console.error('Could not send request to '+endpoint, error, data)
(xhr, status, error) => {
console.error( endpoint+' failed', xhr.responseText)
reject(error)
}
)
@ -58,24 +38,6 @@ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false
}
/**
* Handle authorization process
**/
export function startAuthorization(){
return { type: 'SPOTIFY_START_AUTHORIZATION' }
}
export function authorizationGranted( data ){
data.token_expiry = new Date().getTime() + data.expires_in;
return { type: 'SPOTIFY_AUTHORIZATION_GRANTED', data: data }
}
export function authorizationRevoked(){
return { type: 'SPOTIFY_AUTHORIZATION_REVOKED' }
}
/**
* Check an access token validity
*
@ -154,6 +116,48 @@ function refreshToken( dispatch, getState ){
})
}
/**
* Actions and Action Creators
**/
export function setConfig( config ){
return {
type: 'SPOTIFY_SET_CONFIG',
config: config
}
}
export function connect(){
return {
type: 'SPOTIFY_CONNECTING'
}
}
export function disconnect(){
return {
type: 'SPOTIFY_DISCONNECT'
}
}
/**
* Handle authorization process
**/
export function startAuthorization(){
return { type: 'SPOTIFY_START_AUTHORIZATION' }
}
export function authorizationGranted( data ){
data.token_expiry = new Date().getTime() + data.expires_in;
return { type: 'SPOTIFY_AUTHORIZATION_GRANTED', data: data }
}
export function authorizationRevoked(){
return { type: 'SPOTIFY_AUTHORIZATION_REVOKED' }
}
export function refreshingToken(){
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_TOKEN_REFRESHING' });
@ -337,7 +341,7 @@ export function getLibraryArtists(){
.then( response => {
dispatch({
type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED',
data: response
data: response.artists
});
});
}
@ -450,7 +454,7 @@ export function getCategoryPlaylists( id ){
}
export function getNewReleases(){
return (dispatch, getState) => {
return (dispatch, getState) => {
dispatch({ type: 'SPOTIFY_NEW_RELEASES_LOADED', data: false });
@ -461,6 +465,18 @@ export function getNewReleases(){
data: response.albums
});
});
}
}
export function getURL( url, action_name ){
return (dispatch, getState) => {
sendRequest( dispatch, getState, url )
.then( response => {
dispatch({
type: action_name,
data: response
});
});
}
}

View File

@ -68,15 +68,39 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_ARTIST_ALBUMS_LOADED':
return Object.assign({}, spotify, { artist_albums: action.data });
case 'SPOTIFY_ARTIST_ALBUMS_LOADED_MORE':
return Object.assign({}, spotify, { artist_albums: {
href: action.data.href,
next: action.data.next,
previous: action.data.previous,
items: [ ...spotify.artist_albums.items, ...action.data.items ]
}});
case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
return Object.assign({}, spotify, { library_playlists: action.data });
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED':
return Object.assign({}, spotify, { library_artists: action.data });
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED_MORE':
return Object.assign({}, spotify, { library_artists: {
href: action.data.artists.href,
next: action.data.artists.next,
previous: action.data.artists.previous,
items: [ ...spotify.library_artists.items, ...action.data.artists.items ]
}});
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
return Object.assign({}, spotify, { library_albums: action.data });
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED_MORE':
return Object.assign({}, spotify, { library_albums: {
href: action.data.href,
next: action.data.next,
previous: action.data.previous,
items: [ ...spotify.library_albums.items, ...action.data.items ]
}});
case 'SPOTIFY_LIBRARY_TRACKS_LOADED':
return Object.assign({}, spotify, { library_tracks: action.data });
@ -92,9 +116,25 @@ export default function reducer(spotify = {}, action){
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED':
return Object.assign({}, spotify, { category_playlists: action.data });
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE':
return Object.assign({}, spotify, { category_playlists: {
href: action.data.href,
next: action.data.next,
previous: action.data.previous,
items: [ ...spotify.category_playlists.items, ...action.data.items ]
}});
case 'SPOTIFY_NEW_RELEASES_LOADED':
return Object.assign({}, spotify, { new_releases: action.data });
case 'SPOTIFY_NEW_RELEASES_LOADED_MORE':
return Object.assign({}, spotify, { new_releases: {
href: action.data.albums.href,
next: action.data.albums.next,
previous: action.data.albums.previous,
items: [ ...spotify.new_releases.items, ...action.data.albums.items ]
}});
default:
return spotify
}

View File

@ -16,4 +16,11 @@ export function hideContextMenu(){
}
}
export function lazyLoading( start ){
return {
type: 'UI_LAZY_LOADING',
start: start
}
}

View File

@ -16,6 +16,9 @@ export default function reducer(ui = {}, action){
case 'UI_HIDE_CONTEXT_MENU':
return Object.assign({}, ui, { context_menu: { show: false } });
case 'UI_LAZY_LOADING':
return Object.assign({}, ui, { lazy_loading: action.start });
default:
return ui
}

View File

@ -3,6 +3,7 @@ import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import LazyLoadListener from '../components/LazyLoadListener'
import Header from '../components/Header'
import TrackList from '../components/TrackList'
import AlbumGrid from '../components/AlbumGrid'
@ -30,6 +31,11 @@ class Artist extends React.Component{
}
}
loadMore(){
if( !this.props.spotify.artist_albums || !this.props.spotify.artist_albums.next ) return
this.props.spotifyActions.getURL( this.props.spotify.artist_albums.next, 'SPOTIFY_ARTIST_ALBUMS_LOADED_MORE' );
}
render(){
if( this.props.spotify.artist ){
return (
@ -58,6 +64,7 @@ class Artist extends React.Component{
<h4 className="left-padding">Albums</h4>
{ this.props.spotify.artist_albums ? <AlbumGrid className="no-top-padding" albums={ this.props.spotify.artist_albums.items } /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
}

View File

@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import PlaylistGrid from '../../components/PlaylistGrid'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as spotifyActions from '../../services/spotify/actions'
@ -26,6 +27,11 @@ class DiscoverCategory extends React.Component{
}
}
loadMore(){
if( !this.props.spotify.new_releases || !this.props.spotify.new_releases.next ) return
this.props.spotifyActions.getURL( this.props.spotify.new_releases.next, 'SPOTIFY_NEW_RELEASES_LOADED_MORE' );
}
render(){
if( !this.props.spotify.category ) return null;
@ -33,6 +39,7 @@ class DiscoverCategory extends React.Component{
<div className="view discover-categories-view">
<Header icon="grid" title={this.props.spotify.category.name} />
{ this.props.spotify.category_playlists ? <PlaylistGrid playlists={this.props.spotify.category_playlists.items} /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
}

View File

@ -5,7 +5,9 @@ import { bindActionCreators } from 'redux'
import Header from '../../components/Header'
import AlbumGrid from '../../components/AlbumGrid'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as uiActions from '../../services/ui/actions'
import * as spotifyActions from '../../services/spotify/actions'
class DiscoverNewReleases extends React.Component{
@ -14,16 +16,21 @@ class DiscoverNewReleases extends React.Component{
super(props);
}
// on render
componentDidMount(){
this.props.spotifyActions.getNewReleases();
}
loadMore(){
if( !this.props.spotify.new_releases || !this.props.spotify.new_releases.next ) return
this.props.spotifyActions.getURL( this.props.spotify.new_releases.next, 'SPOTIFY_NEW_RELEASES_LOADED_MORE' );
}
render(){
return (
<div className="view discover-new-releases-view">
<Header icon="leaf" title="New Releases" />
{ this.props.spotify.new_releases ? <AlbumGrid albums={this.props.spotify.new_releases.items} /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
}
@ -42,6 +49,7 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch) => {
return {
uiActions: bindActionCreators(uiActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch)
}
}

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router'
import AlbumGrid from '../../components/AlbumGrid'
import Header from '../../components/Header'
import LazyLoadListener from '../../components/LazyLoadListener'
import * as mopidyActions from '../../services/mopidy/actions'
import * as spotifyActions from '../../services/spotify/actions'
@ -16,11 +17,15 @@ class LibraryAlbums extends React.Component{
super(props);
}
// on render
componentDidMount(){
this.props.spotifyActions.getLibraryAlbums();
}
loadMore(){
if( !this.props.spotify.library_albums || !this.props.spotify.library_albums.next ) return
this.props.spotifyActions.getURL( this.props.spotify.library_albums.next, 'SPOTIFY_LIBRARY_ALBUMS_LOADED_MORE' );
}
render(){
return (
<div className="view library-albums-view">
@ -29,6 +34,7 @@ class LibraryAlbums extends React.Component{
title="My albums"
/>
{ this.props.spotify.library_albums ? <AlbumGrid albums={this.props.spotify.library_albums.items} /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
}

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
import LazyLoadListener from '../../components/LazyLoadListener'
import Header from '../../components/Header'
import ArtistGrid from '../../components/ArtistGrid'
@ -21,6 +22,11 @@ class LibraryArtists extends React.Component{
this.props.spotifyActions.getLibraryArtists();
}
loadMore(){
if( !this.props.spotify.library_artists || !this.props.spotify.library_artists.next ) return
this.props.spotifyActions.getURL( this.props.spotify.library_artists.next, 'SPOTIFY_LIBRARY_ARTISTS_LOADED_MORE' );
}
render(){
return (
<div className="view library-artists-view">
@ -28,7 +34,8 @@ class LibraryArtists extends React.Component{
icon="mic"
title="My artists"
/>
{ this.props.spotify.library_artists ? <ArtistGrid artists={this.props.spotify.library_artists.artists.items} /> : null }
{ this.props.spotify.library_artists ? <ArtistGrid artists={this.props.spotify.library_artists.items} /> : null }
<LazyLoadListener loadMore={ () => this.loadMore() }/>
</div>
);
}

View File

@ -7,6 +7,7 @@
@import 'global/reset';
@import 'global/forms';
@import 'components/lazy-load-listener';
@import 'components/context-menu';
@import 'components/images';
@import 'components/icon';

View File

@ -0,0 +1,9 @@
.lazy-loader {
height: 50px;
color: $light_grey;
&.loading {
}
}