2019-03-30 14:01:21 -07:00

275 lines
12 KiB
TypeScript

import React from 'react';
import Media from 'react-media';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import classNames from 'classnames';
import { AttackTypeEffectiveness } from 'app/models/Config';
import { League } from 'app/models/League';
import { CombatMoveStats, TypeEffectiveness } from 'app/models/Pokemon';
import * as ActionsPokemonApp from 'app/actions';
import * as ActionsPokemonExplorer from 'app/components/PokemonExplorer/actions';
import {
CombatMoveSelectorsOpen,
IndividualValueKey,
IPokemonExplorerDispatch,
IPokemonExplorerStore,
SelectedCombatMoves,
SubNavigation,
} from 'app/components/PokemonExplorer/types';
import { IRouterProps, Navigation as NavigationType } from 'app/types';
import { ExplorerLoading } from 'app/components/PokemonExplorer/ExplorerLoading';
import { LeagueIvExplorer } from 'app/components/PokemonExplorer/LeagueIvExplorer';
import { MovesExplorer } from 'app/components/PokemonExplorer/MovesExplorer';
import { Navigation } from 'app/components/PokemonExplorer/Navigation';
import { PokemonDisplay } from 'app/components/PokemonExplorer/PokemonDisplay';
import { EffectivenessMode, TypeEffectiveDisplay } from 'app/components/PokemonExplorer/TypeEffectiveDisplay';
import { appendQueryString } from 'app/utils/navigation';
import { calculateTypeCoverage } from 'app/utils/types';
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';
interface IPokemonExplorerProps extends IRouterProps {
isMenuOpen : boolean;
isOverlaid : boolean;
attackTypeEffectiveness : AttackTypeEffectiveness;
combatMoves : CombatMoveStats;
toggleInterruption : (isInterruption : boolean) => void;
}
interface IConnectedPokemonExplorerProps extends IPokemonExplorerStore, IPokemonExplorerDispatch, IPokemonExplorerProps {}
class PokemonExplorer extends React.Component<IConnectedPokemonExplorerProps> {
public render() {
const {
isMenuOpen,
isOverlaid,
combatMoves,
attackTypeEffectiveness,
} = this.props;
const {
isLoading,
league,
individualValues,
widgets,
leaguePokemon,
selectedCombatMoves,
combatMoveSelectorsOpen,
} = this.props.pokemonExplorerState;
const isSelectingMove = combatMoveSelectorsOpen.quickMove || combatMoveSelectorsOpen.chargeMove1 || combatMoveSelectorsOpen.chargeMove2;
const isOverlayShown = isOverlaid || isSelectingMove;
const navWrapperCss = classNames(
styles.navigationWrapper,
);
const displayWrapperCss = classNames(
styles.displayWrapper,
);
const overLayCss = classNames(
styles.overlay,
{
[styles.complete]: isSelectingMove
}
);
// weaknesses are indicated by types that cause super effective damage on defense
const pokemonTypeWeaknesses = leaguePokemon !== null ? this.getSuperEffectiveTypes(leaguePokemon.effectiveness) : [];
// strengths are indicuated by types that do super effective damage on offense
const moveTypeStrengths = this.getSuperEffectiveTypes(calculateTypeCoverage(selectedCombatMoves, combatMoves, attackTypeEffectiveness));
return (
<div className={ styles.body }>
{ (leaguePokemon === null || isLoading) &&
<ExplorerLoading />
}
{ leaguePokemon !== null && !isLoading &&
<React.Fragment>
<div className={ navWrapperCss }>
<div className={ displayWrapperCss }>
{ leaguePokemon !== null &&
<PokemonDisplay
leaguePokemon={ leaguePokemon }
isHighlighted={ isOverlayShown }
/>
}
{ widgets.types && leaguePokemon !== null &&
<div className="nes-container with-title">
<h3 className="title">Type Effectivess</h3>
<TypeEffectiveDisplay
mode={ EffectivenessMode.DEFENSE }
effectiveness={ leaguePokemon.effectiveness }
coverage={ moveTypeStrengths }
/>
</div>
}
{ widgets.moves && leaguePokemon !== null &&
<MovesExplorer
movesById={ combatMoves }
quickMoves={ leaguePokemon.moves.quick }
chargeMoves={ leaguePokemon.moves.cinematic }
selectedMoves={ selectedCombatMoves }
pokemonTypeWeaknesses={ pokemonTypeWeaknesses }
attackTypeEffectiveness={ attackTypeEffectiveness }
combatMoveSelectorsOpen={ combatMoveSelectorsOpen }
handleToggleDropdownOpen={ this.handleToggleDropdownOpen }
handleChangeSelectedMove={ this.handleChangeSelectedMove }
/>
}
{ widgets.pvp && leaguePokemon !== null &&
<LeagueIvExplorer
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
</div>
<Media query={ { minWidth: MIN_TABLET_WIDTH } }>
<Navigation
isMenu={ false }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
{ isMenuOpen &&
<Media query={ { maxWidth: MAX_MOBILE_WIDTH } }>
<Navigation
isMenu={ true }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
}
</div>
{ isOverlayShown &&
<div className={ overLayCss } onClick={ this.handleOverlayClick } />
}
</React.Fragment>
}
</div>
);
}
private readonly getSuperEffectiveTypes = (effectiveness : Map<PVPogoProtos.PokemonType, TypeEffectiveness>) => {
const superEffectiveTypes : Array<PVPogoProtos.PokemonType> = [];
Array.from(effectiveness).reduce((accumulator, currentValue) => {
if (currentValue[1] > TypeEffectiveness.NEUTRAL) {
accumulator.push(currentValue[0]);
}
return accumulator;
}, superEffectiveTypes);
return superEffectiveTypes;
}
private readonly handleToggleDropdownOpen = (menu : keyof CombatMoveSelectorsOpen, isOpen : boolean) => {
const combatMoveSelectorsOpen : CombatMoveSelectorsOpen = {
...this.props.pokemonExplorerState.combatMoveSelectorsOpen,
[menu]: isOpen, // 3/10/2019: TyepScript is not checking this!
};
this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen(combatMoveSelectorsOpen));
this.props.toggleInterruption(isOpen);
}
private readonly handleOverlayClick = () => {
this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen({
quickMove: false,
chargeMove1: false,
chargeMove2: false,
}));
this.props.toggleInterruption(false);
}
private readonly handleNavigationClick = (widget : SubNavigation | NavigationType) => {
if (widget === 'pokedex') {
this.props.dispatch(ActionsPokemonApp.setIsInterruption(true));
this.props.dispatch(ActionsPokemonApp.setNavigation('pokedex'));
} else if (widget !== 'menu') {
this.props.dispatch(ActionsPokemonExplorer.setSubNavigationWidgetIsShown(widget, !this.props.pokemonExplorerState.widgets[widget]));
}
}
private readonly handleChangeIndividualValue = (stat : IndividualValueKey, value : number | null) => {
const {
dispatch,
} = this.props;
switch (stat) {
case 'level':
dispatch(ActionsPokemonExplorer.setIvLevel(value));
break;
case 'hp':
dispatch(ActionsPokemonExplorer.setIvHp(value));
break;
case 'atk':
dispatch(ActionsPokemonExplorer.setIvAtk(value));
break;
case 'def':
dispatch(ActionsPokemonExplorer.setIvDef(value));
break;
default:
break;
}
}
private readonly handleMaximizeLevel = () => {
this.props.dispatch(ActionsPokemonExplorer.maximizeLevel());
}
private readonly handleChangeSelectedMove = (moves : SelectedCombatMoves) => {
this.props.dispatch(ActionsPokemonExplorer.setSelectedCombatMoves(moves));
}
// TODO: make this not overwrite other query parameters
private readonly handleChangeLeagueNavigation = (league : League) => {
const {
history,
location,
} = this.props;
history.push({
search: appendQueryString(location, { league: league.toString() })
});
this.handleChangeLeague(league);
}
private readonly handleChangeLeague = (league : League) => {
this.props.dispatch(ActionsPokemonExplorer.setActiveLeague(league));
}
}
const mapStateToProps = (state : IPokemonExplorerStore) : IPokemonExplorerStore => {
return {
...state,
};
};
const mapDispatchToProps = (dispatch : IPokemonExplorerDispatch['dispatch']) : IPokemonExplorerDispatch => {
return {
dispatch
};
};
const mergeProps = (state : IPokemonExplorerStore, dispatchProps : IPokemonExplorerDispatch, ownProps : IPokemonExplorerProps) : IConnectedPokemonExplorerProps => {
return {
...state,
...dispatchProps,
...ownProps,
};
};
export const ConnectedPokemonExplorer = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(PokemonExplorer));