| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import React, { Component } from 'react';
- import logo from './logo.svg';
- import './App.css';
- function CategoryFlatList(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 {
- content = (
- <select size="10" defaultValue="" onChange={props.onChange}>
- <option value="">[ANY]</option>
- {items.map(item => (
- <option key={item.item} value={item.item}>{item.item} - {item.count}</option>
- ))}
- </select>
- );
- }
- return (
- <div className="category-list">
- <h2>{props.title}</h2>
- {content}
- </div>
- );
- }
- function formatDuration(duration) {
- var seconds = duration % 60;
- const minutes = Math.floor(duration / 60);
- if (seconds < 10) {
- seconds = '0' + seconds;
- }
- return minutes + ':' + seconds;
- }
- 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);
- }
- return artists;
- }
- function AlbumList(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} 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>
- </tr>
- ))}
- </tbody></table>
- </div>
- ))}
- </div>
- ))
- }
- return (
- <div className="AlbumList">
- <h2>{props.title}</h2>
- {content}
- </div>
- );
- }
- class Player extends Component {
- stateLoading = {
- error: null,
- isLoaded: false,
- items: []
- };
- constructor(props) {
- super(props);
- this.categories = [{type: 'artist', title: 'Artists'},
- {type: 'year', title: 'Years'},
- {type: 'genre', title: 'Genres'},
- {type: 'publisher', title: 'Labels'},
- {type: 'country', title: 'Countries'},
- {type: 'type', title: 'Album types'},
- {type: 'status', title: 'Album statuses'}];
- this.state = {};
- for (var {type} of this.categories) {
- this.state[type] = Object.assign({}, this.stateLoading);
- }
- this.state.album = Object.assign({}, this.stateLoading);
- this.filterTimeout = null;
- }
- fetchCategory(cat) {
- const {restrictions, filter} = this.state;
- function getQueryString(params) {
- var esc = encodeURIComponent;
- return Object.keys(params)
- .map(k => esc(k) + '=' + esc(params[k]))
- .join('&');
- }
- var params = Object.assign({}, restrictions, {offset:0, limit:100});
- if (cat === 'album' && filter) {
- params.filter = filter;
- }
- const qs = getQueryString(params);
- fetch('/cat/' + cat + (qs ? ('?' + qs) : ''))
- .then(res => (res.ok ? res.json() : Promise.reject({message:res.statusText})))
- .then(result => {
- this.setState({[cat]: {
- isLoaded: true,
- items: result
- }});
- })
- .catch(error => {
- this.setState({[cat]: {
- isLoaded: true,
- error: error
- }});
- });
- this.setState({[cat]: Object.assign({}, this.stateLoading)});
- }
- fetchAll() {
- const {restrictions} = this.state;
- console.log(restrictions);
- this.categories.map(({type}) => !restrictions[type] && this.fetchCategory(type))
- this.fetchCategory('album');
- }
- componentDidMount() {
- this.setState({restrictions: {}});
- }
- componentDidUpdate(prevProps, prevState) {
- console.log('stateChange');
- if (this.state.restrictions !== prevState.restrictions) {
- // Reload category filters on restriction changes
- this.fetchAll();
- } else if (this.state.filter !== prevState.filter) {
- this.fetchCategory('album');
- }
- }
- handleCategoryChange(type, value) {
- console.log(type, value);
- var restrictions = Object.assign({}, this.state.restrictions);
- if (value) {
- restrictions[type] = value;
- } else {
- delete restrictions[type];
- }
- this.setState({restrictions: restrictions});
- }
- handleFilterChange = (e) => {
- if (this.filterTimeout) {
- clearInterval(this.filterTimeout);
- }
- const value = e.target.value;
- this.filterTimeout = setTimeout(() => this.setState({filter:value}), 500);
- }
- render() {
- return (
- <div className="Player">
- <div className="PlayerFilters">
- {this.categories.map(({type, title}) => (
- <CategoryFlatList key={type} title={title} state={this.state[type]} onChange={e => this.handleCategoryChange(type, e.target.value)}/>
- ))}
- </div><div style={{clear:'both', paddingBottom:'20px'}}/>
- <input type="text" placeholder="Search albums" onChange={this.handleFilterChange} size={150} />
- <AlbumList title="Albums" state={this.state.album} />
- </div>
- );
- }
- }
- class App extends Component {
- render() {
- return (
- <div className="App">
- <header className="App-header">
- <img src={logo} className="App-logo" alt="logo" />
- <h1 className="App-title">Chad Music</h1>
- </header>
- <Player />
- </div>
- );
- }
- }
- export default App;
|