|
|
@@ -1,205 +1,70 @@
|
|
|
import React from 'react';
|
|
|
-import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'
|
|
|
-import List from 'react-virtualized/dist/commonjs/List'
|
|
|
-import InfiniteLoader from 'react-virtualized/dist/commonjs/InfiniteLoader';
|
|
|
+import InfiniteScroll from 'react-infinite-scroller';
|
|
|
|
|
|
import {formatDuration} from './utils.js';
|
|
|
import logo from './logo.svg';
|
|
|
-import styles from './AlbumList.css';
|
|
|
+import './AlbumList.css';
|
|
|
|
|
|
-function mapAlbums(items) {
|
|
|
- var artists = [], curArtist = {}, curType = {};
|
|
|
- for (const album of items) {
|
|
|
- if (album.artist !== curArtist.name) {
|
|
|
- curArtist = {name: album.artist, types: []}
|
|
|
- curType = {}
|
|
|
- artists.push(curArtist)
|
|
|
- }
|
|
|
- if (album.type !== curType.type) {
|
|
|
- curType = {type: album.type, albums: []}
|
|
|
- curArtist.types.push(curType)
|
|
|
- }
|
|
|
- curType.albums.push(album);
|
|
|
+function _albumProps(albums, index) {
|
|
|
+ if (index === 0) {
|
|
|
+ return {showArtist: true, showType: true}
|
|
|
}
|
|
|
- return artists;
|
|
|
-}
|
|
|
|
|
|
-const STATUS_LOADING = 1;
|
|
|
-
|
|
|
-export default class AlbumList extends React.PureComponent {
|
|
|
- constructor(props) {
|
|
|
- super(props);
|
|
|
-
|
|
|
- this.state = {
|
|
|
- loadedRowsMap: {},
|
|
|
- };
|
|
|
- this._isRowLoaded = this._isRowLoaded.bind(this);
|
|
|
- this._loadMoreRows = this._loadMoreRows.bind(this);
|
|
|
- this._rowRenderer = this._rowRenderer.bind(this);
|
|
|
- this._rowHeight = this._rowHeight.bind(this);
|
|
|
+ if (albums[index-1].artist !== albums[index].artist) {
|
|
|
+ return {showArtist: true, showType: true}
|
|
|
}
|
|
|
|
|
|
- render() {
|
|
|
- const {rowCount} = this.props;
|
|
|
-
|
|
|
- return (
|
|
|
- <div className="AlbumList">
|
|
|
- <h2>{this.props.title}</h2>
|
|
|
-
|
|
|
- <InfiniteLoader
|
|
|
- isRowLoaded={this._isRowLoaded}
|
|
|
- loadMoreRows={this._loadMoreRows}
|
|
|
- minimumBatchSize={15}
|
|
|
- rowCount={rowCount}>
|
|
|
- {({onRowsRendered, registerChild}) => (
|
|
|
- <AutoSizer disableHeight>
|
|
|
- {({width}) => (
|
|
|
- <List
|
|
|
- ref={registerChild}
|
|
|
- height={1000}
|
|
|
- width={width}
|
|
|
- onRowsRendered={onRowsRendered}
|
|
|
- rowCount={rowCount}
|
|
|
- rowHeight={this._rowHeight}
|
|
|
- rowRenderer={this._rowRenderer}
|
|
|
- />
|
|
|
- )}
|
|
|
- </AutoSizer>
|
|
|
- )}
|
|
|
- </InfiniteLoader>
|
|
|
- </div>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- _isRowLoaded({index}) {
|
|
|
- const {loadedRowsMap} = this.state;
|
|
|
- return !!loadedRowsMap[index]; // STATUS_LOADING or STATUS_LOADED
|
|
|
- }
|
|
|
-
|
|
|
- _loadMoreRows({startIndex, stopIndex}) {
|
|
|
- console.log('_loadMoreRows', startIndex, stopIndex);
|
|
|
- const {loadedRowsMap} = this.state;
|
|
|
-
|
|
|
- for (var i = startIndex; i <= stopIndex; i++) {
|
|
|
- loadedRowsMap[i] = STATUS_LOADING;
|
|
|
- }
|
|
|
-
|
|
|
- return this.props.loadMoreRows({offset: startIndex, limit: (stopIndex-startIndex+1)})
|
|
|
- .then(rows => {
|
|
|
- for (var i = startIndex; i <= stopIndex; i++) {
|
|
|
- loadedRowsMap[i] = rows[i-startIndex];
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- _albumHeadings(index) {
|
|
|
- let rows = this.state.loadedRowsMap;
|
|
|
- if (index === 0 || rows[index-1] === STATUS_LOADING) {
|
|
|
- return {showArtist: true, showType: true}
|
|
|
- }
|
|
|
-
|
|
|
- if (rows[index-1].artist !== rows[index].artist) {
|
|
|
- return {showArtist: true, showType: true}
|
|
|
- }
|
|
|
-
|
|
|
- let showType = (rows[index-1].type !== rows[index].type);
|
|
|
- return {showType};
|
|
|
- }
|
|
|
+ let showType = (albums[index-1].type !== albums[index].type);
|
|
|
+ return {showType};
|
|
|
+}
|
|
|
|
|
|
- _rowHeight({index}) {
|
|
|
- const {loadedRowsMap} = this.state;
|
|
|
- if (!loadedRowsMap[index]) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
- if (loadedRowsMap[index] === STATUS_LOADING) {
|
|
|
- return 80;
|
|
|
+function Album({album, showArtist, showType, onPlayAlbum}) {
|
|
|
+ function onClick(album) {
|
|
|
+ return (e) => {
|
|
|
+ e.preventDefault()
|
|
|
+ onPlayAlbum(album)
|
|
|
}
|
|
|
- let {showArtist, showType} = this._albumHeadings(index);
|
|
|
- return 80 + (showArtist ? 42 : 0) + (showType ? 34 : 0);
|
|
|
}
|
|
|
-
|
|
|
- _rowRenderer({index, key, style}) {
|
|
|
- const {loadedRowsMap} = this.state;
|
|
|
- let album = loadedRowsMap[index];
|
|
|
- if (!album) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- let content;
|
|
|
- if (album === STATUS_LOADING) {
|
|
|
- content = (
|
|
|
- <div className={styles.placeholder} />
|
|
|
- );
|
|
|
- } else {
|
|
|
- let {showArtist, showType} = this._albumHeadings(index)
|
|
|
- content = (
|
|
|
- <div>
|
|
|
- {showArtist && (<h3>{album.artist}</h3>)}
|
|
|
- {showType && (<h4>{album.type}</h4>)}
|
|
|
- <div className="album">
|
|
|
- <img src={album.cover || logo} alt={album.album} />
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ {showArtist && (<h3>{album.artist}</h3>)}
|
|
|
+ {showType && (<h4>{album.type}</h4>)}
|
|
|
+ <div className="album">
|
|
|
+ <div><img src={album.cover || logo} alt={album.album} /></div>
|
|
|
+ <div>
|
|
|
<span>{album.year}</span>
|
|
|
- <span>{album.album}</span>
|
|
|
- <span>{album.publisher}</span>
|
|
|
<span>{album.country}</span>
|
|
|
+ <span>{album.track_count} @ {formatDuration(album.total_duration)}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span><a href={`/album/${album.id}`} onClick={onClick(album)}>{album.album}</a></span>
|
|
|
+ <span>{album.publisher}</span>
|
|
|
<span>{album.genre}</span>
|
|
|
- <span>{album.track_count}</span>
|
|
|
- <span>{formatDuration(album.total_duration)}</span>
|
|
|
- <span><button onClick={() => this.props.onPlayAlbum(album)}>play</button></span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <div className={styles.row} key={key} style={style}>
|
|
|
- {content}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- );
|
|
|
- }
|
|
|
+ </div>
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
-function AlbumListOld(props) {
|
|
|
- const { error, isLoaded, items } = props.state;
|
|
|
-
|
|
|
- var content;
|
|
|
- if (error) {
|
|
|
- content = <div>Error: {error.message}</div>
|
|
|
- } else if (!isLoaded) {
|
|
|
- content = <div>Loading</div>
|
|
|
- } else {
|
|
|
- const artists = mapAlbums(items);
|
|
|
-
|
|
|
- content = artists.map(artist => (
|
|
|
- <div key={artist.name}>
|
|
|
- <h3>{artist.name}</h3>
|
|
|
- {artist.types.map(type => (
|
|
|
- <div key={type.type}>
|
|
|
- <h4>{type.type}</h4>
|
|
|
- <table><tbody>
|
|
|
- {type.albums.map(album => (
|
|
|
- <tr key={album.id}>
|
|
|
- <td><img src={album.cover || logo} alt={album.album} /></td>
|
|
|
- <td>{album.year}</td>
|
|
|
- <td>{album.album}</td>
|
|
|
- <td>{album.publisher}</td>
|
|
|
- <td>{album.country}</td>
|
|
|
- <td>{album.genre}</td>
|
|
|
- <td>{album.track_count}</td>
|
|
|
- <td>{formatDuration(album.total_duration)}</td>
|
|
|
- <td><button onClick={() => props.onPlayAlbum(album)}>play</button></td>
|
|
|
- </tr>
|
|
|
- ))}
|
|
|
- </tbody></table>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- ))
|
|
|
- }
|
|
|
+export default function AlbumList({loadMore, hasMore, albums, title, error, ...props}) {
|
|
|
return (
|
|
|
<div className="AlbumList">
|
|
|
- <h2>{props.title}</h2>
|
|
|
- {content}
|
|
|
+ <h2>{title}</h2>
|
|
|
+ { error ? (
|
|
|
+ <div className="error">{error}></div>
|
|
|
+ ) : (
|
|
|
+ <InfiniteScroll
|
|
|
+ loadMore={loadMore}
|
|
|
+ hasMore={hasMore}
|
|
|
+ loader={<div className="loader" key={0}>Loading ...</div>}
|
|
|
+ initialLoad={false}
|
|
|
+ >
|
|
|
+ {albums.map((album, idx) => (
|
|
|
+ <Album key={idx} album={album} {..._albumProps(albums, idx)} {...props} />
|
|
|
+ ))}
|
|
|
+ </InfiniteScroll>
|
|
|
+ )
|
|
|
+ }
|
|
|
</div>
|
|
|
- );
|
|
|
+ )
|
|
|
}
|