Restructuring and functional component-ifying
This commit is contained in:
@ -28,18 +28,18 @@ import Debug from './views/Debug';
|
||||
import Search from './views/Search';
|
||||
import Settings from './views/Settings';
|
||||
|
||||
import DiscoverRecommendations from './views/discover/DiscoverRecommendations';
|
||||
import DiscoverFeatured from './views/discover/DiscoverFeatured';
|
||||
import DiscoverCategories from './views/discover/DiscoverCategories';
|
||||
import DiscoverCategory from './views/discover/DiscoverCategory';
|
||||
import DiscoverNewReleases from './views/discover/DiscoverNewReleases';
|
||||
import DiscoverRecommendations from './views/Discover/DiscoverRecommendations';
|
||||
import DiscoverFeatured from './views/Discover/DiscoverFeatured';
|
||||
import DiscoverCategories from './views/Discover/DiscoverCategories';
|
||||
import DiscoverCategory from './views/Discover/DiscoverCategory';
|
||||
import DiscoverNewReleases from './views/Discover/DiscoverNewReleases';
|
||||
|
||||
import LibraryArtists from './views/library/LibraryArtists';
|
||||
import LibraryAlbums from './views/library/LibraryAlbums';
|
||||
import LibraryTracks from './views/library/LibraryTracks';
|
||||
import LibraryPlaylists from './views/library/LibraryPlaylists';
|
||||
import LibraryBrowse from './views/library/LibraryBrowse';
|
||||
import LibraryBrowseDirectory from './views/library/LibraryBrowseDirectory';
|
||||
import LibraryArtists from './views/Library/LibraryArtists';
|
||||
import LibraryAlbums from './views/Library/LibraryAlbums';
|
||||
import LibraryTracks from './views/Library/LibraryTracks';
|
||||
import LibraryPlaylists from './views/Library/LibraryPlaylists';
|
||||
import LibraryBrowse from './views/Library/LibraryBrowse';
|
||||
import LibraryBrowseDirectory from './views/Library/LibraryBrowseDirectory';
|
||||
|
||||
import EditPlaylist from './views/modals/EditPlaylist';
|
||||
import CreatePlaylist from './views/modals/CreatePlaylist';
|
||||
|
||||
@ -1,65 +1,43 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { uriType } from '../../util/helpers';
|
||||
import Icon from '../Icon';
|
||||
import { i18n } from '../../locale';
|
||||
import { encodeUri } from '../../util/format';
|
||||
|
||||
class SearchForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const SearchForm = ({
|
||||
term: termProp,
|
||||
onBlur: doBlur,
|
||||
onReset: doReset,
|
||||
onSubmit: doSubmit,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const [term, setTerm] = useState(termProp);
|
||||
const [dirty, setDirty] = useState();
|
||||
|
||||
this.state = {
|
||||
term: props.term,
|
||||
pristine: true,
|
||||
};
|
||||
}
|
||||
useEffect(
|
||||
() => {
|
||||
if (termProp && !dirty) setTerm(termProp);
|
||||
},
|
||||
[termProp],
|
||||
);
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const { pristine, term } = state;
|
||||
if (pristine && term === '' && term !== props.term) {
|
||||
return {
|
||||
term,
|
||||
pristine: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const onChange = (e) => {
|
||||
setTerm(e.target.value);
|
||||
setDirty(true);
|
||||
};
|
||||
|
||||
shouldComponentUpdate = (nextProps, nextState) => {
|
||||
const { term: termProp } = this.props;
|
||||
const { term: termState } = this.state;
|
||||
if (nextProps.term !== termProp) return true;
|
||||
if (nextState.term !== termState) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
this.setState({
|
||||
term: e.target.value,
|
||||
pristine: false,
|
||||
});
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
const { onBlur } = this.props;
|
||||
const { term } = this.state;
|
||||
this.setState({ pristine: false });
|
||||
if (onBlur) {
|
||||
onBlur(term);
|
||||
const onBlur = () => {
|
||||
setDirty(true);
|
||||
if (doBlur) {
|
||||
doBlur(term);
|
||||
}
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
this.setState({ pristine: false });
|
||||
}
|
||||
const onFocus = () => setDirty(true);
|
||||
|
||||
onSubmit = (e) => {
|
||||
const { term } = this.state;
|
||||
const { history } = this.props;
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// check for uri type matching
|
||||
@ -81,52 +59,36 @@ class SearchForm extends React.Component {
|
||||
break;
|
||||
|
||||
default:
|
||||
this.props.onSubmit(term);
|
||||
doSubmit(term);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
setDirty(false);
|
||||
setTerm('');
|
||||
if (doReset) doReset();
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
const { onReset: doReset } = this.props;
|
||||
return (
|
||||
<form className="search-form" onSubmit={onSubmit}>
|
||||
<label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={i18n('fields.search')}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
value={term}
|
||||
/>
|
||||
</label>
|
||||
{term && (
|
||||
<Icon name="close" className="search-form__reset" onClick={onReset} />
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
this.setState(
|
||||
{
|
||||
term: '',
|
||||
pristine: true,
|
||||
},
|
||||
() => {
|
||||
doReset();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { term } = this.state;
|
||||
|
||||
return (
|
||||
<form className="search-form" onSubmit={this.onSubmit}>
|
||||
<label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={i18n('fields.search')}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
value={term}
|
||||
/>
|
||||
</label>
|
||||
{term && (
|
||||
<Icon name="close" className="search-form__reset" onClick={this.onReset} />
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapDispatchToProps)(SearchForm);
|
||||
export default SearchForm;
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import React, { memo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ContextMenuTrigger from './ContextMenuTrigger';
|
||||
import { showContextMenu } from '../services/ui/actions';
|
||||
|
||||
export default memo(({
|
||||
handleContextMenuTrigger,
|
||||
options,
|
||||
title,
|
||||
uiActions,
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const onTrigger = (e) => {
|
||||
if (handleContextMenuTrigger) return handleContextMenuTrigger(e);
|
||||
|
||||
@ -19,7 +21,7 @@ export default memo(({
|
||||
title,
|
||||
options,
|
||||
};
|
||||
uiActions.showContextMenu(data);
|
||||
dispatch(showContextMenu(data));
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@ -130,10 +130,15 @@ export default function reducer(ui = {}, action) {
|
||||
* Loading and processes
|
||||
* */
|
||||
|
||||
case 'START_LOADING':
|
||||
var load_queue = { ...(ui.load_queue ? ui.load_queue : {}) };
|
||||
load_queue[action.key] = action.source || action.key;
|
||||
return { ...ui, load_queue };
|
||||
case 'START_LOADING': {
|
||||
return {
|
||||
...ui,
|
||||
load_queue: {
|
||||
...ui.load_queue || {},
|
||||
[action.key]: action.source || action.key,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'STOP_LOADING':
|
||||
return {
|
||||
|
||||
@ -8,10 +8,10 @@ import Thumbnail from '../components/Thumbnail';
|
||||
import Parallax from '../components/Parallax';
|
||||
import FollowButton from '../components/Fields/FollowButton';
|
||||
import ContextMenuTrigger from '../components/ContextMenuTrigger';
|
||||
import ArtistAbout from './subviews/ArtistAbout';
|
||||
import ArtistOverview from './subviews/ArtistOverview';
|
||||
import ArtistTracks from './subviews/ArtistTracks';
|
||||
import ArtistRelated from './subviews/ArtistRelated';
|
||||
import About from './Artist/About';
|
||||
import Overview from './Artist/Overview';
|
||||
import Tracks from './Artist/Tracks';
|
||||
import Related from './Artist/Related';
|
||||
import Loader from '../components/Loader';
|
||||
import * as coreActions from '../services/core/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
@ -23,7 +23,6 @@ import { encodeUri, decodeUri } from '../util/format';
|
||||
import { arrayOf } from '../util/arrays';
|
||||
import { i18n, I18n } from '../locale';
|
||||
import Button from '../components/Button';
|
||||
import { trackEvent } from '../components/Trackable';
|
||||
import {
|
||||
makeItemSelector,
|
||||
makeLoadingSelector,
|
||||
@ -186,16 +185,16 @@ const Artist = ({
|
||||
<div className="content-wrapper">
|
||||
<Switch>
|
||||
<Route exact path="/artist/:id/related-artists">
|
||||
<ArtistRelated artist={artist} />
|
||||
<Related artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/tracks">
|
||||
<ArtistTracks artist={artist} />
|
||||
<Tracks artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/about">
|
||||
<ArtistAbout artist={artist} />
|
||||
<About artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/:name?">
|
||||
<ArtistOverview artist={artist} albums={albums} />
|
||||
<Overview artist={artist} albums={albums} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { nice_number } from '../../components/NiceNumber';
|
||||
|
||||
const ArtistAbout = ({
|
||||
export default ({
|
||||
artist,
|
||||
}) => {
|
||||
const thumbnails = artist.images && Array.isArray(artist.images) && artist.images.map(
|
||||
@ -88,6 +88,4 @@ const ArtistAbout = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArtistAbout;
|
||||
};
|
||||
@ -11,11 +11,8 @@ import * as uiActions from '../../services/ui/actions';
|
||||
import * as spotifyActions from '../../services/spotify/actions';
|
||||
import { encodeUri } from '../../util/format';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { indexToArray } from '../../util/arrays';
|
||||
import { indexToArray, applyFilter } from '../../util/arrays';
|
||||
import { makeLoadingSelector } from '../../util/selectors';
|
||||
import { applyFilter } from '../../util/arrays';
|
||||
|
||||
const SORT_KEY = 'discover_categories';
|
||||
|
||||
const DiscoverCategories = ({
|
||||
loading,
|
||||
@ -1,160 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import Button from '../../components/Button';
|
||||
import RelatedArtists from '../../components/RelatedArtists';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { sortItems, applyFilter } from '../../util/arrays';
|
||||
import { encodeUri } from '../../util/format';
|
||||
import { getSortSelector } from '../../util/selectors';
|
||||
import {
|
||||
hideContextMenu,
|
||||
setSort,
|
||||
} from '../../services/ui/actions';
|
||||
|
||||
const SORT_KEY = 'artist_albums';
|
||||
|
||||
export default ({
|
||||
artist,
|
||||
albums: albumsProp,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [search, setSearch] = useState('');
|
||||
const [type, setType] = useState('');
|
||||
const [sortField, sortReverse] = useSelector(
|
||||
(state) => getSortSelector(state, SORT_KEY, null),
|
||||
);
|
||||
const {
|
||||
uri,
|
||||
tracks,
|
||||
related_artists,
|
||||
} = artist;
|
||||
let albums = albumsProp;
|
||||
|
||||
const onSortChange = (field) => {
|
||||
let reverse = false;
|
||||
if (field !== null && sortField === field) {
|
||||
reverse = !sortReverse;
|
||||
}
|
||||
|
||||
dispatch(setSort(SORT_KEY, field, reverse));
|
||||
dispatch(hideContextMenu());
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
dispatch(hideContextMenu());
|
||||
};
|
||||
|
||||
if (sortField && albums) {
|
||||
albums = sortItems(albums, sortField, sortReverse);
|
||||
}
|
||||
|
||||
if (type && albums) {
|
||||
albums = applyFilter('type', type, albums);
|
||||
}
|
||||
|
||||
if (search && search !== '') {
|
||||
albums = applyFilter('name', search, albums);
|
||||
}
|
||||
|
||||
const sort_options = [
|
||||
{
|
||||
value: null,
|
||||
label: i18n('artist.albums.sort.default'),
|
||||
},
|
||||
{
|
||||
value: 'name',
|
||||
label: i18n('artist.albums.sort.name'),
|
||||
},
|
||||
{
|
||||
value: 'release_date',
|
||||
label: i18n('artist.albums.sort.release_date'),
|
||||
},
|
||||
{
|
||||
value: 'tracks',
|
||||
label: i18n('artist.albums.sort.track_count'),
|
||||
},
|
||||
];
|
||||
|
||||
const filter_type_options = [
|
||||
{
|
||||
value: null,
|
||||
label: i18n('artist.albums.filter.all'),
|
||||
},
|
||||
{
|
||||
value: 'album',
|
||||
label: i18n('artist.albums.filter.albums'),
|
||||
},
|
||||
{
|
||||
value: 'single',
|
||||
label: i18n('artist.albums.filter.singles'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="body overview">
|
||||
<div className={`top-tracks col col--w${related_artists && related_artists.length > 0 ? '70' : '100'}`}>
|
||||
{tracks && <h4><I18n path="artist.overview.top_tracks" /></h4>}
|
||||
<div className="list-wrapper">
|
||||
<TrackList className="artist-track-list" uri={uri} tracks={tracks ? tracks.slice(0, 10) : []} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col col--w5" />
|
||||
|
||||
{related_artists && related_artists.length > 0 && (
|
||||
<div className="col col--w25 related-artists">
|
||||
<h4><I18n path="artist.overview.related_artists.title" /></h4>
|
||||
<div className="list-wrapper">
|
||||
<RelatedArtists artists={related_artists.slice(0, 6)} />
|
||||
</div>
|
||||
<Button
|
||||
to={`/artist/${encodeUri(uri)}/related-artists`}
|
||||
scrollTo="#sub-views-menu"
|
||||
>
|
||||
<I18n path="artist.overview.related_artists.more" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="cf" />
|
||||
|
||||
<div className="albums">
|
||||
<h4>
|
||||
<I18n path="artist.overview.albums" count={albums ? albums.length : 0} />
|
||||
<div className="actions-wrapper">
|
||||
<FilterField
|
||||
initialValue={search}
|
||||
handleChange={setSearch}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
<DropdownField
|
||||
icon="swap_vert"
|
||||
name="Sort"
|
||||
value={sortField}
|
||||
valueAsLabel
|
||||
options={sort_options}
|
||||
selected_icon={sortField ? (sortReverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={onSortChange}
|
||||
/>
|
||||
<DropdownField
|
||||
icon="filter_list"
|
||||
name="Filter"
|
||||
value={type}
|
||||
valueAsLabel
|
||||
options={filter_type_options}
|
||||
handleChange={setType}
|
||||
/>
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<section className="grid-wrapper no-top-padding">
|
||||
<Grid items={albums} />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user