Merge branch 'feature/lazy-loading'
This commit is contained in:
@ -454,7 +454,6 @@ class IrisCore(object):
|
||||
|
||||
|
||||
def load_more_tracks(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
# this is crude, but it means we don't need to handle expired tokens
|
||||
# TODO: address this when it's clear what Jodal and the team want to do with Pyspotify
|
||||
@ -467,13 +466,7 @@ class IrisCore(object):
|
||||
error = 'IrisFrontend: access_token missing or invalid'
|
||||
self.raven_client.captureMessage(error)
|
||||
logger.error(error)
|
||||
self.broadcast(
|
||||
data={
|
||||
'type': 'error',
|
||||
'message': 'Could not get radio tracks: access_token missing or invalid',
|
||||
'source': 'load_more_tracks'
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
url = 'https://api.spotify.com/v1/recommendations/'
|
||||
@ -497,14 +490,7 @@ class IrisCore(object):
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
logger.error('IrisFrontend: Failed to fetch Spotify recommendations')
|
||||
self.broadcast(
|
||||
data={
|
||||
'type': 'error',
|
||||
'message': 'Could not get radio tracks',
|
||||
'source': 'load_more_tracks'
|
||||
}
|
||||
)
|
||||
return []
|
||||
return False
|
||||
|
||||
|
||||
def check_for_radio_update( self ):
|
||||
@ -636,9 +622,9 @@ class IrisCore(object):
|
||||
}
|
||||
|
||||
try:
|
||||
http_client = tornado.httpclient.AsyncHTTPClient()
|
||||
http_client = tornado.httpclient.HTTPClient()
|
||||
request = tornado.httpclient.HTTPRequest(url, method='POST', body=urllib.urlencode(data))
|
||||
http_client.fetch(request, callback=callback)
|
||||
response = http_client.fetch(request)
|
||||
|
||||
self.broadcast(
|
||||
data={
|
||||
@ -647,6 +633,17 @@ class IrisCore(object):
|
||||
}
|
||||
)
|
||||
|
||||
token = json.loads(response.body)
|
||||
self.spotify_token = token
|
||||
|
||||
if (callback):
|
||||
callback({
|
||||
'status': 1,
|
||||
'spotify_token': token
|
||||
})
|
||||
else:
|
||||
return token
|
||||
|
||||
except urllib2.HTTPError as e:
|
||||
self.raven_client.captureException()
|
||||
error = json.loads(e.read())
|
||||
|
||||
@ -157,24 +157,15 @@ function refreshToken(dispatch, getState){
|
||||
$.ajax(config)
|
||||
.then(
|
||||
response => {
|
||||
if (response.response_code == 200){
|
||||
var token = response.response;
|
||||
token.token_expiry = new Date().getTime() + (token.expires_in * 1000 );
|
||||
token.source = 'mopidy';
|
||||
dispatch({
|
||||
type: 'SPOTIFY_TOKEN_REFRESHED',
|
||||
access_token_provider: 'backend',
|
||||
data: token
|
||||
});
|
||||
resolve(token);
|
||||
|
||||
} else {
|
||||
dispatch({ type: 'SPOTIFY_DISCONNECTED' })
|
||||
reject({
|
||||
config: config,
|
||||
error: response
|
||||
})
|
||||
}
|
||||
var token = response.spotify_token;
|
||||
token.token_expiry = new Date().getTime() + (token.expires_in * 1000 );
|
||||
token.source = 'mopidy';
|
||||
dispatch({
|
||||
type: 'SPOTIFY_TOKEN_REFRESHED',
|
||||
access_token_provider: 'backend',
|
||||
data: token
|
||||
});
|
||||
resolve(token);
|
||||
|
||||
},
|
||||
(xhr, status, error) => {
|
||||
|
||||
@ -12,6 +12,7 @@ import TrackList from '../../components/TrackList'
|
||||
import ArtistSentence from '../../components/ArtistSentence'
|
||||
import DropdownField from '../../components/DropdownField'
|
||||
import FilterField from '../../components/FilterField'
|
||||
import LazyLoadListener from '../../components/LazyLoadListener'
|
||||
|
||||
import * as helpers from '../../helpers'
|
||||
import * as coreActions from '../../services/core/actions'
|
||||
@ -25,7 +26,9 @@ class LibraryAlbums extends React.Component{
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
filter: ''
|
||||
filter: '',
|
||||
limit: 50,
|
||||
per_page: 50
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,75 +117,7 @@ class LibraryAlbums extends React.Component{
|
||||
this.props.uiActions.set(data)
|
||||
}
|
||||
|
||||
renderView(albums){
|
||||
if (!albums || albums.length <= 0) return null
|
||||
|
||||
if (this.props.view == 'list'){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Artists',
|
||||
name: 'artists'
|
||||
},
|
||||
{
|
||||
label: 'Added',
|
||||
name: 'added_at'
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
name: 'tracks_total'
|
||||
},
|
||||
{
|
||||
label: 'Source',
|
||||
name: 'source'
|
||||
}
|
||||
]
|
||||
return (
|
||||
<List
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
rows={albums}
|
||||
columns={columns}
|
||||
className="album-list"
|
||||
link_prefix={global.baseURL+"album/"} />
|
||||
)
|
||||
}else if (this.props.view == 'detail'){
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
albums.map(album => {
|
||||
return (
|
||||
<div className="album" key={album.uri}>
|
||||
<Link to={global.baseURL+'album/'+album.uri}>
|
||||
<Thumbnail size="medium" images={album.images} />
|
||||
</Link>
|
||||
<div className="detail">
|
||||
<Link to={global.baseURL+'album/'+album.uri}>
|
||||
<h2>{ album.name }</h2>
|
||||
</Link>
|
||||
<h3><ArtistSentence className="grey-text" artists={album.artists} /></h3>
|
||||
</div>
|
||||
<div className="list-wrapper">
|
||||
<TrackList tracks={album.tracks} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<AlbumGrid
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
albums={albums} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
renderView(){
|
||||
var albums = []
|
||||
|
||||
// Spotify library items
|
||||
@ -222,6 +157,57 @@ class LibraryAlbums extends React.Component{
|
||||
albums = helpers.applyFilter('name', this.state.filter, albums)
|
||||
}
|
||||
|
||||
// Apply our lazy-load-rendering
|
||||
var total_albums = albums.length;
|
||||
albums = albums.slice(0, this.state.limit);
|
||||
|
||||
if (this.props.view == 'list'){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Artists',
|
||||
name: 'artists'
|
||||
},
|
||||
{
|
||||
label: 'Added',
|
||||
name: 'added_at'
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
name: 'tracks_total'
|
||||
},
|
||||
{
|
||||
label: 'Source',
|
||||
name: 'source'
|
||||
}
|
||||
]
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
rows={albums}
|
||||
columns={columns}
|
||||
className="album-list"
|
||||
link_prefix={global.baseURL+"album/"} />
|
||||
<LazyLoadListener loading={this.state.limit < total_albums} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<AlbumGrid
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
albums={albums} />
|
||||
<LazyLoadListener loading={this.state.limit < total_albums} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
var source_options = [
|
||||
{
|
||||
value: 'all',
|
||||
@ -273,7 +259,7 @@ class LibraryAlbums extends React.Component{
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<FilterField handleChange={value => this.setState({filter: value})} />
|
||||
<FilterField handleChange={value => this.setState({filter: value, limit: this.state.per_page})} />
|
||||
<DropdownField icon="sort" name="Sort" value={this.props.sort} options={sort_options} reverse={this.props.sort_reverse} handleChange={val => {this.setSort(val); this.props.uiActions.hideContextMenu() }} />
|
||||
<DropdownField icon="eye" name="View" value={this.props.view} options={view_options} handleChange={val => {this.props.uiActions.set({ library_albums_view: val }); this.props.uiActions.hideContextMenu() }} />
|
||||
<DropdownField icon="database" name="Source" value={this.props.source} options={source_options} handleChange={val => {this.props.uiActions.set({ library_albums_source: val}); this.props.uiActions.hideContextMenu() }} />
|
||||
@ -282,13 +268,8 @@ class LibraryAlbums extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="view library-albums-view">
|
||||
|
||||
<Header icon="cd" title="My albums" options={options} uiActions={this.props.uiActions} />
|
||||
|
||||
<section className="content-wrapper">
|
||||
{this.renderView(albums)}
|
||||
</section>
|
||||
|
||||
{this.renderView()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,12 +4,12 @@ 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'
|
||||
import List from '../../components/List'
|
||||
import DropdownField from '../../components/DropdownField'
|
||||
import FilterField from '../../components/FilterField'
|
||||
import LazyLoadListener from '../../components/LazyLoadListener'
|
||||
|
||||
import * as helpers from '../../helpers'
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
@ -22,7 +22,9 @@ class LibraryArtists extends React.Component{
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
filter: ''
|
||||
filter: '',
|
||||
limit: 50,
|
||||
per_page: 50
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,44 +87,7 @@ class LibraryArtists extends React.Component{
|
||||
this.props.uiActions.set(data)
|
||||
}
|
||||
|
||||
renderView(artists){
|
||||
if (this.props.view == 'list'){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Followers',
|
||||
name: 'followers.total'
|
||||
},
|
||||
{
|
||||
label: 'Popularity',
|
||||
name: 'popularity'
|
||||
}
|
||||
]
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
rows={artists}
|
||||
columns={columns}
|
||||
className="artist-list"
|
||||
link_prefix={global.baseURL+"artist/"} />
|
||||
</section>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<ArtistGrid
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
artists={artists} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
renderView(){
|
||||
var artists = []
|
||||
|
||||
// Mopidy library items
|
||||
@ -162,6 +127,49 @@ class LibraryArtists extends React.Component{
|
||||
artists = helpers.applyFilter('name', this.state.filter, artists)
|
||||
}
|
||||
|
||||
// Apply our lazy-load-rendering
|
||||
var total_artists = artists.length;
|
||||
artists = artists.slice(0, this.state.limit);
|
||||
|
||||
if (this.props.view == 'list'){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Followers',
|
||||
name: 'followers.total'
|
||||
},
|
||||
{
|
||||
label: 'Popularity',
|
||||
name: 'popularity'
|
||||
}
|
||||
]
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
rows={artists}
|
||||
columns={columns}
|
||||
className="artist-list"
|
||||
link_prefix={global.baseURL+"artist/"} />
|
||||
<LazyLoadListener loading={this.state.limit < total_artists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<ArtistGrid
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
artists={artists} />
|
||||
<LazyLoadListener loading={this.state.limit < total_artists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
var source_options = [
|
||||
{
|
||||
value: 'all',
|
||||
@ -205,7 +213,7 @@ class LibraryArtists extends React.Component{
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<FilterField handleChange={value => this.setState({filter: value})} />
|
||||
<FilterField handleChange={value => this.setState({filter: value, limit: this.state.per_page})} />
|
||||
<DropdownField icon="sort" name="Sort" value={ this.props.sort } options={sort_options} reverse={this.props.sort_reverse} handleChange={value => {this.setSort(value); this.props.uiActions.hideContextMenu() }} />
|
||||
<DropdownField icon="eye" name="View" value={ this.props.view } options={view_options} handleChange={value => {this.props.uiActions.set({ library_artists_view: value }); this.props.uiActions.hideContextMenu()}} />
|
||||
<DropdownField icon="database" name="Source" value={this.props.source} options={source_options} handleChange={val => {this.props.uiActions.set({ library_artists_source: val}); this.props.uiActions.hideContextMenu() }} />
|
||||
@ -214,8 +222,8 @@ class LibraryArtists extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="view library-artists-view">
|
||||
<Header icon="mic" title="My artists" options={options} uiActions={this.props.uiActions} />
|
||||
{ this.renderView(artists) }
|
||||
<Header icon="mic" title="My artists" options={options} uiActions={this.props.uiActions} />
|
||||
{this.renderView()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import List from '../../components/List'
|
||||
import DropdownField from '../../components/DropdownField'
|
||||
import Header from '../../components/Header'
|
||||
import FilterField from '../../components/FilterField'
|
||||
import LazyLoadListener from '../../components/LazyLoadListener'
|
||||
|
||||
import * as helpers from '../../helpers'
|
||||
import * as coreActions from '../../services/core/actions'
|
||||
@ -23,7 +24,9 @@ class LibraryPlaylists extends React.Component{
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
filter: ''
|
||||
filter: '',
|
||||
limit: 50,
|
||||
per_page: 50
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +90,7 @@ class LibraryPlaylists extends React.Component{
|
||||
}
|
||||
|
||||
renderView(){
|
||||
var playlists = []
|
||||
var playlists = [];
|
||||
|
||||
// Spotify library items
|
||||
if (this.props.spotify_library_playlists && (this.props.source == 'all' || this.props.source == 'spotify')){
|
||||
@ -109,13 +112,17 @@ class LibraryPlaylists extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
playlists = helpers.sortItems(playlists, this.props.sort, this.props.sort_reverse)
|
||||
playlists = helpers.removeDuplicates(playlists)
|
||||
playlists = helpers.sortItems(playlists, this.props.sort, this.props.sort_reverse);
|
||||
playlists = helpers.removeDuplicates(playlists);
|
||||
|
||||
if (this.state.filter !== ''){
|
||||
playlists = helpers.applyFilter('name', this.state.filter, playlists)
|
||||
playlists = helpers.applyFilter('name', this.state.filter, playlists);
|
||||
}
|
||||
|
||||
// Apply our lazy-load-rendering
|
||||
var total_playlists = playlists.length;
|
||||
playlists = playlists.slice(0, this.state.limit);
|
||||
|
||||
if (this.props.view == 'list'){
|
||||
if (this.props.slim_mode){
|
||||
var columns = [
|
||||
@ -161,6 +168,7 @@ class LibraryPlaylists extends React.Component{
|
||||
columns={columns}
|
||||
className="playlist-list"
|
||||
link_prefix={global.baseURL+"playlist/"} />
|
||||
<LazyLoadListener loading={this.state.limit < total_playlists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
} else {
|
||||
@ -169,13 +177,13 @@ class LibraryPlaylists extends React.Component{
|
||||
<PlaylistGrid
|
||||
handleContextMenu={(e,item) => this.handleContextMenu(e,item)}
|
||||
playlists={playlists} />
|
||||
<LazyLoadListener loading={this.state.limit < total_playlists} loadMore={() => this.setState({limit: this.state.limit + this.state.per_page})} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
var source_options = [
|
||||
{
|
||||
value: 'all',
|
||||
|
||||
Reference in New Issue
Block a user