215 lines
7.3 KiB
TypeScript
215 lines
7.3 KiB
TypeScript
import POGOProtos from 'pogo-protos';
|
|
|
|
import React from 'react';
|
|
import { ContentRect, default as Measure } from 'react-measure';
|
|
import { Link } from 'react-router-dom';
|
|
import { VariableSizeList } from 'react-window';
|
|
|
|
import classNames from 'classnames';
|
|
|
|
import { formatDexNumber, formatForm } from 'app/utils/formatter';
|
|
import { appendQueryString } from 'app/utils/navigation';
|
|
|
|
import { DEFAULT_POKEMON_NAME, IPokemon } from 'app/models/Pokemon';
|
|
|
|
import * as styles from './styles/PokemonSelectList.scss';
|
|
|
|
export interface IPokemonSelectListProps {
|
|
isLoading : boolean;
|
|
activePokemonId : POGOProtos.Enums.PokemonId | null;
|
|
activePokemonForm : POGOProtos.Enums.Form | null;
|
|
pokemonList : Array<IPokemon>;
|
|
filterTerm : string;
|
|
|
|
handleActivatePokemon : (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => void;
|
|
handleChangeFilter : (filterTerm : string) => Promise<void>;
|
|
}
|
|
|
|
interface IState {
|
|
dimensions : {
|
|
width : number;
|
|
height : number;
|
|
};
|
|
}
|
|
|
|
interface IRowFactory {
|
|
index : number;
|
|
style : React.CSSProperties;
|
|
}
|
|
|
|
export class PokemonSelectList extends React.Component<IPokemonSelectListProps, IState> {
|
|
private listRef : React.RefObject<VariableSizeList>;
|
|
|
|
constructor(props : IPokemonSelectListProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
dimensions: {
|
|
width: -1,
|
|
height: -1,
|
|
}
|
|
};
|
|
|
|
this.listRef = React.createRef();
|
|
}
|
|
|
|
public render() {
|
|
const { width, height } = this.state.dimensions;
|
|
const listLength = this.props.pokemonList.length;
|
|
|
|
const onResize = (contentRect : ContentRect) => {
|
|
if (typeof contentRect.bounds !== 'undefined') {
|
|
this.setState({ dimensions: contentRect.bounds });
|
|
}
|
|
};
|
|
const wrapperCss = classNames(
|
|
styles.leftPanel,
|
|
{
|
|
loading: this.props.isLoading,
|
|
}
|
|
);
|
|
const listWrapperCss = classNames(
|
|
'nes-container',
|
|
styles.listWrapper,
|
|
{
|
|
[ styles.emptyList ]: listLength === 0
|
|
}
|
|
);
|
|
const inputTextCss = classNames(
|
|
'nes-input',
|
|
styles.filterInput
|
|
);
|
|
|
|
return (
|
|
<div id="pokemon-select-list" className={ wrapperCss }>
|
|
<div className={ styles.filterWrapper }>
|
|
<input
|
|
name="filter"
|
|
type="text"
|
|
className={ inputTextCss }
|
|
onChange={ this.handleChangeFilter }
|
|
value={ this.props.filterTerm }
|
|
placeholder="Search"
|
|
/>
|
|
{ this.props.filterTerm !== '' &&
|
|
<i
|
|
className="nes-icon close is-small"
|
|
onClick={ this.handleClickClearFilter }
|
|
/>
|
|
}
|
|
</div>
|
|
<div className={ listWrapperCss }>
|
|
{ listLength > 0 &&
|
|
<Measure
|
|
bounds={ true }
|
|
onResize={ onResize }
|
|
>
|
|
{
|
|
({ measureRef }) => (
|
|
<div ref={ measureRef }>
|
|
<VariableSizeList
|
|
ref={ this.listRef }
|
|
height={ height }
|
|
itemKey={ this.getListItemKey }
|
|
itemCount={ listLength }
|
|
estimatedItemSize={ 25 }
|
|
itemSize={ this.calculateRowHeight }
|
|
width={ width }
|
|
>
|
|
{ this.rowFactory.bind(this) }
|
|
</VariableSizeList>
|
|
</div>
|
|
)
|
|
}
|
|
</Measure>
|
|
}
|
|
{ listLength === 0 && this.props.filterTerm !== '' &&
|
|
<div className={ styles.emptyState }>
|
|
<i className="pokemon-missing-no" />
|
|
<h3>{ DEFAULT_POKEMON_NAME }</h3>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
private readonly getListItemKey = (index : number) => {
|
|
const { pokemonList } = this.props;
|
|
const pokemon = pokemonList[index];
|
|
return `${index}-${pokemon.id}-${pokemon.form}`;
|
|
}
|
|
|
|
private readonly calculateRowHeight = (index : number) => {
|
|
return this.props.pokemonList[index].form === POGOProtos.Enums.Form.FORM_UNSET ? 25 : 40;
|
|
}
|
|
|
|
private rowFactory({ index, style } : IRowFactory) {
|
|
const pokemon = this.props.pokemonList[index];
|
|
const dex = formatDexNumber(pokemon.dex);
|
|
const anchorCss = classNames(
|
|
'list-item',
|
|
{
|
|
active: this.props.activePokemonId === pokemon.id && this.props.activePokemonForm === pokemon.form
|
|
}
|
|
);
|
|
// const menuIconCss = classNames(
|
|
// styles.menuIcon,
|
|
// 'menu',
|
|
// `pokemon-${dex}`
|
|
// );
|
|
const dexCss = classNames(
|
|
'de-emphasize',
|
|
styles.dex
|
|
);
|
|
const formCss = classNames(
|
|
'de-emphasize',
|
|
styles.form
|
|
);
|
|
const onClick = () => this.props.handleActivatePokemon(pokemon.id, pokemon.form);
|
|
const linkTo = {
|
|
// pathname: '/courses',
|
|
search: appendQueryString(location, {
|
|
id: pokemon.id.toString(),
|
|
form: pokemon.form.toString(),
|
|
}),
|
|
// hash: '#the-hash',
|
|
// state: { fromDashboard: true }
|
|
};
|
|
return (
|
|
<Link
|
|
to={ linkTo }
|
|
key={ this.getListItemKey(index) }
|
|
style={ style }
|
|
className={ anchorCss }
|
|
onClick={ onClick }
|
|
>
|
|
<span>{ pokemon.name }</span>
|
|
<span className={ dexCss }>#{ dex }</span>
|
|
{ /* <i className={ menuIconCss } /> */ }
|
|
{ pokemon.form !== POGOProtos.Enums.Form.FORM_UNSET &&
|
|
<span className={ formCss }>{ formatForm(pokemon.form) } Form</span>
|
|
}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
private readonly handleChangeFilter = (event : React.ChangeEvent<HTMLInputElement>) => {
|
|
this.props.handleChangeFilter(event.currentTarget.value)
|
|
.then(() => {
|
|
if (this.listRef.current !== null) {
|
|
this.listRef.current.resetAfterIndex(0, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
private readonly handleClickClearFilter = () => {
|
|
this.props.handleChangeFilter('')
|
|
.then(() => {
|
|
if (this.listRef.current !== null) {
|
|
this.listRef.current.resetAfterIndex(0, true);
|
|
}
|
|
});
|
|
}
|
|
}
|