split pokemon explorer from pokemon app

This commit is contained in:
Jeff Colombo 2019-03-11 00:43:22 -04:00
parent 7cd14f2b58
commit dd37703417
15 changed files with 1453 additions and 947 deletions

126
dist/app.css vendored
View File

@ -440,69 +440,6 @@
align-items: start; align-items: start;
align-self: stretch; } align-self: stretch; }
.PokemonSelectList__wrapper__2LQMY {
font-size: 0.8rem;
width: 20em;
display: flex;
flex-flow: column nowrap;
margin: 0 1.5em 0 auto;
position: relative; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6 {
flex: 1 1 auto;
display: flex;
padding: 0;
overflow: hidden;
height: 340px;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 2; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6 > * {
width: 100%; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6.PokemonSelectList__emptyList__1vgpK .PokemonSelectList__emptyState__3sBmb {
align-self: center;
text-align: center; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6.PokemonSelectList__emptyList__1vgpK .PokemonSelectList__emptyState__3sBmb > *:first-child {
margin: 1em auto; }
.PokemonSelectList__wrapper__2LQMY a {
color: inherit;
text-decoration: none;
padding: 5px 1em 5px 2em;
justify-content: flex-end;
align-content: space-around;
flex-wrap: wrap;
display: flex; }
.PokemonSelectList__wrapper__2LQMY a .PokemonSelectList__menuIcon__1I2_T {
margin: -4px 0 0 0.5em;
opacity: 0.5; }
.PokemonSelectList__wrapper__2LQMY a.active .PokemonSelectList__menuIcon__1I2_T {
opacity: 1; }
.PokemonSelectList__filterWrapper__1d1Wl {
position: relative;
margin: 0.1em; }
.PokemonSelectList__filterWrapper__1d1Wl .close {
position: absolute;
top: 1em;
right: 1em; }
.PokemonSelectList__filterInput__1z_s2 {
margin-left: 0;
margin-right: 0;
padding-right: 3em; }
.PokemonSelectList__dex__1QHut,
.PokemonSelectList__form__VIw8Q {
font-size: 0.8em;
text-transform: capitalize; }
.PokemonSelectList__dex__1QHut {
margin-left: auto; }
.PokemonSelectList__form__VIw8Q {
flex: 0 1 100%; }
.TypeEffectiveDisplay__multiplierWrapper__1E9zx { .TypeEffectiveDisplay__multiplierWrapper__1E9zx {
position: relative; position: relative;
z-index: 0; z-index: 0;
@ -567,5 +504,68 @@
flex-basis: unset; flex-basis: unset;
width: 6.75rem; } width: 6.75rem; }
.PokemonSelectList__wrapper__2LQMY {
font-size: 0.8rem;
width: 20em;
display: flex;
flex-flow: column nowrap;
margin: 0 1.5em 0 auto;
position: relative; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6 {
flex: 1 1 auto;
display: flex;
padding: 0;
overflow: hidden;
height: 340px;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 2; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6 > * {
width: 100%; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6.PokemonSelectList__emptyList__1vgpK .PokemonSelectList__emptyState__3sBmb {
align-self: center;
text-align: center; }
.PokemonSelectList__wrapper__2LQMY .PokemonSelectList__listWrapper__bBtO6.PokemonSelectList__emptyList__1vgpK .PokemonSelectList__emptyState__3sBmb > *:first-child {
margin: 1em auto; }
.PokemonSelectList__wrapper__2LQMY a {
color: inherit;
text-decoration: none;
padding: 5px 1em 5px 2em;
justify-content: flex-end;
align-content: space-around;
flex-wrap: wrap;
display: flex; }
.PokemonSelectList__wrapper__2LQMY a .PokemonSelectList__menuIcon__1I2_T {
margin: -4px 0 0 0.5em;
opacity: 0.5; }
.PokemonSelectList__wrapper__2LQMY a.active .PokemonSelectList__menuIcon__1I2_T {
opacity: 1; }
.PokemonSelectList__filterWrapper__1d1Wl {
position: relative;
margin: 0.1em; }
.PokemonSelectList__filterWrapper__1d1Wl .close {
position: absolute;
top: 1em;
right: 1em; }
.PokemonSelectList__filterInput__1z_s2 {
margin-left: 0;
margin-right: 0;
padding-right: 3em; }
.PokemonSelectList__dex__1QHut,
.PokemonSelectList__form__VIw8Q {
font-size: 0.8em;
text-transform: capitalize; }
.PokemonSelectList__dex__1QHut {
margin-left: auto; }
.PokemonSelectList__form__VIw8Q {
flex: 0 1 100%; }
/*# sourceMappingURL=main.tmp.css.map*/ /*# sourceMappingURL=main.tmp.css.map*/

1116
dist/main-bundle.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -10,35 +10,28 @@ import { League } from 'app/models/League';
import { appReducers } from 'app/index'; import { appReducers } from 'app/index';
import * as ActionsPokemonExplorer from 'app/actions'; import * as ActionsPokemonApp from 'app/actions';
import * as ActionsPokemonExplorer from 'app/components/PokemonExplorer/actions';
import * as ActionsPokemonSelectList from 'app/components/PokemonSelectList/actions'; import * as ActionsPokemonSelectList from 'app/components/PokemonSelectList/actions';
import { CombatMoveSelectorsOpen, IndividualValueKey, IPokemonAppDispatch, SelectedCombatMoves } from 'app/types'; import { IPokemonAppDispatch, IRouterProps } from 'app/types';
import { Footer } from 'app/components/Footer'; import { Footer } from 'app/components/Footer';
import { Header } from 'app/components/Header'; import { Header } from 'app/components/Header';
import { LeagueIvExplorer } from 'app/components/LeagueIvExplorer'; import { ConnectedPokemonExplorer } from 'app/components/PokemonExplorer/PokemonExplorer';
import { MovesExplorer } from 'app/components/MovesExplorer';
import { PokemonDisplay } from 'app/components/PokemonDisplay';
import { PokemonSelectList } from 'app/components/PokemonSelectList/PokemonSelectList'; import { PokemonSelectList } from 'app/components/PokemonSelectList/PokemonSelectList';
import { TypeEffectiveDisplay } from 'app/components/TypeEffectiveDisplay';
import { appendQueryString, getCurrentQueryStringVlaues } from 'app/utils/navigation'; import { getCurrentQueryStringVlaues } from 'app/utils/navigation';
import * as styles from 'app/styles/PokemonApp.scss'; import * as styles from 'app/styles/PokemonApp.scss';
type PokemonAppProps = ReturnType<typeof appReducers>; type PokemonAppProps = ReturnType<typeof appReducers>;
interface IConnectedPokemonAppProps extends PokemonAppProps, IPokemonAppDispatch { interface IConnectedPokemonAppProps extends PokemonAppProps, IPokemonAppDispatch, IRouterProps {}
history : RouteComponentProps['history'];
location : RouteComponentProps['location'];
}
// TODO: this should be on PokemonAppState
export type Navigation = 'pokedex'; export type Navigation = 'pokedex';
type SubNavigation = 'pvp' | 'types' | 'moves';
interface IState { interface IState {
isInterruption : boolean;
activeNavigation : Navigation | null; activeNavigation : Navigation | null;
widgets : { [ key in SubNavigation ] : boolean };
} }
class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> { class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
@ -47,13 +40,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
super(props); super(props);
this.state = { this.state = {
isInterruption: false,
activeNavigation: null, activeNavigation: null,
widgets: {
pvp: true,
types: false,
moves: false,
},
}; };
} }
@ -64,7 +51,8 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
} = this.props; } = this.props;
await Promise.all([ await Promise.all([
dispatch(ActionsPokemonExplorer.fetchConfig()), dispatch(ActionsPokemonApp.fetchConfig()),
// TODO: move this action to PokemonApp actions
dispatch(ActionsPokemonSelectList.fetchPokemonList()) dispatch(ActionsPokemonSelectList.fetchPokemonList())
]); ]);
dispatch(ActionsPokemonSelectList.setIsLoading(false)); dispatch(ActionsPokemonSelectList.setIsLoading(false));
@ -90,6 +78,11 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
} }
public render() { public render() {
const {
isInterruption,
attackTypeEffectiveness,
combatMoves,
} = this.props.pokemonAppState;
const { const {
activePokemonId, activePokemonId,
activePokemonForm, activePokemonForm,
@ -98,36 +91,16 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
filterTerm, filterTerm,
} = this.props.pokemonSelectListState; } = this.props.pokemonSelectListState;
const { const {
league,
individualValues,
leaguePokemon,
combatMoves,
selectedCombatMoves,
combatMoveSelectorsOpen,
} = this.props.pokemonExplorerState;
const {
isInterruption,
activeNavigation, activeNavigation,
widgets,
} = this.state; } = this.state;
const isOverlayShown = isInterruption || activeNavigation === 'pokedex';
const wrapperCss = classNames( const wrapperCss = classNames(
styles.wrapper, styles.wrapper,
{ {
[styles.overlaid]: isOverlayShown, [styles.overlaid]: isInterruption,
} }
); );
const leftNavCss = classNames(
styles.leftNavigation,
);
const displayWrapperCss = classNames(
styles.displayWrapper,
);
const iconCss = classNames( const iconCss = classNames(
'icon', 'icon',
'pixel', 'pixel',
@ -147,43 +120,6 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
// } // }
); );
const pvpCss = classNames(
iconCss,
'pvp',
{
active: widgets.pvp,
}
);
const pvpButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.pvp,
// }
);
const badgeCss = classNames(
iconCss,
'badge',
{
active: widgets.types,
}
);
const badgeButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.types,
// }
);
const tmCss = classNames(
iconCss,
'tm',
{
active: widgets.moves,
}
);
const tmButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.moves,
// }
);
return ( return (
<div className={ wrapperCss }> <div className={ wrapperCss }>
<Header> <Header>
@ -199,114 +135,45 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
/> />
<button className={ pokedexButtonCss } onClick={ this.handlePokedexClick }><i className={ pokedexCss } /></button> <button className={ pokedexButtonCss } onClick={ this.handlePokedexClick }><i className={ pokedexCss } /></button>
</Header> </Header>
<div className={ styles.body }> <ConnectedPokemonExplorer
<div className={ displayWrapperCss }> isOverlaid={ isInterruption }
{ leaguePokemon !== null && attackTypeEffectiveness={ attackTypeEffectiveness }
<PokemonDisplay combatMoves={ combatMoves }
leaguePokemon={ leaguePokemon } toggleInterruption={ this.handleToggleInterruption }
isHighlighted={ isOverlayShown } history={ this.props.history }
location={ this.props.location }
/> />
}
{ widgets.types && leaguePokemon !== null &&
<TypeEffectiveDisplay
effectiveness={ leaguePokemon.effectiveness }
pokemonName={ leaguePokemon.name }
/>
}
{ widgets.pvp && leaguePokemon !== null &&
<LeagueIvExplorer
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
{ widgets.moves && leaguePokemon !== null &&
<MovesExplorer
movesById={ combatMoves }
quickMoves={ leaguePokemon.moves.quick }
chargeMoves={ leaguePokemon.moves.cinematic }
selectedMoves={ selectedCombatMoves }
attackTypeEffectiveness={ null }
combatMoveSelectorsOpen={ combatMoveSelectorsOpen }
handleToggleDropdownOpen={ this.handleToggleDropdownOpen }
handleChangeSelectedMove={ this.handleChangeSelectedMove }
/>
}
</div>
<div className={ leftNavCss }>
<button className={ pvpButtonCss } onClick={ this.handlePvpClick }><i className={ pvpCss } /></button>
<button className={ badgeButtonCss } onClick={ this.handleTypesClick }><i className={ badgeCss } /></button>
<button className={ tmButtonCss } onClick={ this.handleMovesClick }><i className={ tmCss } /></button>
</div>
{ isOverlayShown &&
<div className={ styles.overlay } onClick={ this.handleOverlayClick } />
}
</div>
<Footer /> <Footer />
</div> </div>
); );
} }
private readonly handleToggleDropdownOpen = (menu : keyof CombatMoveSelectorsOpen, isOpen : boolean) => { private readonly handleSearchInterruption = (isInterruption : boolean) => {
this.setState({ this.setState({
isInterruption: isOpen, activeNavigation: isInterruption ? 'pokedex' : null,
}); });
const combatMoveSelectorsOpen : CombatMoveSelectorsOpen = { this.props.dispatch(ActionsPokemonApp.setIsInterruption(isInterruption));
...this.props.pokemonExplorerState.combatMoveSelectorsOpen,
[menu]: isOpen, // 3/10/2019: TyepScript is not checking this!
};
this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen(combatMoveSelectorsOpen));
}
private readonly handleOverlayClick = () => { if (isInterruption) {
this.setState({
activeNavigation: null,
isInterruption: false,
});
this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen({ this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen({
quickMove: false, quickMove: false,
chargeMove1: false, chargeMove1: false,
chargeMove2: false, chargeMove2: false,
})); }));
} }
}
private readonly handleToggleInterruption = (isInterruption : boolean) => {
this.props.dispatch(ActionsPokemonApp.setIsInterruption(isInterruption));
if (!isInterruption) {
this.setState({
activeNavigation: null,
});
}
}
private readonly handlePokedexClick = () => { private readonly handlePokedexClick = () => {
this.setState({ this.handleSearchInterruption(true);
activeNavigation: this.state.activeNavigation !== 'pokedex' ? 'pokedex' : null,
});
}
private readonly handlePvpClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
pvp: !widgets.pvp
}
});
}
private readonly handleTypesClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
types: !widgets.types
}
});
}
private readonly handleMovesClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
moves: !widgets.moves
}
});
} }
private readonly handleActivatePokemon = (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => { private readonly handleActivatePokemon = (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => {
@ -335,56 +202,10 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
} }
private readonly handleChangeFilter = (filterTerm : string) => { private readonly handleChangeFilter = (filterTerm : string) => {
this.setState({ this.handleSearchInterruption(true);
activeNavigation: 'pokedex',
});
return this.props.dispatch(ActionsPokemonSelectList.filterPokemonList(filterTerm)); return this.props.dispatch(ActionsPokemonSelectList.filterPokemonList(filterTerm));
} }
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));
}
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) => { private readonly handleChangeLeague = (league : League) => {
this.props.dispatch(ActionsPokemonExplorer.setActiveLeague(league)); this.props.dispatch(ActionsPokemonExplorer.setActiveLeague(league));
} }
@ -392,6 +213,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
const mapStateToProps = (state : PokemonAppProps) : PokemonAppProps => { const mapStateToProps = (state : PokemonAppProps) : PokemonAppProps => {
return { return {
pokemonAppState: state.pokemonAppState,
pokemonExplorerState: state.pokemonExplorerState, pokemonExplorerState: state.pokemonExplorerState,
pokemonSelectListState: state.pokemonSelectListState, pokemonSelectListState: state.pokemonSelectListState,
}; };

View File

@ -1,71 +1,23 @@
import { action } from 'typesafe-actions'; import { action } from 'typesafe-actions';
import { CombatMoveSelectorsOpen, PokemonExplorerActionTypes, SelectedCombatMoves, ThunkResult } from 'app/types'; import { PokemonAppActionTypes, ThunkResult } from 'app/types';
import { calculateMaxLevelForLeague } from 'app/utils/calculator'; import { AttackTypeEffectiveness } from 'app/models/Config';
import { ILeaguePokemon, League } from 'app/models/League';
import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon'; import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading }); export const setIsInterruption = (isInterruption : boolean) => action(PokemonAppActionTypes.SET_IS_INTERRUPTION, { isInterruption });
export const setMaxPossibleStats = (maxStats : IMaxStats) => action(PokemonAppActionTypes.SET_MAX_STATS, { maxStats });
export const setMaxPossibleStats = (maxStats : IMaxStats) => action(PokemonExplorerActionTypes.SET_MAX_STATS, { maxStats }); export const setAttackTypeEffectiveness = (attackTypeEffectiveness : AttackTypeEffectiveness) => action(PokemonAppActionTypes.SET_COMBAT_MOVE_STATS, { attackTypeEffectiveness });
export const setCombatMoveStats = (combatMoves : CombatMoveStats) => action(PokemonExplorerActionTypes.SET_COMBAT_MOVE_STATS, { combatMoves }); export const setCombatMoveStats = (combatMoves : CombatMoveStats) => action(PokemonAppActionTypes.SET_COMBAT_MOVE_STATS, { combatMoves });
export const setLeaguePokemon = (leaguePokemon : ILeaguePokemon | null) => action(PokemonExplorerActionTypes.SET_LEAGUE_POKEMON, { leaguePokemon });
export const setIvLevel = (level : number | null) => action(PokemonExplorerActionTypes.SET_IV_LEVEL, { level });
export const setIvHp = (ivHp : number | null) => action(PokemonExplorerActionTypes.SET_IV_HP, { ivHp });
export const setIvAtk = (ivAtk : number | null) => action(PokemonExplorerActionTypes.SET_IV_ATK, { ivAtk });
export const setIvDef = (ivDef : number | null) => action(PokemonExplorerActionTypes.SET_IV_DEF, { ivDef });
export const setActiveLeague = (league : League) => action(PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE, { league });
export const setSelectedCombatMoves = (moves : SelectedCombatMoves) => action(PokemonExplorerActionTypes.SET_SELECTED_COMBAT_MOVES, { moves });
export const setCombatMoveSelectorsOpen = (selectorsOpen : CombatMoveSelectorsOpen) => action(PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN, { selectorsOpen });
export const fetchConfig = ( export const fetchConfig = (
) : ThunkResult<Promise<void>> => { ) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => { return async (dispatch, getState, extraArguments) => {
const config = await extraArguments.services.pokemonService.getConfig(); const config = await extraArguments.services.pokemonService.getConfig();
dispatch(setMaxPossibleStats(config.maxPossibleStats)); dispatch(setMaxPossibleStats(config.maxPossibleStats));
dispatch(setAttackTypeEffectiveness(config.attackTypeEffectiveness));
dispatch(setCombatMoveStats(config.combatMoves)); dispatch(setCombatMoveStats(config.combatMoves));
}; };
}; };
export const maximizeLevel = (
) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => {
const pokemonExplorerState = getState().pokemonExplorerState;
const {
ivHp,
ivAtk,
ivDef,
} = pokemonExplorerState.individualValues;
if (pokemonExplorerState.leaguePokemon !== null) {
const pokemonLeagueValues = pokemonExplorerState.leaguePokemon.pvp[pokemonExplorerState.league];
const statsSet = pokemonLeagueValues.some((stats) => {
if (((ivHp === null) || (stats.ivHp === ivHp)) &&
((ivAtk === null) || (stats.ivAtk === ivAtk)) &&
((ivDef === null) || (stats.ivDef === ivDef))
) {
dispatch(setIvHp(stats.ivHp));
dispatch(setIvAtk(stats.ivAtk));
dispatch(setIvDef(stats.ivDef));
dispatch(setIvLevel(stats.level));
return true;
}
return false;
});
if (!statsSet && ivHp !== null && ivAtk !== null && ivDef !== null) {
dispatch(setIvLevel(calculateMaxLevelForLeague(pokemonExplorerState.leaguePokemon.stats, ivHp, ivAtk, ivDef, pokemonExplorerState.league)));
}
}
};
};

View File

@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { IIndividualValues, IndividualValueKey } from '../types'; import { IIndividualValues, IndividualValueKey } from 'app/components/PokemonExplorer/types';
import * as styles from 'app/styles/IvForm.scss'; import * as styles from 'app/styles/IvForm.scss';

View File

@ -6,7 +6,7 @@ import { IBestWorstStats, ILeaguePokemon, League, MaxCpByLeague } from 'app/mode
import { Grade, IStats } from 'app/models/Pokemon'; import { Grade, IStats } from 'app/models/Pokemon';
import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator'; import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator';
import { IIndividualValues, IndividualValueKey } from 'app/types'; import { IIndividualValues, IndividualValueKey } from 'app/components/PokemonExplorer/types';
import { IvForm } from './IvForm'; import { IvForm } from './IvForm';
import { LeagueSelector } from './LeagueSelector'; import { LeagueSelector } from './LeagueSelector';

View File

@ -6,8 +6,8 @@ import { FixedSizeList } from 'react-window';
import classNames from 'classnames'; import classNames from 'classnames';
import { IIndividualValues } from 'app/components/PokemonExplorer/types';
import { Grade, IStats } from 'app/models/Pokemon'; import { Grade, IStats } from 'app/models/Pokemon';
import { IIndividualValues } from 'app/types';
import * as styles from 'app/styles/LeagueStatsList.scss'; import * as styles from 'app/styles/LeagueStatsList.scss';

View File

@ -5,7 +5,7 @@ import classNames from 'classnames';
import { AttackTypeEffectiveness } from 'app/models/Config'; import { AttackTypeEffectiveness } from 'app/models/Config';
import { CombatMoveStats, ICombatMoveStats, IPokemonMove } from 'app/models/Pokemon'; import { CombatMoveStats, ICombatMoveStats, IPokemonMove } from 'app/models/Pokemon';
import { CombatMoveSelectorsOpen, SelectedCombatMoves } from 'app/types'; import { CombatMoveSelectorsOpen, SelectedCombatMoves } from 'app/components/PokemonExplorer/types';
import { MovesDropdown } from 'app/components/MovesDropdown'; import { MovesDropdown } from 'app/components/MovesDropdown';
import { TypeIndicator } from './TypeIndicator'; import { TypeIndicator } from './TypeIndicator';
@ -21,7 +21,7 @@ export interface IMovesExplorerProps {
chargeMove1 : IPokemonMove | null; chargeMove1 : IPokemonMove | null;
chargeMove2 : IPokemonMove | null; chargeMove2 : IPokemonMove | null;
}; };
attackTypeEffectiveness : AttackTypeEffectiveness | null; attackTypeEffectiveness : AttackTypeEffectiveness;
combatMoveSelectorsOpen : CombatMoveSelectorsOpen; combatMoveSelectorsOpen : CombatMoveSelectorsOpen;
handleToggleDropdownOpen : (menu : keyof CombatMoveSelectorsOpen, isOpen : boolean) => void; handleToggleDropdownOpen : (menu : keyof CombatMoveSelectorsOpen, isOpen : boolean) => void;
handleChangeSelectedMove : (moves : SelectedCombatMoves) => void; handleChangeSelectedMove : (moves : SelectedCombatMoves) => void;

View File

@ -0,0 +1,298 @@
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router-dom';
import classNames from 'classnames';
import { AttackTypeEffectiveness } from 'app/models/Config';
import { League } from 'app/models/League';
import { CombatMoveStats } from 'app/models/Pokemon';
import * as ActionsPokemonExplorer from 'app/components/PokemonExplorer/actions';
import {
CombatMoveSelectorsOpen,
IndividualValueKey,
IPokemonExplorerDispatch,
IPokemonExplorerStore,
SelectedCombatMoves
} from 'app/components/PokemonExplorer/types';
import { IRouterProps } from 'app/types';
import { LeagueIvExplorer } from 'app/components/LeagueIvExplorer';
import { MovesExplorer } from 'app/components/MovesExplorer';
import { PokemonDisplay } from 'app/components/PokemonDisplay';
import { TypeEffectiveDisplay } from 'app/components/TypeEffectiveDisplay';
import { appendQueryString } from 'app/utils/navigation';
import * as styles from 'app/styles/PokemonApp.scss';
// TODO: better way to expose IRouterProps than just passing them in???
interface IPokemonExplorerProps extends IRouterProps {
isOverlaid : boolean;
attackTypeEffectiveness : AttackTypeEffectiveness;
combatMoves : CombatMoveStats;
toggleInterruption : (isInterruption : boolean) => void;
}
interface IConnectedPokemonExplorerProps extends IPokemonExplorerStore, IPokemonExplorerDispatch, IPokemonExplorerProps {}
type SubNavigation = 'pvp' | 'types' | 'moves';
interface IState {
widgets : { [ key in SubNavigation ] : boolean };
}
class PokemonExplorer extends React.Component<IConnectedPokemonExplorerProps, IState> {
constructor(props : IConnectedPokemonExplorerProps) {
super(props);
this.state = {
widgets: {
pvp: true,
types: false,
moves: false,
},
};
}
public render() {
const {
combatMoves,
attackTypeEffectiveness,
} = this.props;
const {
league,
individualValues,
leaguePokemon,
selectedCombatMoves,
combatMoveSelectorsOpen,
} = this.props.pokemonExplorerState;
const {
widgets,
} = this.state;
const isOverlayShown = this.props.isOverlaid || combatMoveSelectorsOpen.quickMove || combatMoveSelectorsOpen.chargeMove1 || combatMoveSelectorsOpen.chargeMove2;
const leftNavCss = classNames(
styles.leftNavigation,
);
const displayWrapperCss = classNames(
styles.displayWrapper,
);
const iconCss = classNames(
'icon',
'pixel',
'sprite',
);
const pvpCss = classNames(
iconCss,
'pvp',
{
active: widgets.pvp,
}
);
const pvpButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.pvp,
// }
);
const badgeCss = classNames(
iconCss,
'badge',
{
active: widgets.types,
}
);
const badgeButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.types,
// }
);
const tmCss = classNames(
iconCss,
'tm',
{
active: widgets.moves,
}
);
const tmButtonCss = classNames(
// {
// [styles.activeNavigationButton]: widgets.moves,
// }
);
return (
<div className={ styles.body }>
<div className={ displayWrapperCss }>
{ leaguePokemon !== null &&
<PokemonDisplay
leaguePokemon={ leaguePokemon }
isHighlighted={ isOverlayShown }
/>
}
{ widgets.types && leaguePokemon !== null &&
<TypeEffectiveDisplay
effectiveness={ leaguePokemon.effectiveness }
pokemonName={ leaguePokemon.name }
/>
}
{ widgets.pvp && leaguePokemon !== null &&
<LeagueIvExplorer
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
{ widgets.moves && leaguePokemon !== null &&
<MovesExplorer
movesById={ combatMoves }
quickMoves={ leaguePokemon.moves.quick }
chargeMoves={ leaguePokemon.moves.cinematic }
selectedMoves={ selectedCombatMoves }
attackTypeEffectiveness={ attackTypeEffectiveness }
combatMoveSelectorsOpen={ combatMoveSelectorsOpen }
handleToggleDropdownOpen={ this.handleToggleDropdownOpen }
handleChangeSelectedMove={ this.handleChangeSelectedMove }
/>
}
</div>
<div className={ leftNavCss }>
<button className={ pvpButtonCss } onClick={ this.handlePvpClick }><i className={ pvpCss } /></button>
<button className={ badgeButtonCss } onClick={ this.handleTypesClick }><i className={ badgeCss } /></button>
<button className={ tmButtonCss } onClick={ this.handleMovesClick }><i className={ tmCss } /></button>
</div>
{ isOverlayShown &&
<div className={ styles.overlay } onClick={ this.handleOverlayClick } />
}
</div>
);
}
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(true);
}
private readonly handleOverlayClick = () => {
this.props.dispatch(ActionsPokemonExplorer.setCombatMoveSelectorsOpen({
quickMove: false,
chargeMove1: false,
chargeMove2: false,
}));
this.props.toggleInterruption(false);
}
private readonly handlePvpClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
pvp: !widgets.pvp
}
});
}
private readonly handleTypesClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
types: !widgets.types
}
});
}
private readonly handleMovesClick = () => {
const widgets = this.state.widgets;
this.setState({
widgets: {
...widgets,
moves: !widgets.moves
}
});
}
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,
history: ownProps.history,
location: ownProps.location,
};
};
export const ConnectedPokemonExplorer = connect(mapStateToProps, mapDispatchToProps, mergeProps)(PokemonExplorer);

View File

@ -0,0 +1,62 @@
import { action } from 'typesafe-actions';
import {
CombatMoveSelectorsOpen,
PokemonExplorerActionTypes,
SelectedCombatMoves,
ThunkResult
} from 'app/components/PokemonExplorer/types';
import { calculateMaxLevelForLeague } from 'app/utils/calculator';
import { ILeaguePokemon, League } from 'app/models/League';
export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading });
export const setLeaguePokemon = (leaguePokemon : ILeaguePokemon | null) => action(PokemonExplorerActionTypes.SET_LEAGUE_POKEMON, { leaguePokemon });
export const setIvLevel = (level : number | null) => action(PokemonExplorerActionTypes.SET_IV_LEVEL, { level });
export const setIvHp = (ivHp : number | null) => action(PokemonExplorerActionTypes.SET_IV_HP, { ivHp });
export const setIvAtk = (ivAtk : number | null) => action(PokemonExplorerActionTypes.SET_IV_ATK, { ivAtk });
export const setIvDef = (ivDef : number | null) => action(PokemonExplorerActionTypes.SET_IV_DEF, { ivDef });
export const setActiveLeague = (league : League) => action(PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE, { league });
export const setSelectedCombatMoves = (moves : SelectedCombatMoves) => action(PokemonExplorerActionTypes.SET_SELECTED_COMBAT_MOVES, { moves });
export const setCombatMoveSelectorsOpen = (selectorsOpen : CombatMoveSelectorsOpen) => action(PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN, { selectorsOpen });
export const maximizeLevel = (
) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => {
const pokemonExplorerState = getState().pokemonExplorerState;
const {
ivHp,
ivAtk,
ivDef,
} = pokemonExplorerState.individualValues;
if (pokemonExplorerState.leaguePokemon !== null) {
const pokemonLeagueValues = pokemonExplorerState.leaguePokemon.pvp[pokemonExplorerState.league];
const statsSet = pokemonLeagueValues.some((stats) => {
if (((ivHp === null) || (stats.ivHp === ivHp)) &&
((ivAtk === null) || (stats.ivAtk === ivAtk)) &&
((ivDef === null) || (stats.ivDef === ivDef))
) {
dispatch(setIvHp(stats.ivHp));
dispatch(setIvAtk(stats.ivAtk));
dispatch(setIvDef(stats.ivDef));
dispatch(setIvLevel(stats.level));
return true;
}
return false;
});
if (!statsSet && ivHp !== null && ivAtk !== null && ivDef !== null) {
dispatch(setIvLevel(calculateMaxLevelForLeague(pokemonExplorerState.leaguePokemon.stats, ivHp, ivAtk, ivDef, pokemonExplorerState.league)));
}
}
};
};

View File

@ -0,0 +1,144 @@
import { Reducer } from 'redux';
import { League } from 'app/models/League';
import * as Actions from 'app/components/PokemonExplorer/actions';
import { IPokemonExplorerState, PokemonExplorerActionTypes } from 'app/components/PokemonExplorer/types';
export const initialState : IPokemonExplorerState = {
isLoading: false,
leaguePokemon: null,
individualValues: {
level: null,
ivHp: null,
ivAtk: null,
ivDef: null,
},
league: League.GREAT,
selectedCombatMoves: {
quickMove: null,
chargeMove1: null,
chargeMove2: null,
},
combatMoveSelectorsOpen: {
quickMove: false,
chargeMove1: false,
chargeMove2: false,
},
};
const reduceSetIsLoading = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIsLoading>
) : IPokemonExplorerState => ({
...state,
isLoading: action.payload.isLoading,
});
const reduceSetLeaguePokemon = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setLeaguePokemon>
) : IPokemonExplorerState => ({
...state,
leaguePokemon: action.payload.leaguePokemon,
});
const reduceSetIvLevel = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvLevel>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
level: action.payload.level,
}
});
const reduceSetIvHp = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvHp>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivHp: action.payload.ivHp,
}
});
const reduceSetIvAtk = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvAtk>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivAtk: action.payload.ivAtk,
}
});
const reduceSetIvDef = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvDef>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivDef: action.payload.ivDef,
}
});
const reduceSetActiveLeague = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setActiveLeague>
) : IPokemonExplorerState => ({
...state,
league: action.payload.league
});
const reduceSetSelectedCombatMoves = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setSelectedCombatMoves>
) : IPokemonExplorerState => ({
...state,
selectedCombatMoves: {
...action.payload.moves
}
});
const reduceSetCombatMoveSelectorsOpen = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setCombatMoveSelectorsOpen>
) : IPokemonExplorerState => ({
...state,
combatMoveSelectorsOpen: {
...action.payload.selectorsOpen
}
});
export const PokemonExplorerReducers : Reducer<IPokemonExplorerState> = (
state : IPokemonExplorerState = initialState,
action,
) : IPokemonExplorerState => {
switch (action.type) {
case PokemonExplorerActionTypes.SET_IS_LOADING:
return reduceSetIsLoading(state, action as ReturnType<typeof Actions.setIsLoading>);
case PokemonExplorerActionTypes.SET_LEAGUE_POKEMON:
return reduceSetLeaguePokemon(state, action as ReturnType<typeof Actions.setLeaguePokemon>);
case PokemonExplorerActionTypes.SET_IV_LEVEL:
return reduceSetIvLevel(state, action as ReturnType<typeof Actions.setIvLevel>);
case PokemonExplorerActionTypes.SET_IV_HP:
return reduceSetIvHp(state, action as ReturnType<typeof Actions.setIvHp>);
case PokemonExplorerActionTypes.SET_IV_ATK:
return reduceSetIvAtk(state, action as ReturnType<typeof Actions.setIvAtk>);
case PokemonExplorerActionTypes.SET_IV_DEF:
return reduceSetIvDef(state, action as ReturnType<typeof Actions.setIvDef>);
case PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE:
return reduceSetActiveLeague(state, action as ReturnType<typeof Actions.setActiveLeague>);
case PokemonExplorerActionTypes.SET_SELECTED_COMBAT_MOVES:
return reduceSetSelectedCombatMoves(state, action as ReturnType<typeof Actions.setSelectedCombatMoves>);
case PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN:
return reduceSetCombatMoveSelectorsOpen(state, action as ReturnType<typeof Actions.setCombatMoveSelectorsOpen>);
default:
return state;
}
};

View File

@ -0,0 +1,66 @@
import { Action } from 'redux';
import { ThunkAction, ThunkDispatch } from 'redux-thunk';
import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments';
import { ILeaguePokemon, League } from 'app/models/League';
import { IPokemonMove } from 'app/models/Pokemon';
import { PokemonService } from 'api/PokemonService';
export type IndividualValueKey = 'level' | 'hp' | 'atk' | 'def';
export interface IIndividualValues {
level : number | null;
ivHp : number | null;
ivAtk : number | null;
ivDef : number | null;
}
type MoveTypes = 'quickMove' | 'chargeMove1' | 'chargeMove2';
export type SelectedCombatMoves = {
[ key in MoveTypes ] : IPokemonMove | null;
};
export type CombatMoveSelectorsOpen = {
[ key in MoveTypes ] : boolean;
};
export interface IPokemonExplorerState {
isLoading : boolean;
leaguePokemon : ILeaguePokemon | null;
individualValues : IIndividualValues;
league : League;
selectedCombatMoves : SelectedCombatMoves;
combatMoveSelectorsOpen : CombatMoveSelectorsOpen;
}
export const PokemonExplorerActionTypes = {
SET_IS_LOADING: 'POKEMON_EXPLORER/SET_IS_LOADING',
SET_LEAGUE_POKEMON: 'POKEMON_EXPLORER/SET_LEAGUE_POKEMON',
SET_IV_LEVEL: 'POKEMON_EXPLORER/SET_IV_LEVEL',
SET_IV_HP: 'POKEMON_EXPLORER/SET_IV_HP',
SET_IV_ATK: 'POKEMON_EXPLORER/SET_IV_ATK',
SET_IV_DEF: 'POKEMON_EXPLORER/SET_IV_DEF',
SET_ACTIVE_LEAGUE: 'POKEMON_EXPLORER/SET_ACTIVE_LEAGUE',
SET_SELECTED_COMBAT_MOVES: 'POKEMON_EXPLORER/SET_SELECTED_COMBAT_MOVES',
SET_COMBAT_MOVE_SELECTORS_OPEN: 'POKEMON_EXPLORER/SET_COMBAT_MOVE_SELECTORS_OPEN',
};
export interface IPokemonExplorerStore {
pokemonExplorerState : IPokemonExplorerState;
}
export interface IPokemonExplorerServices {
pokemonService : PokemonService;
}
export interface IPokemonExplorerExtraArguments extends IProviderExtraArguments {
services : IPokemonExplorerServices;
}
export type ThunkResult<R> = ThunkAction<R, IPokemonExplorerStore, IPokemonExplorerExtraArguments, Action>;
type ThunkDispatchPokemonExplorer = ThunkDispatch<IPokemonExplorerStore, IPokemonExplorerExtraArguments, Action>;
export interface IPokemonExplorerDispatch {
dispatch : ThunkDispatchPokemonExplorer;
}

View File

@ -9,12 +9,14 @@ import { IPokemonAppExtraArguments } from 'app/types';
import { PokemonService } from 'api/PokemonService'; import { PokemonService } from 'api/PokemonService';
import { PokemonExplorerReducers } from 'app/components/PokemonExplorer/reducers';
import { PokemonSelectListReducers } from 'app/components/PokemonSelectList/reducers'; import { PokemonSelectListReducers } from 'app/components/PokemonSelectList/reducers';
import { PokemonExplorerReducers } from 'app/reducers'; import { PokemonAppReducers } from 'app/reducers';
import { ConnectedPokemonApp } from './PokemonApp'; import { ConnectedPokemonApp } from './PokemonApp';
export const appReducers = Redux.combineReducers({ export const appReducers = Redux.combineReducers({
pokemonAppState: PokemonAppReducers,
pokemonSelectListState: PokemonSelectListReducers, pokemonSelectListState: PokemonSelectListReducers,
pokemonExplorerState: PokemonExplorerReducers, pokemonExplorerState: PokemonExplorerReducers,
}); });

View File

@ -1,172 +1,67 @@
import { Reducer } from 'redux'; import { Reducer } from 'redux';
import { League } from 'app/models/League';
import * as Actions from 'app/actions'; import * as Actions from 'app/actions';
import { IPokemonExplorerState, PokemonExplorerActionTypes } from 'app/types'; import { IPokemonAppState, PokemonAppActionTypes } from 'app/types';
export const initialState : IPokemonExplorerState = { export const initialState : IPokemonAppState = {
isLoading: false, isInterruption: false,
leaguePokemon: null,
maxPossibleStats: { maxPossibleStats: {
baseStamina: 0, baseStamina: 0,
baseAttack: 0, baseAttack: 0,
baseDefense: 0, baseDefense: 0,
level: 0, level: 0,
}, },
individualValues: { attackTypeEffectiveness: new Map(),
level: null,
ivHp: null,
ivAtk: null,
ivDef: null,
},
league: League.GREAT,
combatMoves: new Map(), combatMoves: new Map(),
selectedCombatMoves: {
quickMove: null,
chargeMove1: null,
chargeMove2: null,
},
combatMoveSelectorsOpen: {
quickMove: false,
chargeMove1: false,
chargeMove2: false,
},
}; };
const reduceSetIsLoading = ( const reduceSetInterruption = (
state : IPokemonExplorerState, state : IPokemonAppState,
action : ReturnType<typeof Actions.setIsLoading> action : ReturnType<typeof Actions.setIsInterruption>
) : IPokemonExplorerState => ({ ) : IPokemonAppState => ({
...state, ...state,
isLoading: action.payload.isLoading, isInterruption: action.payload.isInterruption,
}); });
const reduceSetMaxPossibleStats = ( const reduceSetMaxPossibleStats = (
state : IPokemonExplorerState, state : IPokemonAppState,
action : ReturnType<typeof Actions.setMaxPossibleStats> action : ReturnType<typeof Actions.setMaxPossibleStats>
) : IPokemonExplorerState => ({ ) : IPokemonAppState => ({
...state, ...state,
maxPossibleStats: { maxPossibleStats: {
...action.payload.maxStats, ...action.payload.maxStats,
}, },
}); });
const reduceSetAttackTypeEffectiveness = (
state : IPokemonAppState,
action : ReturnType<typeof Actions.setAttackTypeEffectiveness>
) : IPokemonAppState => ({
...state,
attackTypeEffectiveness: action.payload.attackTypeEffectiveness,
});
const reduceSetCombatMoveStats = ( const reduceSetCombatMoveStats = (
state : IPokemonExplorerState, state : IPokemonAppState,
action : ReturnType<typeof Actions.setCombatMoveStats> action : ReturnType<typeof Actions.setCombatMoveStats>
) : IPokemonExplorerState => ({ ) : IPokemonAppState => ({
...state, ...state,
combatMoves: action.payload.combatMoves, combatMoves: action.payload.combatMoves,
}); });
const reduceSetLeaguePokemon = ( export const PokemonAppReducers : Reducer<IPokemonAppState> = (
state : IPokemonExplorerState, state : IPokemonAppState = initialState,
action : ReturnType<typeof Actions.setLeaguePokemon>
) : IPokemonExplorerState => ({
...state,
leaguePokemon: action.payload.leaguePokemon,
});
const reduceSetIvLevel = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvLevel>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
level: action.payload.level,
}
});
const reduceSetIvHp = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvHp>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivHp: action.payload.ivHp,
}
});
const reduceSetIvAtk = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvAtk>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivAtk: action.payload.ivAtk,
}
});
const reduceSetIvDef = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setIvDef>
) : IPokemonExplorerState => ({
...state,
individualValues: {
...state.individualValues,
ivDef: action.payload.ivDef,
}
});
const reduceSetActiveLeague = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setActiveLeague>
) : IPokemonExplorerState => ({
...state,
league: action.payload.league
});
const reduceSetSelectedCombatMoves = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setSelectedCombatMoves>
) : IPokemonExplorerState => ({
...state,
selectedCombatMoves: {
...action.payload.moves
}
});
const reduceSetCombatMoveSelectorsOpen = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setCombatMoveSelectorsOpen>
) : IPokemonExplorerState => ({
...state,
combatMoveSelectorsOpen: {
...action.payload.selectorsOpen
}
});
export const PokemonExplorerReducers : Reducer<IPokemonExplorerState> = (
state : IPokemonExplorerState = initialState,
action, action,
) : IPokemonExplorerState => { ) : IPokemonAppState => {
switch (action.type) { switch (action.type) {
case PokemonExplorerActionTypes.SET_IS_LOADING: case PokemonAppActionTypes.SET_IS_INTERRUPTION:
return reduceSetIsLoading(state, action as ReturnType<typeof Actions.setIsLoading>); return reduceSetInterruption(state, action as ReturnType<typeof Actions.setIsInterruption>);
case PokemonExplorerActionTypes.SET_MAX_STATS: case PokemonAppActionTypes.SET_MAX_STATS:
return reduceSetMaxPossibleStats(state, action as ReturnType<typeof Actions.setMaxPossibleStats>); return reduceSetMaxPossibleStats(state, action as ReturnType<typeof Actions.setMaxPossibleStats>);
case PokemonExplorerActionTypes.SET_COMBAT_MOVE_STATS: case PokemonAppActionTypes.SET_ATTACK_TYPE_EFFECTIVENESS:
return reduceSetAttackTypeEffectiveness(state, action as ReturnType<typeof Actions.setAttackTypeEffectiveness>);
case PokemonAppActionTypes.SET_COMBAT_MOVE_STATS:
return reduceSetCombatMoveStats(state, action as ReturnType<typeof Actions.setCombatMoveStats>); return reduceSetCombatMoveStats(state, action as ReturnType<typeof Actions.setCombatMoveStats>);
case PokemonExplorerActionTypes.SET_LEAGUE_POKEMON:
return reduceSetLeaguePokemon(state, action as ReturnType<typeof Actions.setLeaguePokemon>);
case PokemonExplorerActionTypes.SET_IV_LEVEL:
return reduceSetIvLevel(state, action as ReturnType<typeof Actions.setIvLevel>);
case PokemonExplorerActionTypes.SET_IV_HP:
return reduceSetIvHp(state, action as ReturnType<typeof Actions.setIvHp>);
case PokemonExplorerActionTypes.SET_IV_ATK:
return reduceSetIvAtk(state, action as ReturnType<typeof Actions.setIvAtk>);
case PokemonExplorerActionTypes.SET_IV_DEF:
return reduceSetIvDef(state, action as ReturnType<typeof Actions.setIvDef>);
case PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE:
return reduceSetActiveLeague(state, action as ReturnType<typeof Actions.setActiveLeague>);
case PokemonExplorerActionTypes.SET_SELECTED_COMBAT_MOVES:
return reduceSetSelectedCombatMoves(state, action as ReturnType<typeof Actions.setSelectedCombatMoves>);
case PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN:
return reduceSetCombatMoveSelectorsOpen(state, action as ReturnType<typeof Actions.setCombatMoveSelectorsOpen>);
default: default:
return state; return state;
} }

View File

@ -1,22 +1,35 @@
import POGOProtos from 'pogo-protos'; import { RouteComponentProps } from 'react-router-dom';
import { Action } from 'redux'; import { Action } from 'redux';
import { ThunkAction, ThunkDispatch } from 'redux-thunk'; import { ThunkAction, ThunkDispatch } from 'redux-thunk';
import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments'; import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments';
import { IPokemonExplorerStore } from 'app/components/PokemonExplorer/types';
import { IPokemonSelectListState } from 'app/components/PokemonSelectList/types'; import { IPokemonSelectListState } from 'app/components/PokemonSelectList/types';
import { AttackTypeEffectiveness } from 'app/models/Config';
import { ILeaguePokemon, League } from 'app/models/League'; import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
import { CombatMoveStats, IMaxStats, IPokemonMove } from 'app/models/Pokemon';
import { PokemonService } from 'api/PokemonService'; import { PokemonService } from 'api/PokemonService';
export interface IPokemonAppStore { export interface IPokemonAppState {
pokemonSelectListState : IPokemonSelectListState; isInterruption : boolean;
pokemonExplorerState : IPokemonExplorerState; maxPossibleStats : IMaxStats;
attackTypeEffectiveness : AttackTypeEffectiveness;
combatMoves : CombatMoveStats;
} }
export const PokemonAppActionTypes = {
SET_IS_INTERRUPTION: 'POKEMON_APP/SET_IS_INTERRUPTION',
SET_MAX_STATS: 'POKEMON_APP/SET_MAX_STATS',
SET_ATTACK_TYPE_EFFECTIVENESS: 'POKEMON_APP/SET_ATTACK_TYPE_EFFECTIVENESS',
SET_COMBAT_MOVE_STATS: 'POKEMON_APP/SET_COMBAT_MOVE_STATS',
};
export interface IPokemonAppStore extends IPokemonExplorerStore {
pokemonSelectListState : IPokemonSelectListState;
}
// TODO: do i need to import/extend a service from PokemonExplorer?
export interface IPokemonAppServices { export interface IPokemonAppServices {
pokemonService : PokemonService; pokemonService : PokemonService;
} }
@ -25,51 +38,13 @@ export interface IPokemonAppExtraArguments extends IProviderExtraArguments {
services : IPokemonAppServices; services : IPokemonAppServices;
} }
export interface IRouterProps {
history : RouteComponentProps['history'];
location : RouteComponentProps['location'];
}
export type ThunkResult<R> = ThunkAction<R, IPokemonAppStore, IPokemonAppExtraArguments, Action>; export type ThunkResult<R> = ThunkAction<R, IPokemonAppStore, IPokemonAppExtraArguments, Action>;
type ThunkDispatchPokemonApp = ThunkDispatch<IPokemonAppStore, IPokemonAppExtraArguments, Action>; type ThunkDispatchPokemonApp = ThunkDispatch<IPokemonAppStore, IPokemonAppExtraArguments, Action>;
export interface IPokemonAppDispatch { export interface IPokemonAppDispatch {
dispatch : ThunkDispatchPokemonApp; dispatch : ThunkDispatchPokemonApp;
} }
export type IndividualValueKey = 'level' | 'hp' | 'atk' | 'def';
export interface IIndividualValues {
level : number | null;
ivHp : number | null;
ivAtk : number | null;
ivDef : number | null;
}
type MoveTypes = 'quickMove' | 'chargeMove1' | 'chargeMove2';
export type SelectedCombatMoves = {
[ key in MoveTypes ] : IPokemonMove | null;
};
export type CombatMoveSelectorsOpen = {
[ key in MoveTypes ] : boolean;
};
export interface IPokemonExplorerState {
isLoading : boolean;
leaguePokemon : ILeaguePokemon | null;
maxPossibleStats : IMaxStats;
individualValues : IIndividualValues;
league : League;
combatMoves : CombatMoveStats;
selectedCombatMoves : SelectedCombatMoves;
combatMoveSelectorsOpen : CombatMoveSelectorsOpen;
}
export const PokemonExplorerActionTypes = {
SET_IS_LOADING: 'POKEMON_EXPLORER/SET_IS_LOADING',
SET_MAX_STATS: 'POKEMON_EXPLORER/SET_MAX_STATS',
SET_COMBAT_MOVE_STATS: 'POKEMON_EXPLORER/SET_COMBAT_MOVE_STATS',
SET_LEAGUE_POKEMON: 'POKEMON_EXPLORER/SET_LEAGUE_POKEMON',
SET_IV_LEVEL: 'POKEMON_EXPLORER/SET_IV_LEVEL',
SET_IV_HP: 'POKEMON_EXPLORER/SET_IV_HP',
SET_IV_ATK: 'POKEMON_EXPLORER/SET_IV_ATK',
SET_IV_DEF: 'POKEMON_EXPLORER/SET_IV_DEF',
SET_ACTIVE_LEAGUE: 'POKEMON_EXPLORER/SET_ACTIVE_LEAGUE',
SET_SELECTED_COMBAT_MOVES: 'POKEMON_EXPLORER/SET_SELECTED_COMBAT_MOVES',
SET_COMBAT_MOVE_SELECTORS_OPEN: 'POKEMON_EXPLORER/SET_COMBAT_MOVE_SELECTORS_OPEN',
};