pvpokemon/src/ts/app/PokemonApp.tsx
2019-05-10 13:03:06 -04:00

251 lines
9.5 KiB
TypeScript

import React from 'react';
import { ReactCookieProps, withCookies } from 'react-cookie';
import Media from 'react-media';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import classNames from 'classnames';
import { appReducers } from 'app/index';
import * as ActionsPokemonApp from 'app/actions';
import * as ActionsPokemonExplorer from 'app/components/PokemonExplorer/actions';
import * as ActionsPokemonSelectList from 'app/components/PokemonSelectList/actions';
import { IPokemonAppDispatch, IRouterProps, Navigation } from 'app/types';
import { convertFormParamToPokemonForm, convertIdParamToPokemonId, convertLeagueParamToLeague } from 'app/utils/navigation';
import { Footer } from 'app/components/Footer';
import { Header } from 'app/components/Header';
import { ConnectedPokemonExplorer } from 'app/components/PokemonExplorer/PokemonExplorer';
import { PokemonSelectList } from 'app/components/PokemonSelectList/PokemonSelectList';
import { WelcomeDialog } from 'app/components/WelcomeDialog';
import { MAX_MOBILE_WIDTH, MIN_TABLET_WIDTH } from 'common/models/constants';
import * as PVPogoProtos from 'common/models/PVPogoProtos';
import * as styles from 'app/styles/PokemonApp.scss';
type PokemonAppProps = ReturnType<typeof appReducers>;
interface IConnectedPokemonAppProps extends PokemonAppProps, ReactCookieProps, IPokemonAppDispatch, IRouterProps {}
class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
public async componentWillMount() {
const {
cookies,
dispatch,
match,
} = this.props;
const {
id,
form,
league,
} = match.params;
await Promise.all([
dispatch(ActionsPokemonApp.fetchConfig()),
// TODO: move this action to PokemonApp actions
dispatch(ActionsPokemonSelectList.fetchPokemonList())
]);
dispatch(ActionsPokemonSelectList.setIsLoading(false));
const pokemonId = convertIdParamToPokemonId(id);
const pokemonForm = convertFormParamToPokemonForm(form);
if (pokemonId !== null && pokemonForm !== null) {
this.handleActivatePokemon(pokemonId, pokemonForm);
}
const activeLeague = convertLeagueParamToLeague(league);
if (activeLeague !== null) {
dispatch(ActionsPokemonExplorer.setActiveLeague(activeLeague));
}
if (pokemonId === null || pokemonForm === null || typeof cookies === 'undefined' || !cookies.get('welcomed')) {
if (typeof cookies !== 'undefined') {
cookies.set('welcomed', 1);
}
this.handleOpenWelcomeDialog();
}
}
public render() {
const {
isWelcomeShown,
navigation,
isInterruption,
attackTypeEffectiveness,
combatMoves,
} = this.props.pokemonAppState;
const {
pokemonList,
pokemonListFiltered,
filterTerm,
} = this.props.pokemonSelectListState;
const {
leaguePokemon,
} = this.props.pokemonExplorerState;
const matchParams = this.props.match.params;
const activePokemonId = convertIdParamToPokemonId(matchParams.id);
const activePokemonForm = convertFormParamToPokemonForm(matchParams.form);
const isOverlayShown = isWelcomeShown || isInterruption;
const wrapperCss = classNames(
styles.wrapper,
{
[styles.overlaid]: isOverlayShown,
}
);
const iconCss = classNames(
'icon',
'pixel',
'sprite',
);
const pokedexCss = classNames(
iconCss,
'pokedex',
{
active: navigation === 'pokedex',
}
);
const pokedexButtonCss = classNames(
styles.navigationButton,
// {
// [styles.activeNavigationButton]: activeNavigation === 'pokedex',
// }
);
const hamburgerButtonCss = classNames(
styles.hamburgerIcon,
styles.navigationButton,
// {
// [styles.activeNavigationButton]: activeNavigation === 'menu',
// }
);
// react optimization for rendering on pokemon switching
// https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
const uniquePokemonId = leaguePokemon !== null ? `${leaguePokemon.id}~${leaguePokemon.form}` : undefined;
return (
<div className={ wrapperCss }>
<Header
handleHomeClick={ this.handleOpenWelcomeDialog }
>
<PokemonSelectList
isLoading={ this.props.pokemonSelectListState.isLoading }
isListOpen={ navigation === 'pokedex' }
activePokemonId={ activePokemonId }
activePokemonForm={ activePokemonForm }
pokemonList={ filterTerm === '' ? pokemonList : pokemonListFiltered }
filterTerm={ this.props.pokemonSelectListState.filterTerm }
handleActivatePokemon={ this.handleActivatePokemon }
handleChangeFilter={ this.handleChangeFilter }
/>
<Media query={ { minWidth: MIN_TABLET_WIDTH } }>
<button className={ pokedexButtonCss } onClick={ this.handlePokedexClick }><i className={ pokedexCss } /></button>
</Media>
<Media query={ { maxWidth: MAX_MOBILE_WIDTH } }>
<button className={ hamburgerButtonCss } onClick={ this.handleMenuClick }>&#9776;</button>
</Media>
</Header>
<ConnectedPokemonExplorer
key={ uniquePokemonId }
isMenuOpen={ navigation === 'menu' }
isOverlaid={ isOverlayShown }
attackTypeEffectiveness={ attackTypeEffectiveness }
combatMoves={ combatMoves }
toggleInterruption={ this.handleToggleInterruption }
/>
{ isWelcomeShown &&
<WelcomeDialog
handleClose={ this.handleCloseWelcomeDialog }
/>
}
<Footer />
</div>
);
}
private readonly handleOpenWelcomeDialog = () => {
this.props.dispatch(ActionsPokemonApp.setWelcomeShown(true));
}
private readonly handleCloseWelcomeDialog = () => {
this.props.dispatch(ActionsPokemonApp.setWelcomeShown(false));
const matchParams = this.props.match.params;
const activePokemonId = convertIdParamToPokemonId(matchParams.id);
const activePokemonForm = convertFormParamToPokemonForm(matchParams.form);
if (activePokemonId === null || activePokemonForm === null) {
// this.handleActivatePokemon(1, 0); // set active pokemon to bulbasaur
this.props.history.replace('/explorer/1/0');
}
}
private readonly handleInterruption = (isInterruption : boolean, navigation : Navigation) => {
const { dispatch } = this.props;
dispatch(ActionsPokemonApp.setNavigation(isInterruption ? navigation : null));
dispatch(ActionsPokemonApp.setIsInterruption(isInterruption));
if (isInterruption) {
dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen({
quickMove: false,
chargeMove1: false,
chargeMove2: false,
}));
}
}
private readonly handleToggleInterruption = (isInterruption : boolean) => {
this.props.dispatch(ActionsPokemonApp.setIsInterruption(isInterruption));
if (!isInterruption) {
this.props.dispatch(ActionsPokemonApp.setNavigation(null));
this.handleCloseWelcomeDialog();
}
}
private readonly handlePokedexClick = () => {
this.handleInterruption(true, 'pokedex');
}
private readonly handleMenuClick = () => {
this.handleInterruption(true, 'menu');
}
private readonly handleActivatePokemon = async (pokemonId : PVPogoProtos.PokemonId, form : PVPogoProtos.PokemonForm) => {
const { dispatch } = this.props;
dispatch(ActionsPokemonExplorer.setIsLoading(true));
try {
const leaguePokemon = await dispatch(ActionsPokemonApp.fetchPokemonLeagueStats(pokemonId, form));
dispatch(ActionsPokemonExplorer.reset(leaguePokemon));
} catch (error) {
// tslint:disable-next-line:no-console
console.error(error);
dispatch(ActionsPokemonExplorer.setLeaguePokemon(null));
}
dispatch(ActionsPokemonExplorer.setIsLoading(false));
this.handleInterruption(false, 'pokedex');
}
private readonly handleChangeFilter = (filterTerm : string) => {
this.handleInterruption(true, 'pokedex');
return this.props.dispatch(ActionsPokemonSelectList.filterPokemonList(filterTerm));
}
}
const mapStateToProps = (state : PokemonAppProps) : PokemonAppProps => {
return {
pokemonAppState: state.pokemonAppState,
pokemonExplorerState: state.pokemonExplorerState,
pokemonSelectListState: state.pokemonSelectListState,
};
};
export const ConnectedPokemonApp = withCookies(withRouter(connect(mapStateToProps)(PokemonApp)));