pokemon explorer

This commit is contained in:
Jeff Colombo 2019-01-19 19:02:56 -05:00
parent 6b93730057
commit f887b3e13e
16 changed files with 202 additions and 159 deletions

View File

@ -1,13 +1,14 @@
import * as React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { appReducers } from './index'; import { appReducers } from './index';
import * as ActionsPokemonApp from './actions'; import * as ActionsPokemonExplorer from './components/PokemonExplorer/actions';
import * as ActionsPokemonSelectList from './components/PokemonSelectList/actions';
import { ThunkDispatchPokemonSelectList } from './types'; import { ThunkDispatchPokemonSelectList } from './types';
import { PokemonSelectList } from './components/PokemonSelectList'; import { PokemonExplorer } from './components/PokemonExplorer/PokemonExplorer';
import { PokemonSelectList } from './components/PokemonSelectList/PokemonSelectList';
type PokemonAppProps = ReturnType<typeof appReducers>; type PokemonAppProps = ReturnType<typeof appReducers>;
@ -21,28 +22,58 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
} }
public componentWillMount() { public componentWillMount() {
this.props.dispatch(ActionsPokemonApp.fetchPokemonList()); this.props.dispatch(ActionsPokemonSelectList.fetchPokemonList())
.then(() => this.props.dispatch(ActionsPokemonSelectList.setIsLoading(false)));
} }
public render() { public render() {
const { pokemonSelectListState } = this.props; const {
activePokemonIndex,
pokemonList,
} = this.props.pokemonSelectListState;
const {
leaguePokemon,
} = this.props.pokemonExplorerState;
return ( return (
<div>
<PokemonSelectList <PokemonSelectList
activePokemonIndex={ pokemonSelectListState.activePokemonIndex } isLoading={ this.props.pokemonSelectListState.isLoading }
pokemonList={ pokemonSelectListState.pokemonList } activePokemonIndex={ activePokemonIndex }
pokemonList={ pokemonList }
onActivatePokemon={ this.onActivatePokemon } onActivatePokemon={ this.onActivatePokemon }
/> />
{ leaguePokemon !== null &&
<PokemonExplorer
isLoading={ this.props.pokemonExplorerState.isLoading }
leaguePokemon={ leaguePokemon }
/>
}
</div>
); );
} }
private readonly onActivatePokemon = (pokemonIndex : number) => { private readonly onActivatePokemon = (pokemonIndex : number) => {
this.props.dispatch(ActionsPokemonApp.setActivePokemonIndex(pokemonIndex)); const { dispatch, pokemonSelectListState } = this.props;
this.props.dispatch(ActionsPokemonApp.fetchPokemonLeagueStats(this.props.pokemonSelectListState.pokemonList[pokemonIndex].id)); const pokemonId = pokemonSelectListState.pokemonList[pokemonIndex].id;
dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId))
.then((leaguePokemon) => {
dispatch(ActionsPokemonSelectList.setActivePokemonIndex(pokemonIndex));
dispatch(ActionsPokemonExplorer.setLeaguePokemon(leaguePokemon));
})
.catch((error) => {
// tslint:disable-next-line:no-console
console.error(error);
dispatch(ActionsPokemonExplorer.setLeaguePokemon(null));
})
.then(() => dispatch(ActionsPokemonExplorer.setIsLoading(false)));
} }
} }
const mapStateToProps = (state : ReturnType<typeof appReducers>) : PokemonAppProps => { const mapStateToProps = (state : ReturnType<typeof appReducers>) : PokemonAppProps => {
return { return {
pokemonExplorerState: state.pokemonExplorerState,
pokemonSelectListState: state.pokemonSelectListState, pokemonSelectListState: state.pokemonSelectListState,
}; };
}; };

View File

@ -1,31 +0,0 @@
"use strict";
exports.__esModule = true;
var typesafe_actions_1 = require("typesafe-actions");
var types_1 = require("./types");
exports.setPokemonList = function (pokemonList) { return typesafe_actions_1.action(types_1.PokemonSelectListActionTypes.SET_POKEMON_LIST, { pokemonList: pokemonList }); };
exports.setActivePokemonIndex = function (activePokemonIndex) { return typesafe_actions_1.action(types_1.PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX, { activePokemonIndex: activePokemonIndex }); };
exports.setPokemonLeagueStats = function (pokemonId, pokemonLeagueStats) { return typesafe_actions_1.action(types_1.PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId: pokemonId, pokemonLeagueStats: pokemonLeagueStats }); };
exports.fetchPokemonList = function () {
return function (dispatch, getState, extraArguments) {
return new Promise(function (resolve, reject) {
extraArguments.services.pokemonService.getPokemonList()
.then(function (pokemonList) {
dispatch(exports.setPokemonList(pokemonList));
resolve();
});
});
};
};
exports.fetchPokemonLeagueStats = function (pokemonId) {
return function (dispatch, getState, extraArguments) {
return new Promise(function (resolve, reject) {
extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId)
.then(function (pokemonLeagueStats) {
dispatch(exports.setPokemonLeagueStats(pokemonId, pokemonLeagueStats));
resolve();
})["catch"](function () {
reject();
});
});
};
};

View File

@ -1,41 +0,0 @@
import { action } from 'typesafe-actions';
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon';
import { PokemonSelectListActionTypes, ThunkResult } from './types';
export const setPokemonList = (pokemonList : Array<IPokemon>) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST, { pokemonList });
export const setActivePokemonIndex = (activePokemonIndex : number) => action(PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX, { activePokemonIndex });
export const setPokemonLeagueStats = (pokemonId : string, pokemonLeagueStats : ILeaguePokemon) => action(PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, pokemonLeagueStats });
export const fetchPokemonList = (
) : ThunkResult<Promise<void>> => {
return (dispatch, getState, extraArguments) => {
return new Promise<void>((resolve, reject) => {
extraArguments.services.pokemonService.getPokemonList()
.then((pokemonList) => {
dispatch(setPokemonList(pokemonList));
resolve();
});
});
};
};
export const fetchPokemonLeagueStats = (
pokemonId : string
) : ThunkResult<Promise<void>> => {
return (dispatch, getState, extraArguments) => {
return new Promise<void>((resolve, reject) => {
extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId)
.then((pokemonLeagueStats) => {
dispatch(setPokemonLeagueStats(pokemonId, pokemonLeagueStats));
resolve();
})
.catch(() => {
reject();
});
});
};
};

View File

@ -0,0 +1,17 @@
import React from 'react';
import { ILeaguePokemon } from 'app/models/Pokemon';
export interface IPokemonSelectListProps {
isLoading : boolean;
leaguePokemon : ILeaguePokemon;
}
interface IState {
}
export class PokemonExplorer extends React.Component<IPokemonSelectListProps, IState> {
public render() {
return (<div>{ this.props.leaguePokemon.name }</div>);
}
}

View File

@ -0,0 +1,9 @@
import { action } from 'typesafe-actions';
import { PokemonExplorerActionTypes } from './types';
import { ILeaguePokemon } from 'app/models/Pokemon';
export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading });
export const setLeaguePokemon = (leaguePokemon : ILeaguePokemon | null) => action(PokemonExplorerActionTypes.SET_LEAGUE_POKEMON, { leaguePokemon });

View File

@ -0,0 +1,39 @@
import { Reducer } from 'redux';
import * as Actions from './actions';
import { IPokemonExplorerState, PokemonExplorerActionTypes } from './types';
export const initialState : IPokemonExplorerState = {
isLoading: false,
leaguePokemon: null,
};
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,
});
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>);
default:
return state;
}
};

View File

@ -0,0 +1,11 @@
import { ILeaguePokemon } from 'app/models/Pokemon';
export interface IPokemonExplorerState {
isLoading : boolean;
leaguePokemon : ILeaguePokemon | null;
}
export const PokemonExplorerActionTypes = {
SET_IS_LOADING: 'POKEMON_EXPLORER/SET_IS_LOADING',
SET_LEAGUE_POKEMON: 'POKEMON_EXPLORER/SET_LEAGUE_POKEMON',
};

View File

@ -7,7 +7,8 @@ import classNames from 'classnames';
import { IPokemon } from 'app/models/Pokemon'; import { IPokemon } from 'app/models/Pokemon';
export interface IPokemonSelectListProps { export interface IPokemonSelectListProps {
activePokemonIndex : number; isLoading : boolean;
activePokemonIndex : number | null;
pokemonList : Array<IPokemon>; pokemonList : Array<IPokemon>;
onActivatePokemon : (index : number) => void; onActivatePokemon : (index : number) => void;
@ -45,9 +46,12 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
this.setState({ dimensions: contentRect.bounds }); this.setState({ dimensions: contentRect.bounds });
} }
}; };
const classes = classNames({
loading: this.props.isLoading,
});
return ( return (
<div style={ { height: '400px' } }> <div className={ classes } style={ { height: '400px' } }>
<Measure <Measure
bounds={ true } bounds={ true }
onResize={ onResize } onResize={ onResize }

View File

@ -0,0 +1,32 @@
import { action } from 'typesafe-actions';
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon';
import { ThunkResult } from 'app/types';
import { PokemonSelectListActionTypes } from './types';
export const setIsLoading = (isLoading : boolean) => action(PokemonSelectListActionTypes.SET_IS_LOADING, { isLoading });
export const setPokemonList = (pokemonList : Array<IPokemon>) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST, { pokemonList });
export const setActivePokemonIndex = (activePokemonIndex : number | null) => action(PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX, { activePokemonIndex });
export const setPokemonLeagueStats = (pokemonId : string, pokemonLeagueStats : ILeaguePokemon) => action(PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, pokemonLeagueStats });
export const fetchPokemonList = (
) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => {
const pokemonList = await extraArguments.services.pokemonService.getPokemonList();
dispatch(setPokemonList(pokemonList));
};
};
export const fetchPokemonLeagueStats = (
pokemonId : string
) : ThunkResult<Promise<ILeaguePokemon>> => {
return async (dispatch, getState, extraArguments) => {
const pokemonLeagueStats = await extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId);
dispatch(setPokemonLeagueStats(pokemonId, pokemonLeagueStats));
return pokemonLeagueStats;
};
};

View File

@ -4,12 +4,21 @@ import * as Actions from './actions';
import { IPokemonSelectListState, PokemonSelectListActionTypes } from './types'; import { IPokemonSelectListState, PokemonSelectListActionTypes } from './types';
export const initialState : IPokemonSelectListState = { export const initialState : IPokemonSelectListState = {
activePokemonIndex: -1, isLoading: true,
activePokemonIndex: null,
pokemonList: [], pokemonList: [],
pokemonListFiltered: [], pokemonListFiltered: [],
pokemonLeagueStats: {} pokemonLeagueStats: {}
}; };
const reduceSetIsLoading = (
state : IPokemonSelectListState,
action : ReturnType<typeof Actions.setIsLoading>
) : IPokemonSelectListState => ({
...state,
isLoading: action.payload.isLoading,
});
const reduceSetPokemonList = ( const reduceSetPokemonList = (
state : IPokemonSelectListState, state : IPokemonSelectListState,
action : ReturnType<typeof Actions.setPokemonList> action : ReturnType<typeof Actions.setPokemonList>
@ -42,6 +51,8 @@ export const PokemonSelectListReducers : Reducer<IPokemonSelectListState> = (
action, action,
) : IPokemonSelectListState => { ) : IPokemonSelectListState => {
switch (action.type) { switch (action.type) {
case PokemonSelectListActionTypes.SET_IS_LOADING:
return reduceSetIsLoading(state, action as ReturnType<typeof Actions.setIsLoading>);
case PokemonSelectListActionTypes.SET_POKEMON_LIST: case PokemonSelectListActionTypes.SET_POKEMON_LIST:
return reduceSetPokemonList(state, action as ReturnType<typeof Actions.setPokemonList>); return reduceSetPokemonList(state, action as ReturnType<typeof Actions.setPokemonList>);
case PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX: case PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX:

View File

@ -0,0 +1,16 @@
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon';
export interface IPokemonSelectListState {
isLoading : boolean;
activePokemonIndex : number | null;
pokemonList : Array<IPokemon>;
pokemonListFiltered : Array<IPokemon>;
pokemonLeagueStats : { [id : string] : ILeaguePokemon };
}
export const PokemonSelectListActionTypes = {
SET_IS_LOADING: 'POKEMON_SELECT_LIST/SET_IS_LOADING',
SET_POKEMON_LIST: 'POKEMON_SELECT_LIST/SET_POKEMON_LIST',
SET_ACTIVE_POKEMON_INDEX: 'POKEMON_SELECT_LIST/SET_ACTIVE_POKEMON_INDEX',
SET_POKEMON_LEAGUE_STATS: 'POKEMON_SELECT_LIST/SET_POKEMON_LEAGUE_STATS',
};

View File

@ -4,21 +4,23 @@ import { Provider } from 'react-redux';
import * as Redux from 'redux'; import * as Redux from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { IPokemonSelectListExtraArguments } from 'app/types'; import { IPokemonAppExtraArguments } from 'app/types';
import { PokemonService } from 'api/PokemonService'; import { PokemonService } from 'api/PokemonService';
import { PokemonSelectListReducers } from './reducers'; import { PokemonExplorerReducers } from './components/PokemonExplorer/reducers';
import { PokemonSelectListReducers } from './components/PokemonSelectList/reducers';
import { ConnectedPokemonApp } from './PokemonApp'; import { ConnectedPokemonApp } from './PokemonApp';
import 'styles/index.scss'; import 'styles/index.scss';
export const appReducers = Redux.combineReducers({ export const appReducers = Redux.combineReducers({
pokemonSelectListState: PokemonSelectListReducers pokemonSelectListState: PokemonSelectListReducers,
pokemonExplorerState: PokemonExplorerReducers,
}); });
const extraArguments : IPokemonSelectListExtraArguments = { const extraArguments : IPokemonAppExtraArguments = {
services: { services: {
pokemonService: new PokemonService() pokemonService: new PokemonService()
} }

View File

@ -1,39 +0,0 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
var types_1 = require("./types");
exports.initialState = {
activePokemonIndex: -1,
pokemonList: [],
pokemonListFiltered: [],
pokemonLeagueStats: {}
};
var reduceSetPokemonList = function (state, action) { return (__assign({}, state, { pokemonList: action.payload.pokemonList })); };
var reduceSetActivePokemonIndex = function (state, action) { return (__assign({}, state, { activePokemonIndex: action.payload.activePokemonIndex })); };
var reduceSetPokemonLeagueStats = function (state, action) {
var _a;
return (__assign({}, state, { pokemonLeagueStats: __assign({}, state.pokemonLeagueStats, (_a = {}, _a[action.payload.pokemonId] = action.payload.pokemonStats, _a)) }));
};
exports.PokemonSelectListReducers = function (state, action) {
if (state === void 0) { state = exports.initialState; }
switch (action.type) {
case types_1.PokemonSelectListActionTypes.SET_POKEMON_LIST:
return reduceSetPokemonList(state, action);
case types_1.PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_INDEX:
return reduceSetActivePokemonIndex(state, action);
case types_1.PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS:
return reduceSetPokemonLeagueStats(state, action);
default:
return state;
}
};

View File

@ -1,7 +0,0 @@
"use strict";
exports.__esModule = true;
exports.PokemonSelectListActionTypes = {
SET_POKEMON_LIST: 'POKEMON_APP/SET_POKEMON_LIST',
SET_ACTIVE_POKEMON_INDEX: 'POKEMON_APP/SET_ACTIVE_POKEMON_INDEX',
SET_POKEMON_LEAGUE_STATS: 'POKEMON_APP/SET_POKEMON_LEAGUE_STATS'
};

View File

@ -3,34 +3,23 @@ import { ThunkAction, ThunkDispatch } from 'redux-thunk';
import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments'; import { IProviderExtraArguments } from 'common/models/IProviderExtraArguments';
import { IPokemonExplorerState } from './components/PokemonExplorer/types';
import { IPokemonSelectListState } from './components/PokemonSelectList/types';
import { PokemonService } from 'api/PokemonService'; import { PokemonService } from 'api/PokemonService';
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon'; export interface IPokemonAppStore {
export interface IPokemonSelectListState {
activePokemonIndex : number;
pokemonList : Array<IPokemon>;
pokemonListFiltered : Array<IPokemon>;
pokemonLeagueStats : { [id : string] : ILeaguePokemon };
}
export interface IPokemonSelectListStore {
pokemonSelectListState : IPokemonSelectListState; pokemonSelectListState : IPokemonSelectListState;
pokemonExplorerState : IPokemonExplorerState;
} }
export interface IPokemonSelectListServices { export interface IPokemonAppServices {
pokemonService : PokemonService; pokemonService : PokemonService;
} }
export interface IPokemonSelectListExtraArguments extends IProviderExtraArguments { export interface IPokemonAppExtraArguments extends IProviderExtraArguments {
services : IPokemonSelectListServices; services : IPokemonAppServices;
} }
export type ThunkResult<R> = ThunkAction<R, IPokemonSelectListStore, IPokemonSelectListExtraArguments, Action>; export type ThunkResult<R> = ThunkAction<R, IPokemonAppStore, IPokemonAppExtraArguments, Action>;
export type ThunkDispatchPokemonSelectList = ThunkDispatch<IPokemonSelectListStore, IPokemonSelectListExtraArguments, Action>; export type ThunkDispatchPokemonSelectList = ThunkDispatch<IPokemonAppStore, IPokemonAppExtraArguments, Action>;
export const PokemonSelectListActionTypes = {
SET_POKEMON_LIST: 'POKEMON_APP/SET_POKEMON_LIST',
SET_ACTIVE_POKEMON_INDEX: 'POKEMON_APP/SET_ACTIVE_POKEMON_INDEX',
SET_POKEMON_LEAGUE_STATS: 'POKEMON_APP/SET_POKEMON_LEAGUE_STATS',
};