refactor store for router navigation
This commit is contained in:
parent
c6fccae553
commit
ea3d340dc2
2
dist/global-bundle.js
vendored
2
dist/global-bundle.js
vendored
File diff suppressed because one or more lines are too long
14
dist/main-bundle.js
vendored
14
dist/main-bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -4,8 +4,6 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { League } from 'app/models/League';
|
|
||||||
|
|
||||||
import { appReducers } from 'app/index';
|
import { appReducers } from 'app/index';
|
||||||
|
|
||||||
import * as ActionsPokemonApp from 'app/actions';
|
import * as ActionsPokemonApp from 'app/actions';
|
||||||
@ -13,6 +11,8 @@ 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 { IPokemonAppDispatch, IRouterProps } from 'app/types';
|
import { IPokemonAppDispatch, IRouterProps } from 'app/types';
|
||||||
|
|
||||||
|
import { convertFormParamToPokemonForm, convertIdParamToPokemonId } from 'app/utils/navigation';
|
||||||
|
|
||||||
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 { ConnectedPokemonExplorer } from 'app/components/PokemonExplorer/PokemonExplorer';
|
import { ConnectedPokemonExplorer } from 'app/components/PokemonExplorer/PokemonExplorer';
|
||||||
@ -53,25 +53,6 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
|
|||||||
dispatch(ActionsPokemonSelectList.fetchPokemonList())
|
dispatch(ActionsPokemonSelectList.fetchPokemonList())
|
||||||
]);
|
]);
|
||||||
dispatch(ActionsPokemonSelectList.setIsLoading(false));
|
dispatch(ActionsPokemonSelectList.setIsLoading(false));
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
form,
|
|
||||||
league,
|
|
||||||
} = this.props.match.params;
|
|
||||||
|
|
||||||
const pokemonId = id ? parseInt(id, 10) : null;
|
|
||||||
const pokemonForm = form ? parseInt(form, 10) : null;
|
|
||||||
if (pokemonId !== null && typeof PVPogoProtos.PokemonId[pokemonId] !== 'undefined' &&
|
|
||||||
pokemonForm !== null && typeof PVPogoProtos.PokemonForm[pokemonForm] !== 'undefined'
|
|
||||||
) {
|
|
||||||
this.handleActivatePokemon(pokemonId, pokemonForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeLeague = league ? parseInt(league, 10) : null;
|
|
||||||
if (activeLeague !== null && typeof League[activeLeague] !== 'undefined') {
|
|
||||||
this.handleChangeLeague(activeLeague);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@ -81,15 +62,19 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
|
|||||||
combatMoves,
|
combatMoves,
|
||||||
} = this.props.pokemonAppState;
|
} = this.props.pokemonAppState;
|
||||||
const {
|
const {
|
||||||
activePokemonId,
|
|
||||||
activePokemonForm,
|
|
||||||
pokemonList,
|
pokemonList,
|
||||||
pokemonListFiltered,
|
pokemonListFiltered,
|
||||||
filterTerm,
|
filterTerm,
|
||||||
} = this.props.pokemonSelectListState;
|
} = this.props.pokemonSelectListState;
|
||||||
|
const {
|
||||||
|
leaguePokemon,
|
||||||
|
} = this.props.pokemonExplorerState;
|
||||||
const {
|
const {
|
||||||
activeNavigation,
|
activeNavigation,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const matchParams = this.props.match.params;
|
||||||
|
const activePokemonId = convertIdParamToPokemonId(matchParams.id);
|
||||||
|
const activePokemonForm = convertFormParamToPokemonForm(matchParams.form);
|
||||||
|
|
||||||
const wrapperCss = classNames(
|
const wrapperCss = classNames(
|
||||||
styles.wrapper,
|
styles.wrapper,
|
||||||
@ -117,6 +102,10 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
|
|||||||
// }
|
// }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<div className={ wrapperCss }>
|
<div className={ wrapperCss }>
|
||||||
<Header>
|
<Header>
|
||||||
@ -133,6 +122,7 @@ 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>
|
||||||
<ConnectedPokemonExplorer
|
<ConnectedPokemonExplorer
|
||||||
|
key={ uniquePokemonId }
|
||||||
isOverlaid={ isInterruption }
|
isOverlaid={ isInterruption }
|
||||||
attackTypeEffectiveness={ attackTypeEffectiveness }
|
attackTypeEffectiveness={ attackTypeEffectiveness }
|
||||||
combatMoves={ combatMoves }
|
combatMoves={ combatMoves }
|
||||||
@ -174,42 +164,26 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps, IState> {
|
|||||||
this.handleSearchInterruption(true);
|
this.handleSearchInterruption(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly handleActivatePokemon = (pokemonId : PVPogoProtos.PokemonId, form : PVPogoProtos.PokemonForm) => {
|
private readonly handleActivatePokemon = async (pokemonId : PVPogoProtos.PokemonId, form : PVPogoProtos.PokemonForm) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId, form))
|
dispatch(ActionsPokemonExplorer.setIsLoading(true));
|
||||||
.then((leaguePokemon) => {
|
try {
|
||||||
dispatch(ActionsPokemonSelectList.setActivePokemonId(pokemonId, form));
|
const leaguePokemon = await dispatch(ActionsPokemonApp.fetchPokemonLeagueStats(pokemonId, form));
|
||||||
dispatch(ActionsPokemonExplorer.setIvLevel(null));
|
dispatch(ActionsPokemonExplorer.reset(leaguePokemon));
|
||||||
dispatch(ActionsPokemonExplorer.setIvHp(null));
|
} catch (error) {
|
||||||
dispatch(ActionsPokemonExplorer.setIvAtk(null));
|
|
||||||
dispatch(ActionsPokemonExplorer.setIvDef(null));
|
|
||||||
dispatch(ActionsPokemonExplorer.setLeaguePokemon(leaguePokemon));
|
|
||||||
dispatch(ActionsPokemonExplorer.setSelectedCombatMoves({
|
|
||||||
quickMove: null,
|
|
||||||
chargeMove1: null,
|
|
||||||
chargeMove2: null,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
// tslint:disable-next-line:no-console
|
// tslint:disable-next-line:no-console
|
||||||
console.error(error);
|
console.error(error);
|
||||||
dispatch(ActionsPokemonExplorer.setLeaguePokemon(null));
|
dispatch(ActionsPokemonExplorer.setLeaguePokemon(null));
|
||||||
})
|
}
|
||||||
.then(() => {
|
dispatch(ActionsPokemonExplorer.setIsLoading(false));
|
||||||
dispatch(ActionsPokemonExplorer.setIsLoading(false));
|
this.handleSearchInterruption(false);
|
||||||
this.handleSearchInterruption(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly handleChangeFilter = (filterTerm : string) => {
|
private readonly handleChangeFilter = (filterTerm : string) => {
|
||||||
this.handleSearchInterruption(true);
|
this.handleSearchInterruption(true);
|
||||||
return this.props.dispatch(ActionsPokemonSelectList.filterPokemonList(filterTerm));
|
return this.props.dispatch(ActionsPokemonSelectList.filterPokemonList(filterTerm));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly handleChangeLeague = (league : League) => {
|
|
||||||
this.props.dispatch(ActionsPokemonExplorer.setActiveLeague(league));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state : PokemonAppProps) : PokemonAppProps => {
|
const mapStateToProps = (state : PokemonAppProps) : PokemonAppProps => {
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { action } from 'typesafe-actions';
|
import { action } from 'typesafe-actions';
|
||||||
|
|
||||||
|
import { ILeaguePokemon } from 'app/models/League';
|
||||||
|
import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
|
||||||
|
import * as PVPogoProtos from 'common/models/PVPogoProtos';
|
||||||
|
|
||||||
import { PokemonAppActionTypes, ThunkResult } from 'app/types';
|
import { PokemonAppActionTypes, ThunkResult } from 'app/types';
|
||||||
|
|
||||||
import { AttackTypeEffectiveness } from 'app/models/Config';
|
import { AttackTypeEffectiveness } from 'app/models/Config';
|
||||||
import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
|
|
||||||
|
|
||||||
export const setIsInterruption = (isInterruption : boolean) => action(PokemonAppActionTypes.SET_IS_INTERRUPTION, { isInterruption });
|
export const setIsInterruption = (isInterruption : boolean) => action(PokemonAppActionTypes.SET_IS_INTERRUPTION, { isInterruption });
|
||||||
|
|
||||||
@ -13,6 +16,12 @@ export const setAttackTypeEffectiveness = (attackTypeEffectiveness : AttackTypeE
|
|||||||
|
|
||||||
export const setCombatMoveStats = (combatMoves : CombatMoveStats) => action(PokemonAppActionTypes.SET_COMBAT_MOVE_STATS, { combatMoves });
|
export const setCombatMoveStats = (combatMoves : CombatMoveStats) => action(PokemonAppActionTypes.SET_COMBAT_MOVE_STATS, { combatMoves });
|
||||||
|
|
||||||
|
export const setPokemonLeagueStats = (
|
||||||
|
pokemonId : PVPogoProtos.PokemonId,
|
||||||
|
form : PVPogoProtos.PokemonForm,
|
||||||
|
pokemonLeagueStats : ILeaguePokemon
|
||||||
|
) => action(PokemonAppActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, form, pokemonLeagueStats });
|
||||||
|
|
||||||
export const fetchConfig = (
|
export const fetchConfig = (
|
||||||
) : ThunkResult<Promise<void>> => {
|
) : ThunkResult<Promise<void>> => {
|
||||||
return async (dispatch, getState, extraArguments) => {
|
return async (dispatch, getState, extraArguments) => {
|
||||||
@ -22,3 +31,21 @@ export const fetchConfig = (
|
|||||||
dispatch(setCombatMoveStats(config.combatMoves));
|
dispatch(setCombatMoveStats(config.combatMoves));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchPokemonLeagueStats = (
|
||||||
|
pokemonId : PVPogoProtos.PokemonId,
|
||||||
|
form : PVPogoProtos.PokemonForm
|
||||||
|
) : ThunkResult<Promise<ILeaguePokemon>> => {
|
||||||
|
return async (dispatch, getState, extraArguments) => {
|
||||||
|
const leagueStats = getState().pokemonAppState.pokemonLeagueStats;
|
||||||
|
// TODO: need better accessor so `~` isn't floating around all over the place, maybe make a class
|
||||||
|
const cachedLeaguePokemon = leagueStats[pokemonId + '~' + form];
|
||||||
|
if (typeof cachedLeaguePokemon !== 'undefined') {
|
||||||
|
return Promise.resolve(cachedLeaguePokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pokemonLeagueStats = await extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId, form);
|
||||||
|
dispatch(setPokemonLeagueStats(pokemonId, form, pokemonLeagueStats));
|
||||||
|
return pokemonLeagueStats;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export class Header extends React.Component<IHeaderProps> {
|
|||||||
to="/explorer/1/0"
|
to="/explorer/1/0"
|
||||||
// style={ style }
|
// style={ style }
|
||||||
// className={ anchorCss }
|
// className={ anchorCss }
|
||||||
onClick={ this.reload }
|
// onClick={ this.reload }
|
||||||
>
|
>
|
||||||
PVPokemon
|
PVPokemon
|
||||||
</Link>
|
</Link>
|
||||||
@ -56,6 +56,4 @@ export class Header extends React.Component<IHeaderProps> {
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly reload = () => window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,34 +29,50 @@ export const setSelectedCombatMoves = (moves : SelectedCombatMoves) => action(Po
|
|||||||
|
|
||||||
export const setCombatMoveSelectorsOpen = (selectorsOpen : CombatMoveSelectorsOpen) => action(PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN, { selectorsOpen });
|
export const setCombatMoveSelectorsOpen = (selectorsOpen : CombatMoveSelectorsOpen) => action(PokemonExplorerActionTypes.SET_COMBAT_MOVE_SELECTORS_OPEN, { selectorsOpen });
|
||||||
|
|
||||||
export const maximizeLevel = (
|
export const reset = (
|
||||||
) : ThunkResult<Promise<void>> => {
|
leaguePokemon : ILeaguePokemon | null
|
||||||
return async (dispatch, getState, extraArguments) => {
|
) : ThunkResult<Promise<void>> => {
|
||||||
const pokemonExplorerState = getState().pokemonExplorerState;
|
return async (dispatch, getState, extraArguments) => {
|
||||||
const {
|
dispatch(setIvLevel(null));
|
||||||
ivHp,
|
dispatch(setIvHp(null));
|
||||||
ivAtk,
|
dispatch(setIvAtk(null));
|
||||||
ivDef,
|
dispatch(setIvDef(null));
|
||||||
} = pokemonExplorerState.individualValues;
|
dispatch(setLeaguePokemon(leaguePokemon));
|
||||||
|
dispatch(setSelectedCombatMoves({
|
||||||
if (pokemonExplorerState.leaguePokemon !== null) {
|
quickMove: null,
|
||||||
const pokemonLeagueValues = pokemonExplorerState.leaguePokemon.pvp[pokemonExplorerState.league];
|
chargeMove1: null,
|
||||||
const statsSet = pokemonLeagueValues.some((stats) => {
|
chargeMove2: null,
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -14,10 +14,6 @@ export const setPokemonList = (pokemonList : Array<IPokemon>) => action(PokemonS
|
|||||||
|
|
||||||
export const setPokemonListFiltered = (filterTerm : string, pokemonListFiltered : Array<IPokemon>) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED, { filterTerm, pokemonListFiltered });
|
export const setPokemonListFiltered = (filterTerm : string, pokemonListFiltered : Array<IPokemon>) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED, { filterTerm, pokemonListFiltered });
|
||||||
|
|
||||||
export const setActivePokemonId = (activePokemonId : PVPogoProtos.PokemonId | null, activePokemonForm : PVPogoProtos.PokemonForm | null) => action(PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_ID, { activePokemonId, activePokemonForm });
|
|
||||||
|
|
||||||
export const setPokemonLeagueStats = (pokemonId : PVPogoProtos.PokemonId, pokemonLeagueStats : ILeaguePokemon) => action(PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, pokemonLeagueStats });
|
|
||||||
|
|
||||||
export const filterPokemonList = (
|
export const filterPokemonList = (
|
||||||
filterTerm : string
|
filterTerm : string
|
||||||
) : ThunkResult<Promise<void>> => {
|
) : ThunkResult<Promise<void>> => {
|
||||||
@ -26,6 +22,10 @@ export const filterPokemonList = (
|
|||||||
if (filterTerm !== '') {
|
if (filterTerm !== '') {
|
||||||
const pokemonList = getState().pokemonSelectListState.pokemonList;
|
const pokemonList = getState().pokemonSelectListState.pokemonList;
|
||||||
const normalizedFilterTerm = filterTerm.toLowerCase();
|
const normalizedFilterTerm = filterTerm.toLowerCase();
|
||||||
|
// TODO: memoize the filtering, move to component instance variable?
|
||||||
|
// filter = memoize(
|
||||||
|
// (list, filterText) => list.filter(item => item.text.includes(filterText))
|
||||||
|
// );
|
||||||
pokemonListFiltered = pokemonList.reduce((result : Array<IPokemon>, pokemon) => {
|
pokemonListFiltered = pokemonList.reduce((result : Array<IPokemon>, pokemon) => {
|
||||||
const pokemonName = pokemon.name.toLowerCase();
|
const pokemonName = pokemon.name.toLowerCase();
|
||||||
const pokemonDex = '' + pokemon.dex;
|
const pokemonDex = '' + pokemon.dex;
|
||||||
@ -50,14 +50,3 @@ export const fetchPokemonList = (
|
|||||||
dispatch(setPokemonList(pokemonList));
|
dispatch(setPokemonList(pokemonList));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchPokemonLeagueStats = (
|
|
||||||
pokemonId : PVPogoProtos.PokemonId,
|
|
||||||
form : PVPogoProtos.PokemonForm
|
|
||||||
) : ThunkResult<Promise<ILeaguePokemon>> => {
|
|
||||||
return async (dispatch, getState, extraArguments) => {
|
|
||||||
const pokemonLeagueStats = await extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId, form);
|
|
||||||
dispatch(setPokemonLeagueStats(pokemonId, pokemonLeagueStats));
|
|
||||||
return pokemonLeagueStats;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@ -5,12 +5,9 @@ import { IPokemonSelectListState, PokemonSelectListActionTypes } from './types';
|
|||||||
|
|
||||||
export const initialState : IPokemonSelectListState = {
|
export const initialState : IPokemonSelectListState = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
activePokemonId: null,
|
|
||||||
activePokemonForm: null,
|
|
||||||
pokemonList: [],
|
pokemonList: [],
|
||||||
pokemonListFiltered: [],
|
pokemonListFiltered: [],
|
||||||
filterTerm: '',
|
filterTerm: '',
|
||||||
pokemonLeagueStats: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reduceSetIsLoading = (
|
const reduceSetIsLoading = (
|
||||||
@ -38,26 +35,6 @@ const reduceSetPokemonListFiltered = (
|
|||||||
pokemonListFiltered: action.payload.pokemonListFiltered,
|
pokemonListFiltered: action.payload.pokemonListFiltered,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reduceSetActivePokemonId = (
|
|
||||||
state : IPokemonSelectListState,
|
|
||||||
action : ReturnType<typeof Actions.setActivePokemonId>
|
|
||||||
) : IPokemonSelectListState => ({
|
|
||||||
...state,
|
|
||||||
activePokemonId: action.payload.activePokemonId,
|
|
||||||
activePokemonForm: action.payload.activePokemonForm,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reduceSetPokemonLeagueStats = (
|
|
||||||
state : IPokemonSelectListState,
|
|
||||||
action : ReturnType<typeof Actions.setPokemonLeagueStats>
|
|
||||||
) : IPokemonSelectListState => ({
|
|
||||||
...state,
|
|
||||||
pokemonLeagueStats: {
|
|
||||||
...state.pokemonLeagueStats,
|
|
||||||
[action.payload.pokemonId] : action.payload.pokemonLeagueStats,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PokemonSelectListReducers : Reducer<IPokemonSelectListState> = (
|
export const PokemonSelectListReducers : Reducer<IPokemonSelectListState> = (
|
||||||
state : IPokemonSelectListState = initialState,
|
state : IPokemonSelectListState = initialState,
|
||||||
action,
|
action,
|
||||||
@ -69,10 +46,6 @@ export const PokemonSelectListReducers : Reducer<IPokemonSelectListState> = (
|
|||||||
return reduceSetPokemonList(state, action as ReturnType<typeof Actions.setPokemonList>);
|
return reduceSetPokemonList(state, action as ReturnType<typeof Actions.setPokemonList>);
|
||||||
case PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED:
|
case PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED:
|
||||||
return reduceSetPokemonListFiltered(state, action as ReturnType<typeof Actions.setPokemonListFiltered>);
|
return reduceSetPokemonListFiltered(state, action as ReturnType<typeof Actions.setPokemonListFiltered>);
|
||||||
case PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_ID:
|
|
||||||
return reduceSetActivePokemonId(state, action as ReturnType<typeof Actions.setActivePokemonId>);
|
|
||||||
case PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS:
|
|
||||||
return reduceSetPokemonLeagueStats(state, action as ReturnType<typeof Actions.setPokemonLeagueStats>);
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,14 @@
|
|||||||
import { ILeaguePokemon } from 'app/models/League';
|
|
||||||
import { IPokemon } from 'app/models/Pokemon';
|
import { IPokemon } from 'app/models/Pokemon';
|
||||||
|
|
||||||
import * as PVPogoProtos from 'common/models/PVPogoProtos';
|
|
||||||
|
|
||||||
export interface IPokemonSelectListState {
|
export interface IPokemonSelectListState {
|
||||||
isLoading : boolean;
|
isLoading : boolean;
|
||||||
activePokemonId : PVPogoProtos.PokemonId | null;
|
|
||||||
activePokemonForm : PVPogoProtos.PokemonForm | null;
|
|
||||||
pokemonList : Array<IPokemon>;
|
pokemonList : Array<IPokemon>;
|
||||||
pokemonListFiltered : Array<IPokemon>;
|
pokemonListFiltered : Array<IPokemon>;
|
||||||
filterTerm : string;
|
filterTerm : string;
|
||||||
pokemonLeagueStats : { [id in keyof typeof PVPogoProtos.PokemonId]? : ILeaguePokemon };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PokemonSelectListActionTypes = {
|
export const PokemonSelectListActionTypes = {
|
||||||
SET_IS_LOADING: 'POKEMON_SELECT_LIST/SET_IS_LOADING',
|
SET_IS_LOADING: 'POKEMON_SELECT_LIST/SET_IS_LOADING',
|
||||||
SET_POKEMON_LIST: 'POKEMON_SELECT_LIST/SET_POKEMON_LIST',
|
SET_POKEMON_LIST: 'POKEMON_SELECT_LIST/SET_POKEMON_LIST',
|
||||||
SET_POKEMON_LIST_FILTERED: 'POKEMON_SELECT_LIST/SET_POKEMON_LIST_FILTERED',
|
SET_POKEMON_LIST_FILTERED: 'POKEMON_SELECT_LIST/SET_POKEMON_LIST_FILTERED',
|
||||||
SET_ACTIVE_POKEMON_ID: 'POKEMON_SELECT_LIST/SET_ACTIVE_POKEMON_ID',
|
|
||||||
SET_POKEMON_LEAGUE_STATS: 'POKEMON_SELECT_LIST/SET_POKEMON_LEAGUE_STATS',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-d
|
|||||||
import * as Redux from 'redux';
|
import * as Redux from 'redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
import { IPokemonAppExtraArguments } from 'app/types';
|
import { routePokemonApp } from 'app/router';
|
||||||
|
import { IPokemonAppExtraArguments, IRouterProps } from 'app/types';
|
||||||
|
|
||||||
import { PokemonService } from 'api/PokemonService';
|
import { PokemonService } from 'api/PokemonService';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ import { PokemonExplorerReducers } from 'app/components/PokemonExplorer/reducers
|
|||||||
import { PokemonSelectListReducers } from 'app/components/PokemonSelectList/reducers';
|
import { PokemonSelectListReducers } from 'app/components/PokemonSelectList/reducers';
|
||||||
import { PokemonAppReducers } from 'app/reducers';
|
import { PokemonAppReducers } from 'app/reducers';
|
||||||
|
|
||||||
import { ConnectedPokemonApp } from './PokemonApp';
|
import { ConnectedPokemonApp } from 'app/PokemonApp';
|
||||||
|
|
||||||
export const appReducers = Redux.combineReducers({
|
export const appReducers = Redux.combineReducers({
|
||||||
pokemonAppState: PokemonAppReducers,
|
pokemonAppState: PokemonAppReducers,
|
||||||
@ -35,11 +36,22 @@ const store = Redux.createStore(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderRoutePokemonApp = (props : IRouterProps) => {
|
||||||
|
routePokemonApp(props, store.dispatch);
|
||||||
|
return (
|
||||||
|
<ConnectedPokemonApp
|
||||||
|
history={ props.history }
|
||||||
|
location={ props.location }
|
||||||
|
match={ props.match }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={ store }>
|
<Provider store={ store }>
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/explorer/:id/:form" component={ ConnectedPokemonApp } />
|
<Route path="/explorer/:id/:form" render={ renderRoutePokemonApp } />
|
||||||
|
|
||||||
<Redirect from="/" to="/explorer/1/0" />
|
<Redirect from="/" to="/explorer/1/0" />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@ -11,8 +11,9 @@ export const initialState : IPokemonAppState = {
|
|||||||
baseDefense: 0,
|
baseDefense: 0,
|
||||||
level: 0,
|
level: 0,
|
||||||
},
|
},
|
||||||
attackTypeEffectiveness: new Map(),
|
attackTypeEffectiveness: new Map(), // TODO: READONLY after init
|
||||||
combatMoves: new Map(),
|
combatMoves: new Map(), // TODO: READONLY after init
|
||||||
|
pokemonLeagueStats: {}, // TODO: this should be a map, but only if it makes sense with action reducers
|
||||||
};
|
};
|
||||||
|
|
||||||
const reduceSetInterruption = (
|
const reduceSetInterruption = (
|
||||||
@ -49,6 +50,17 @@ const reduceSetCombatMoveStats = (
|
|||||||
combatMoves: action.payload.combatMoves,
|
combatMoves: action.payload.combatMoves,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const reduceSetPokemonLeagueStats = (
|
||||||
|
state : IPokemonAppState,
|
||||||
|
action : ReturnType<typeof Actions.setPokemonLeagueStats>
|
||||||
|
) : IPokemonAppState => ({
|
||||||
|
...state,
|
||||||
|
pokemonLeagueStats: {
|
||||||
|
...state.pokemonLeagueStats,
|
||||||
|
[action.payload.pokemonId + '~' + action.payload.form] : action.payload.pokemonLeagueStats,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const PokemonAppReducers : Reducer<IPokemonAppState> = (
|
export const PokemonAppReducers : Reducer<IPokemonAppState> = (
|
||||||
state : IPokemonAppState = initialState,
|
state : IPokemonAppState = initialState,
|
||||||
action,
|
action,
|
||||||
@ -62,6 +74,8 @@ export const PokemonAppReducers : Reducer<IPokemonAppState> = (
|
|||||||
return reduceSetAttackTypeEffectiveness(state, action as ReturnType<typeof Actions.setAttackTypeEffectiveness>);
|
return reduceSetAttackTypeEffectiveness(state, action as ReturnType<typeof Actions.setAttackTypeEffectiveness>);
|
||||||
case PokemonAppActionTypes.SET_COMBAT_MOVE_STATS:
|
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 PokemonAppActionTypes.SET_POKEMON_LEAGUE_STATS:
|
||||||
|
return reduceSetPokemonLeagueStats(state, action as ReturnType<typeof Actions.setPokemonLeagueStats>);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/ts/app/router.ts
Normal file
34
src/ts/app/router.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { IPokemonAppDispatch, IRouterProps } from 'app/types';
|
||||||
|
|
||||||
|
import * as ActionsPokemonApp from 'app/actions';
|
||||||
|
import * as ActionsPokemonExplorer from 'app/components/PokemonExplorer/actions';
|
||||||
|
|
||||||
|
import { convertFormParamToPokemonForm, convertIdParamToPokemonId, convertLeagueParamToLeague } from 'app/utils/navigation';
|
||||||
|
|
||||||
|
export const routePokemonApp = async (props : IRouterProps, dispatch : IPokemonAppDispatch['dispatch']) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
form,
|
||||||
|
league,
|
||||||
|
} = props.match.params;
|
||||||
|
|
||||||
|
const pokemonId = convertIdParamToPokemonId(id);
|
||||||
|
const pokemonForm = convertFormParamToPokemonForm(form);
|
||||||
|
if (pokemonId !== null && pokemonForm !== null) {
|
||||||
|
dispatch(ActionsPokemonExplorer.setIsLoading(true));
|
||||||
|
try {
|
||||||
|
const leaguePokemon = await dispatch(ActionsPokemonApp.fetchPokemonLeagueStats(pokemonId, pokemonForm));
|
||||||
|
dispatch(ActionsPokemonExplorer.reset(leaguePokemon));
|
||||||
|
} catch (error) {
|
||||||
|
// tslint:disable-next-line:no-console
|
||||||
|
console.error(error);
|
||||||
|
dispatch(ActionsPokemonExplorer.setLeaguePokemon(null));
|
||||||
|
}
|
||||||
|
dispatch(ActionsPokemonExplorer.setIsLoading(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeLeague = convertLeagueParamToLeague(league);
|
||||||
|
if (activeLeague !== null) {
|
||||||
|
dispatch(ActionsPokemonExplorer.setActiveLeague(activeLeague));
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,12 +2,13 @@ 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 { AttackTypeEffectiveness } from 'app/models/Config';
|
||||||
|
import { ILeaguePokemon } from 'app/models/League';
|
||||||
|
import { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
|
||||||
import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments';
|
import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments';
|
||||||
|
|
||||||
import { IPokemonExplorerStore } from 'app/components/PokemonExplorer/types';
|
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 { CombatMoveStats, IMaxStats } from 'app/models/Pokemon';
|
|
||||||
|
|
||||||
import { PokemonService } from 'api/PokemonService';
|
import { PokemonService } from 'api/PokemonService';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ export interface IPokemonAppState {
|
|||||||
maxPossibleStats : IMaxStats;
|
maxPossibleStats : IMaxStats;
|
||||||
attackTypeEffectiveness : AttackTypeEffectiveness;
|
attackTypeEffectiveness : AttackTypeEffectiveness;
|
||||||
combatMoves : CombatMoveStats;
|
combatMoves : CombatMoveStats;
|
||||||
|
pokemonLeagueStats : { [key : string] : ILeaguePokemon }; // TODO: map?
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PokemonAppActionTypes = {
|
export const PokemonAppActionTypes = {
|
||||||
@ -23,9 +25,11 @@ export const PokemonAppActionTypes = {
|
|||||||
SET_MAX_STATS: 'POKEMON_APP/SET_MAX_STATS',
|
SET_MAX_STATS: 'POKEMON_APP/SET_MAX_STATS',
|
||||||
SET_ATTACK_TYPE_EFFECTIVENESS: 'POKEMON_APP/SET_ATTACK_TYPE_EFFECTIVENESS',
|
SET_ATTACK_TYPE_EFFECTIVENESS: 'POKEMON_APP/SET_ATTACK_TYPE_EFFECTIVENESS',
|
||||||
SET_COMBAT_MOVE_STATS: 'POKEMON_APP/SET_COMBAT_MOVE_STATS',
|
SET_COMBAT_MOVE_STATS: 'POKEMON_APP/SET_COMBAT_MOVE_STATS',
|
||||||
|
SET_POKEMON_LEAGUE_STATS: 'POKEMON_SELECT_LIST/SET_POKEMON_LEAGUE_STATS',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IPokemonAppStore extends IPokemonExplorerStore {
|
export interface IPokemonAppStore extends IPokemonExplorerStore {
|
||||||
|
pokemonAppState : IPokemonAppState;
|
||||||
pokemonSelectListState : IPokemonSelectListState;
|
pokemonSelectListState : IPokemonSelectListState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { League } from 'app/models/League';
|
||||||
|
import * as PVPogoProtos from 'common/models/PVPogoProtos';
|
||||||
|
|
||||||
export const getCurrentQueryString = (location : RouteComponentProps['location'] | Window['location']) => {
|
export const getCurrentQueryString = (location : RouteComponentProps['location'] | Window['location']) => {
|
||||||
const search = new URLSearchParams(location.search);
|
const search = new URLSearchParams(location.search);
|
||||||
return '?' + search.toString();
|
return '?' + search.toString();
|
||||||
@ -17,3 +20,30 @@ export const appendQueryString = (location : RouteComponentProps['location'] | W
|
|||||||
});
|
});
|
||||||
return '?' + search.toString();
|
return '?' + search.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertIdParamToPokemonId = (id : string) : PVPogoProtos.PokemonId | null => {
|
||||||
|
const pokemonId = id ? parseInt(id, 10) : null;
|
||||||
|
if (pokemonId !== null && typeof PVPogoProtos.PokemonId[pokemonId] !== 'undefined') {
|
||||||
|
return pokemonId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertFormParamToPokemonForm = (form : string) : PVPogoProtos.PokemonForm | null => {
|
||||||
|
const pokemonForm = form ? parseInt(form, 10) : null;
|
||||||
|
if (pokemonForm !== null && typeof PVPogoProtos.PokemonForm[pokemonForm] !== 'undefined') {
|
||||||
|
return pokemonForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertLeagueParamToLeague = (leagueParam : string) : League | null => {
|
||||||
|
const league = leagueParam ? parseInt(leagueParam, 10) : null;
|
||||||
|
if (league !== null && typeof League[league] !== 'undefined') {
|
||||||
|
return league;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user