Lazy loading cleanup

This commit is contained in:
James Barnsley
2018-09-03 08:24:49 +12:00
parent dbb5effbb9
commit 3344cf5db4
18 changed files with 10931 additions and 14038 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details // Release details
// These are automatically injected to built HTML // These are automatically injected to built HTML
var build = "1535658713"; var build = "1535848770";
var version = "3.25.1"; var version = "3.25.1";
// Construct the script tag // Construct the script tag

View File

@ -1,14 +1,17 @@
import React, { PropTypes } from 'react' import React, { PropTypes } from 'react';
import * as helpers from '../helpers';
export default class LazyLoadListener extends React.Component{ export default class LazyLoadListener extends React.Component{
constructor(props){ constructor(props){
super(props); super(props);
this.state = { this.state = {
loading: false listening: false,
loadKey: this.props.loadKey
} }
this.handleScroll = this.handleScroll.bind(this);
this.handleScroll = helpers.throttle(this.handleScroll.bind(this), 50);
} }
componentWillMount(){ componentWillMount(){
@ -19,21 +22,37 @@ export default class LazyLoadListener extends React.Component{
window.removeEventListener("scroll", this.handleScroll, false); window.removeEventListener("scroll", this.handleScroll, false);
} }
componentWillReceiveProps(nextProps){
if (nextProps.loadKey !== this.state.loadKey){
this.setState({
loadKey: nextProps.loadKey,
listening: true
});
}
}
handleScroll(e){ handleScroll(e){
if (this.state.listening){
// At, or nearly at the bottom of the page
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80)){ if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80)){
if (!this.state.loading && this.props.loading){
this.setState({ loading: true }) // Immediately stop listening to avoid duplicating pagination requests
this.setState(
{listening: false},
() => {
this.props.loadMore(); this.props.loadMore();
} }
}else if (this.state.loading){ );
this.setState({ loading: false })
}
} }
} }
render(){ render(){
return ( return (
<div className={"lazy-loader body-loader"+(this.state.loading || this.props.forceLoader ? ' loading' : '')}> <div className={"lazy-loader body-loader"+(this.props.showLoader ? ' loading' : '')}>
{this.state.loading || this.props.forceLoader ? <div className="loader"></div> : null} {this.props.showLoader ? <div className="loader"></div> : null}
</div> </div>
) )
} }

View File

@ -337,6 +337,9 @@ export let formatAlbum = function(data){
if (data.date && !album.date) album.release_date = data.date; if (data.date && !album.date) album.release_date = data.date;
// Actively overwrite "type" with "album_type"
if (data.album_type) album.type = data.album_type;
return album; return album;
} }
@ -364,7 +367,11 @@ export let formatArtist = function(data){
'biography_publish_date', 'biography_publish_date',
'related_artists_uris', 'related_artists_uris',
'albums_uris', 'albums_uris',
'tracks_uris' 'albums_total',
'albums_more',
'tracks_uris',
'tracks_total',
'tracks_more'
]; ];
// Loop fields and import from data // Loop fields and import from data
@ -414,7 +421,9 @@ export let formatPlaylist = function(data){
'last_modified_date', 'last_modified_date',
'can_edit', 'can_edit',
'user_uri', 'user_uri',
'tracks_uris' 'tracks_uris',
'tracks_total',
'tracks_more'
]; ];
// Loop fields and import from data // Loop fields and import from data
@ -445,7 +454,10 @@ export let formatUser = function(data){
'provider', 'provider',
'name', 'name',
'images', 'images',
'followers' 'followers',
'playlists_uris',
'playlists_total',
'playlists_more'
]; ];
// Loop fields and import from data // Loop fields and import from data
@ -568,6 +580,7 @@ export let collate = function(obj, indexes = {}){
if (obj.tracks_uris !== undefined) obj.tracks = []; if (obj.tracks_uris !== undefined) obj.tracks = [];
if (obj.users_uris !== undefined) obj.users = []; if (obj.users_uris !== undefined) obj.users = [];
if (obj.playlists_uris !== undefined) obj.playlists = []; if (obj.playlists_uris !== undefined) obj.playlists = [];
if (obj.related_artists_uris !== undefined) obj.related_artists = [];
if (indexes.artists){ if (indexes.artists){
if (obj.artists_uris){ if (obj.artists_uris){
@ -577,6 +590,13 @@ export let collate = function(obj, indexes = {}){
} }
} }
} }
if (obj.related_artists_uris){
for (var uri of obj.related_artists_uris){
if (indexes.artists[uri]){
obj.related_artists.push(indexes.artists[uri]);
}
}
}
if (obj.artist_uri){ if (obj.artist_uri){
if (indexes.artists[obj.artist_uri]){ if (indexes.artists[obj.artist_uri]){
obj.artist = indexes.artists[obj.artist_uri]; obj.artist = indexes.artists[obj.artist_uri];

View File

@ -461,13 +461,10 @@ const CoreMiddleware = (function(){
break; break;
case 'LOAD_USER': case 'LOAD_USER':
console.log(action);
if ( if (
!action.force_reload && !action.force_reload &&
store.getState().core.playlists[action.uri] && store.getState().core.users[action.uri] &&
store.getState().core.playlists[action.uri].albums_uris ){ store.getState().core.users[action.uri].playlists_uris ){
console.info('Loading "'+action.uri+'" from index'); console.info('Loading "'+action.uri+'" from index');
break; break;
} }
@ -487,6 +484,14 @@ const CoreMiddleware = (function(){
break; break;
case 'LOAD_USER_PLAYLISTS': case 'LOAD_USER_PLAYLISTS':
if (
!action.force_reload &&
store.getState().core.users[action.uri] &&
store.getState().core.users[action.uri].playlists_uris ){
console.info('Loading "'+action.uri+'" playlists from index');
break;
}
switch (helpers.uriSource(action.uri)){ switch (helpers.uriSource(action.uri)){
case 'spotify': case 'spotify':
store.dispatch(spotifyActions.getUserPlaylists(action.uri)); store.dispatch(spotifyActions.getUserPlaylists(action.uri));

View File

@ -1273,7 +1273,7 @@ export function getUserPlaylists(uri){
playlists.push(playlist); playlists.push(playlist);
} }
dispatch(coreActions.userPlaylistsLoaded(uri, playlists, response.more, response.total)); dispatch(coreActions.userPlaylistsLoaded(uri, playlists, response.next, response.total));
}, },
error => { error => {
dispatch(coreActions.handleException( dispatch(coreActions.handleException(

View File

@ -155,7 +155,11 @@ class Album extends React.Component{
<section className="list-wrapper"> <section className="list-wrapper">
<TrackList className="album-track-list" tracks={album.tracks} uri={this.props.params.uri} /> <TrackList className="album-track-list" tracks={album.tracks} uri={this.props.params.uri} />
<LazyLoadListener loading={album.tracks_more} forceLoader={is_loading_tracks} loadMore={() => this.loadMore()}/> <LazyLoadListener
loadKey={album.tracks_more}
showLoader={is_loading_tracks}
loadMore={() => this.loadMore()}
/>
</section> </section>
</div> </div>

View File

@ -115,34 +115,21 @@ class Artist extends React.Component{
renderBody(){ renderBody(){
var scheme = helpers.uriSource(this.props.params.uri); var scheme = helpers.uriSource(this.props.params.uri);
var artist = helpers.collate(
this.props.artist,
{
artists: this.props.artists,
albums: this.props.albums,
tracks: this.props.tracks
}
);
var related_artists = [] if (this.props.sort && artist.albums){
if (this.props.artist.related_artists_uris){ artist.albums = helpers.sortItems(artist.albums, this.props.sort, this.props.sort_reverse);
for (var i = 0; i < this.props.artist.related_artists_uris.length; i++){
var uri = this.props.artist.related_artists_uris[i]
if (this.props.artists.hasOwnProperty(uri)){
related_artists.push(this.props.artists[uri])
}
}
} }
var albums = []; if (this.props.filter && artist.albums){
if (this.props.artist.albums_uris){ artist.albums = helpers.applyFilter('type', this.props.filter, artist.albums);
var albums_uris = helpers.removeDuplicates(this.props.artist.albums_uris);
for (var i = 0; i < albums_uris.length; i++){
var uri = albums_uris[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);
}
if (this.props.filter){
albums = helpers.applyFilter('album_type', this.props.filter, albums);
}
} }
switch (this.props.params.sub_view){ switch (this.props.params.sub_view){
@ -151,7 +138,7 @@ class Artist extends React.Component{
return ( return (
<div className="body related-artists"> <div className="body related-artists">
<section className="grid-wrapper no-top-padding"> <section className="grid-wrapper no-top-padding">
<ArtistGrid artists={related_artists} /> <ArtistGrid artists={artist.related_artists} />
</section> </section>
</div> </div>
) )
@ -161,18 +148,18 @@ class Artist extends React.Component{
<div className="body about"> <div className="body about">
<div className="col w40 tiles artist-stats"> <div className="col w40 tiles artist-stats">
{this.props.artist.images ? <div className="tile thumbnail-wrapper"><Thumbnail size="huge" canZoom images={this.props.artist.images} /></div> : null} {artist.images ? <div className="tile thumbnail-wrapper"><Thumbnail size="huge" canZoom images={artist.images} /></div> : null}
{this.props.artist.images_additional ? <div className="tile thumbnail-wrapper"><Thumbnail size="huge" canZoom images={this.props.artist.images_additional} /></div> : null} {artist.images_additional ? <div className="tile thumbnail-wrapper"><Thumbnail size="huge" canZoom images={artist.images_additional} /></div> : null}
{this.props.artist.followers ? <div className="tile"><span className="content"><Icon type="fontawesome" name="users" />{this.props.artist.followers.toLocaleString()} followers</span></div> : null} {artist.followers ? <div className="tile"><span className="content"><Icon type="fontawesome" name="users" />{artist.followers.toLocaleString()} followers</span></div> : null}
{this.props.artist.popularity ? <div className="tile"><span className="content"><Icon type="fontawesome" name="fire" />{this.props.artist.popularity }% popularity</span></div> : null} {artist.popularity ? <div className="tile"><span className="content"><Icon type="fontawesome" name="fire" />{artist.popularity }% popularity</span></div> : null}
{this.props.artist.listeners ? <div className="tile"><span className="content"><Icon type="fontawesome" name="headphones" />{ this.props.artist.listeners.toLocaleString() } listeners</span></div> : null } {artist.listeners ? <div className="tile"><span className="content"><Icon type="fontawesome" name="headphones" />{ artist.listeners.toLocaleString() } listeners</span></div> : null }
</div> </div>
<div className="col w60 biography"> <div className="col w60 biography">
<section> <section>
{ this.props.artist.biography ? <div className="biography-text"><p>{this.props.artist.biography}</p><br /> { artist.biography ? <div className="biography-text"><p>{artist.biography}</p><br />
<div className="grey-text">Published: { this.props.artist.biography_publish_date }</div> <div className="grey-text">Published: { artist.biography_publish_date }</div>
<div className="grey-text">Origin: <a href={ this.props.artist.biography_link } target="_blank">{ this.props.artist.biography_link }</a></div></div> : null } <div className="grey-text">Origin: <a href={ artist.biography_link } target="_blank">{ artist.biography_link }</a></div></div> : null }
</section> </section>
</div> </div>
</div> </div>
@ -218,17 +205,7 @@ class Artist extends React.Component{
} }
]; ];
var tracks = []; if (artist.tracks && artist.tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_artists/'+helpers.getFromUri('artistid',artist.uri)+'/top-tracks'])){
if (this.props.artist.tracks_uris && this.props.tracks){
for (var i = 0; i < this.props.artist.tracks_uris.length; i++){
var uri = this.props.artist.tracks_uris[i]
if (this.props.tracks.hasOwnProperty(uri)){
tracks.push(this.props.tracks[uri])
}
}
}
if (tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_artists/'+helpers.getFromUri('artistid',this.props.params.uri)+'/top-tracks'])){
var is_loading_tracks = true; var is_loading_tracks = true;
} else { } else {
var is_loading_tracks = false; var is_loading_tracks = false;
@ -236,17 +213,17 @@ class Artist extends React.Component{
return ( return (
<div className="body overview"> <div className="body overview">
<div className={"top-tracks col w"+(related_artists.length > 0 ? "70" : "100")}> <div className={"top-tracks col w"+(artist.related_artists && artist.related_artists.length > 0 ? "70" : "100")}>
<h4>Top tracks</h4> <h4>Top tracks</h4>
<div className="list-wrapper"> <div className="list-wrapper">
<TrackList className="artist-track-list" uri={this.props.params.uri} tracks={tracks} /> <TrackList className="artist-track-list" uri={artist.uri} tracks={artist.tracks} />
<LazyLoadListener forceLoader={is_loading_tracks} /> <LazyLoadListener forceLoader={is_loading_tracks} />
</div> </div>
</div> </div>
<div className="col w5"></div> <div className="col w5"></div>
{related_artists.length > 0 ? <div className="col w25 related-artists"><h4>Related artists</h4><div className="list-wrapper"><RelatedArtists artists={related_artists.slice(0,6)} /></div><Link to={global.baseURL+'artist/'+this.props.params.uri+'/related-artists'} className="button grey">All related artists</Link></div> : null} {artist.related_artists ? <div className="col w25 related-artists"><h4>Related artists</h4><div className="list-wrapper"><RelatedArtists artists={artist.related_artists.slice(0,6)} /></div><Link to={global.baseURL+'artist/'+artist.uri+'/related-artists'} className="button grey">All related artists</Link></div> : null}
<div className="cf"></div> <div className="cf"></div>
@ -271,8 +248,12 @@ class Artist extends React.Component{
</h4> </h4>
<section className="grid-wrapper no-top-padding"> <section className="grid-wrapper no-top-padding">
<AlbumGrid albums={albums} /> <AlbumGrid albums={artist.albums} />
<LazyLoadListener loading={this.props.artist.albums_more} loadMore={() => this.loadMore()} /> <LazyLoadListener
loadKey={artist.albums_more}
showLoader={artist.albums_more}
loadMore={() => this.loadMore()}
/>
</section> </section>
</div> </div>
</div> </div>

View File

@ -213,7 +213,11 @@ class Playlist extends React.Component{
<section className="list-wrapper"> <section className="list-wrapper">
<TrackList uri={playlist.uri} className="playlist-track-list" context={context} tracks={playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> <TrackList uri={playlist.uri} className="playlist-track-list" context={context} tracks={playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } />
<LazyLoadListener loading={playlist.tracks_more} forceLoader={is_loading_tracks} loadMore={() => this.loadMore()}/> <LazyLoadListener
loadKey={playlist.tracks_more}
showLoader={is_loading_tracks}
loadMore={() => this.loadMore()}
/>
</section> </section>
</div> </div>
) )

View File

@ -185,7 +185,8 @@ class Queue extends React.Component{
removeTracks={track_indexes => this.removeTracks(track_indexes)} removeTracks={track_indexes => this.removeTracks(track_indexes)}
playTracks={tracks => this.playTracks(tracks)} playTracks={tracks => this.playTracks(tracks)}
playTrack={track => this.playTrack(track)} playTrack={track => this.playTrack(track)}
reorderTracks={(indexes, index) => this.reorderTracks(indexes, index)} /> reorderTracks={(indexes, index) => this.reorderTracks(indexes, index)}
/>
</section> </section>
</div> </div>

View File

@ -110,7 +110,11 @@ class User extends React.Component{
<section className="grid-wrapper"> <section className="grid-wrapper">
<h4>Playlists</h4> <h4>Playlists</h4>
<PlaylistGrid playlists={user.playlists} /> <PlaylistGrid playlists={user.playlists} />
<LazyLoadListener loading={user.playlists_more} loadMore={() => this.loadMore()} /> <LazyLoadListener
loadKey={user.playlists_more}
showLoader={user.playlists_more}
loadMore={() => this.loadMore()}
/>
</section> </section>
</div> </div>
</div> </div>

View File

@ -97,7 +97,11 @@ class DiscoverCategory extends React.Component{
<section className="grid-wrapper"> <section className="grid-wrapper">
<PlaylistGrid playlists={playlists} /> <PlaylistGrid playlists={playlists} />
</section> </section>
<LazyLoadListener loading={this.props.category.playlists_more} loadMore={ () => this.loadMore() }/> <LazyLoadListener
loadKey={this.props.category.playlists_more}
showLoader={this.props.category.playlists_more}
loadMore={() => this.loadMore()}
/>
</div> </div>
</div> </div>
); );

View File

@ -32,6 +32,11 @@ class DiscoverNewReleases extends React.Component{
} }
loadMore(){ loadMore(){
// TODO: Figure out how to check for duplicated requests
// perhaps we catch this at Spotify actions, by checking for
// pending requests on the same URL, and invalidate the Promise?
this.props.spotifyActions.getMore( this.props.spotifyActions.getMore(
this.props.new_releases_more, this.props.new_releases_more,
null, null,
@ -138,7 +143,11 @@ class DiscoverNewReleases extends React.Component{
<section className="content-wrapper grid-wrapper"> <section className="content-wrapper grid-wrapper">
<AlbumGrid albums={albums} /> <AlbumGrid albums={albums} />
</section> </section>
<LazyLoadListener loading={this.props.new_releases_more} loadMore={ () => this.loadMore() }/> <LazyLoadListener
loadKey={this.props.new_releases_more}
showLoader={this.props.new_releases_more}
loadMore={() => this.loadMore()}
/>
</div> </div>
); );
} }

View File

@ -183,7 +183,11 @@ class LibraryAlbums extends React.Component{
columns={columns} columns={columns}
className="album-list" className="album-list"
link_prefix={global.baseURL+"album/"} /> link_prefix={global.baseURL+"album/"} />
<LazyLoadListener loading={this.state.limit < total_albums} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} /> <LazyLoadListener
loadKey={total_albums}
showLoader={this.state.limit < total_albums}
loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})}
/>
</section> </section>
) )
} else { } else {
@ -192,7 +196,11 @@ class LibraryAlbums extends React.Component{
<AlbumGrid <AlbumGrid
handleContextMenu={(e,item) => this.handleContextMenu(e,item)} handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
albums={albums} /> albums={albums} />
<LazyLoadListener loading={this.state.limit < total_albums} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} /> <LazyLoadListener
loadKey={total_albums}
showLoader={this.state.limit < total_albums}
loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})}
/>
</section> </section>
) )
} }

View File

@ -159,7 +159,11 @@ class LibraryArtists extends React.Component{
columns={columns} columns={columns}
className="artist-list" className="artist-list"
link_prefix={global.baseURL+"artist/"} /> link_prefix={global.baseURL+"artist/"} />
<LazyLoadListener loading={this.state.limit < total_artists} loadMore={() => this.loadMore()} /> <LazyLoadListener
loadKey={total_artists}
showLoader={this.state.limit < total_artists}
loadMore={() => this.loadMore()}
/>
</section> </section>
) )
} else { } else {
@ -168,7 +172,11 @@ class LibraryArtists extends React.Component{
<ArtistGrid <ArtistGrid
handleContextMenu={(e,item) => this.handleContextMenu(e,item)} handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
artists={artists} /> artists={artists} />
<LazyLoadListener loading={this.state.limit < total_artists} loadMore={() => this.loadMore()} /> <LazyLoadListener
loadKey={total_artists}
showLoader={this.state.limit < total_artists}
loadMore={() => this.loadMore()}
/>
</section> </section>
) )
} }

View File

@ -155,7 +155,11 @@ class LibraryPlaylists extends React.Component{
columns={columns} columns={columns}
className="playlist-list" className="playlist-list"
link_prefix={global.baseURL+"playlist/"} /> link_prefix={global.baseURL+"playlist/"} />
<LazyLoadListener loading={this.state.limit < total_playlists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} /> <LazyLoadListener
loadKey="playlists"
loading={this.state.limit < total_playlists}
loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})}
/>
</section> </section>
) )
} else { } else {
@ -164,7 +168,11 @@ class LibraryPlaylists extends React.Component{
<PlaylistGrid <PlaylistGrid
handleContextMenu={(e,item) => this.handleContextMenu(e,item)} handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
playlists={playlists} /> playlists={playlists} />
<LazyLoadListener loading={this.state.limit < total_playlists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} /> <LazyLoadListener
loadKey="playlists"
loading={this.state.limit < total_playlists}
loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})}
/>
</section> </section>
) )
} }

View File

@ -85,7 +85,11 @@ class LibraryTracks extends React.Component{
</Header> </Header>
<section className="content-wrapper"> <section className="content-wrapper">
<TrackList tracks={tracks} /> <TrackList tracks={tracks} />
<LazyLoadListener loading={this.props.library_tracks_more} loadMore={() => this.loadMore()}/> <LazyLoadListener
loadKey={this.props.library_tracks_more}
loading={this.props.library_tracks_more}
loadMore={() => this.loadMore()}
/>
</section> </section>
</div> </div>
); );