275 lines
12 KiB
TypeScript
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));
|