add loading state

This commit is contained in:
Jeff Colombo 2019-03-30 14:01:21 -07:00
parent e274f32486
commit 498c7d3e32
8 changed files with 191 additions and 68 deletions

2
dist/app.css vendored
View File

@ -4,6 +4,8 @@
.PokemonApp__wrapper__3ZEoC{display:flex;flex-flow:column nowrap;align-items:stretch}.PokemonApp__wrapper__3ZEoC .PokemonApp__body__23cv_{background-color:#fff;display:flex;flex-flow:row nowrap;align-items:stretch;position:relative;padding-bottom:60px}.PokemonApp__wrapper__3ZEoC.PokemonApp__overlaid__3B_Ol{background-color:rgba(0,0,0,0.7)}.PokemonApp__wrapper__3ZEoC.PokemonApp__overlaid__3B_Ol .PokemonApp__highlight__1zywH{z-index:1}.PokemonApp__header__2s_s2{display:flex;flex:0 0 auto;justify-content:space-between;align-items:center;padding:0.5rem 1rem;position:sticky;top:0}.PokemonApp__header__2s_s2 a{display:inline-block;display:flex}.PokemonApp__footer__3q19Q{height:60px;position:fixed;right:0;bottom:0;left:0}.PokemonApp__header__2s_s2,.PokemonApp__footer__3q19Q{margin:0.1em;z-index:10}.PokemonApp__navigationButton__2BuiE{}.PokemonApp__navigationButton__2BuiE.PokemonApp__hamburgerIcon__1ujIT{font-size:2rem;margin-top:-6px}.PokemonApp__overlay__2vc-r{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#000;opacity:.7}.PokemonApp__overlay__2vc-r.PokemonApp__complete__h3L_s{z-index:1}.PokemonApp__navigationWrapper__3oyCI{display:flex;margin:0 auto}.PokemonApp__displayWrapper__2PiN5{margin:0 auto}.PokemonApp__displayWrapper__2PiN5>*{width:450px;margin:0 1em}@media only screen and (max-width: 767px){.PokemonApp__displayWrapper__2PiN5>*{width:auto;margin:0 0.1em}}.PokemonApp__container__MsUHy{display:flex;justify-content:space-evenly}.PokemonApp__container__MsUHy .PokemonApp__leftColumn__3Lv_L,.PokemonApp__container__MsUHy .PokemonApp__rightColumn__1xE25{display:flex;flex-flow:column nowrap;align-items:center}.PokemonApp__container__MsUHy .PokemonApp__leftColumn__3Lv_L{width:45%;text-align:center}.PokemonApp__container__MsUHy .PokemonApp__rightColumn__1xE25{flex-grow:1;align-items:start} .PokemonApp__wrapper__3ZEoC{display:flex;flex-flow:column nowrap;align-items:stretch}.PokemonApp__wrapper__3ZEoC .PokemonApp__body__23cv_{background-color:#fff;display:flex;flex-flow:row nowrap;align-items:stretch;position:relative;padding-bottom:60px}.PokemonApp__wrapper__3ZEoC.PokemonApp__overlaid__3B_Ol{background-color:rgba(0,0,0,0.7)}.PokemonApp__wrapper__3ZEoC.PokemonApp__overlaid__3B_Ol .PokemonApp__highlight__1zywH{z-index:1}.PokemonApp__header__2s_s2{display:flex;flex:0 0 auto;justify-content:space-between;align-items:center;padding:0.5rem 1rem;position:sticky;top:0}.PokemonApp__header__2s_s2 a{display:inline-block;display:flex}.PokemonApp__footer__3q19Q{height:60px;position:fixed;right:0;bottom:0;left:0}.PokemonApp__header__2s_s2,.PokemonApp__footer__3q19Q{margin:0.1em;z-index:10}.PokemonApp__navigationButton__2BuiE{}.PokemonApp__navigationButton__2BuiE.PokemonApp__hamburgerIcon__1ujIT{font-size:2rem;margin-top:-6px}.PokemonApp__overlay__2vc-r{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#000;opacity:.7}.PokemonApp__overlay__2vc-r.PokemonApp__complete__h3L_s{z-index:1}.PokemonApp__navigationWrapper__3oyCI{display:flex;margin:0 auto}.PokemonApp__displayWrapper__2PiN5{margin:0 auto}.PokemonApp__displayWrapper__2PiN5>*{width:450px;margin:0 1em}@media only screen and (max-width: 767px){.PokemonApp__displayWrapper__2PiN5>*{width:auto;margin:0 0.1em}}.PokemonApp__container__MsUHy{display:flex;justify-content:space-evenly}.PokemonApp__container__MsUHy .PokemonApp__leftColumn__3Lv_L,.PokemonApp__container__MsUHy .PokemonApp__rightColumn__1xE25{display:flex;flex-flow:column nowrap;align-items:center}.PokemonApp__container__MsUHy .PokemonApp__leftColumn__3Lv_L{width:45%;text-align:center}.PokemonApp__container__MsUHy .PokemonApp__rightColumn__1xE25{flex-grow:1;align-items:start}
.ExplorerLoading__wrapper__MGSlF{display:flex;flex-wrap:wrap;width:480px;margin:0 auto}@media only screen and (max-width: 767px){.ExplorerLoading__wrapper__MGSlF{flex-direction:column}}.ExplorerLoading__wrapper__MGSlF .icon{animation:ExplorerLoading__bounce__F9WGl 0.6s both linear infinite}@media only screen and (max-width: 767px){.ExplorerLoading__wrapper__MGSlF .icon{margin:0 auto}}@keyframes ExplorerLoading__bounce__F9WGl{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}.ExplorerLoading__wrapper__MGSlF .icon:nth-child(2){animation-delay:0.2s}.ExplorerLoading__wrapper__MGSlF .icon:nth-child(3){animation-delay:0.4s}.ExplorerLoading__loading__kpcaU{width:100%;text-align:center;margin-top:0.5em}.ExplorerLoading__loading__kpcaU::after{position:absolute;overflow:hidden;display:inline-block;vertical-align:bottom;animation:ExplorerLoading__ellipsis__3kLb6 steps(4, end) 900ms infinite;content:"\2026";width:0}@keyframes ExplorerLoading__ellipsis__3kLb6{to{width:1.25em}}
.nes-field.is-inline .IvForm__ivInput__xR5IU{width:4.25em;padding-left:0.45em;padding-right:0.25em}.nes-field.is-inline .IvForm__ivInput__xR5IU.IvForm__levelInput__1n6We{width:6.5em}.nes-field.is-inline.IvForm__fieldRow__3HcBN{font-size:0.9em;justify-content:space-between;align-items:baseline}.nes-field.is-inline.IvForm__fieldRow__3HcBN label{flex-grow:0;margin-left:1em}.nes-field.is-inline.IvForm__fieldRow__3HcBN label:first-child{margin-left:0} .nes-field.is-inline .IvForm__ivInput__xR5IU{width:4.25em;padding-left:0.45em;padding-right:0.25em}.nes-field.is-inline .IvForm__ivInput__xR5IU.IvForm__levelInput__1n6We{width:6.5em}.nes-field.is-inline.IvForm__fieldRow__3HcBN{font-size:0.9em;justify-content:space-between;align-items:baseline}.nes-field.is-inline.IvForm__fieldRow__3HcBN label{flex-grow:0;margin-left:1em}.nes-field.is-inline.IvForm__fieldRow__3HcBN label:first-child{margin-left:0}
.LeagueSelector__wrapper__fxmRz{font-size:0.8em;display:flex;justify-content:space-around;margin:0 0 0.5rem 0}.LeagueSelector__leagueRadioLabel__3aPV9{display:flex;align-items:center}.LeagueSelector__leagueRadio__3hY7B{display:none} .LeagueSelector__wrapper__fxmRz{font-size:0.8em;display:flex;justify-content:space-around;margin:0 0 0.5rem 0}.LeagueSelector__leagueRadioLabel__3aPV9{display:flex;align-items:center}.LeagueSelector__leagueRadio__3hY7B{display:none}

File diff suppressed because one or more lines are too long

14
dist/main-bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
import React from 'react';
import classNames from 'classnames';
import { formatDexNumber } from 'app/utils/formatter';
import * as styles from 'app/components/PokemonExplorer/styles/ExplorerLoading.scss';
export interface IExplorerLoadingProps {}
export class ExplorerLoading extends React.Component<IExplorerLoadingProps> {
public render() {
const pokemonGroups = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[172, 25, 26],
[144, 145, 146],
];
const pokemonIconCss = classNames(
'icon',
'pixel',
'sprite',
);
const groupIndex = Math.floor(Math.random() * pokemonGroups.length);
const pokemonIcons = pokemonGroups[groupIndex].map((dex) => {
const pokemonCss = classNames(
pokemonIconCss,
`pokemon-${ formatDexNumber(dex) }`,
);
return <div key={ dex } className={ pokemonCss } />;
});
return (
<div className={ styles.wrapper }>
{ pokemonIcons }
<h2 className={ styles.loading }>Loading</h2>
</div>
);
}
}

View File

@ -21,6 +21,7 @@ import {
} from 'app/components/PokemonExplorer/types'; } from 'app/components/PokemonExplorer/types';
import { IRouterProps, Navigation as NavigationType } from 'app/types'; import { IRouterProps, Navigation as NavigationType } from 'app/types';
import { ExplorerLoading } from 'app/components/PokemonExplorer/ExplorerLoading';
import { LeagueIvExplorer } from 'app/components/PokemonExplorer/LeagueIvExplorer'; import { LeagueIvExplorer } from 'app/components/PokemonExplorer/LeagueIvExplorer';
import { MovesExplorer } from 'app/components/PokemonExplorer/MovesExplorer'; import { MovesExplorer } from 'app/components/PokemonExplorer/MovesExplorer';
import { Navigation } from 'app/components/PokemonExplorer/Navigation'; import { Navigation } from 'app/components/PokemonExplorer/Navigation';
@ -55,6 +56,7 @@ class PokemonExplorer extends React.Component<IConnectedPokemonExplorerProps> {
attackTypeEffectiveness, attackTypeEffectiveness,
} = this.props; } = this.props;
const { const {
isLoading,
league, league,
individualValues, individualValues,
widgets, widgets,
@ -88,67 +90,74 @@ class PokemonExplorer extends React.Component<IConnectedPokemonExplorerProps> {
return ( return (
<div className={ styles.body }> <div className={ styles.body }>
<div className={ navWrapperCss }> { (leaguePokemon === null || isLoading) &&
<div className={ displayWrapperCss }> <ExplorerLoading />
{ leaguePokemon !== null && }
<PokemonDisplay { leaguePokemon !== null && !isLoading &&
leaguePokemon={ leaguePokemon } <React.Fragment>
isHighlighted={ isOverlayShown } <div className={ navWrapperCss }>
/> <div className={ displayWrapperCss }>
} { leaguePokemon !== null &&
{ widgets.types && leaguePokemon !== null && <PokemonDisplay
<div className="nes-container with-title"> leaguePokemon={ leaguePokemon }
<h3 className="title">Type Effectivess</h3> isHighlighted={ isOverlayShown }
<TypeEffectiveDisplay />
mode={ EffectivenessMode.DEFENSE } }
effectiveness={ leaguePokemon.effectiveness } { widgets.types && leaguePokemon !== null &&
coverage={ moveTypeStrengths } <div className="nes-container with-title">
/> <h3 className="title">Type Effectivess</h3>
<TypeEffectiveDisplay
mode={ EffectivenessMode.DEFENSE }
effectiveness={ leaguePokemon.effectiveness }
coverage={ moveTypeStrengths }
/>
</div>
}
{ widgets.moves && leaguePokemon !== null &&
<MovesExplorer
movesById={ combatMoves }
quickMoves={ leaguePokemon.moves.quick }
chargeMoves={ leaguePokemon.moves.cinematic }
selectedMoves={ selectedCombatMoves }
pokemonTypeWeaknesses={ pokemonTypeWeaknesses }
attackTypeEffectiveness={ attackTypeEffectiveness }
combatMoveSelectorsOpen={ combatMoveSelectorsOpen }
handleToggleDropdownOpen={ this.handleToggleDropdownOpen }
handleChangeSelectedMove={ this.handleChangeSelectedMove }
/>
}
{ widgets.pvp && leaguePokemon !== null &&
<LeagueIvExplorer
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
</div> </div>
<Media query={ { minWidth: MIN_TABLET_WIDTH } }>
<Navigation
isMenu={ false }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
{ isMenuOpen &&
<Media query={ { maxWidth: MAX_MOBILE_WIDTH } }>
<Navigation
isMenu={ true }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
}
</div>
{ isOverlayShown &&
<div className={ overLayCss } onClick={ this.handleOverlayClick } />
} }
{ widgets.moves && leaguePokemon !== null && </React.Fragment>
<MovesExplorer
movesById={ combatMoves }
quickMoves={ leaguePokemon.moves.quick }
chargeMoves={ leaguePokemon.moves.cinematic }
selectedMoves={ selectedCombatMoves }
pokemonTypeWeaknesses={ pokemonTypeWeaknesses }
attackTypeEffectiveness={ attackTypeEffectiveness }
combatMoveSelectorsOpen={ combatMoveSelectorsOpen }
handleToggleDropdownOpen={ this.handleToggleDropdownOpen }
handleChangeSelectedMove={ this.handleChangeSelectedMove }
/>
}
{ widgets.pvp && leaguePokemon !== null &&
<LeagueIvExplorer
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
</div>
<Media query={ { minWidth: MIN_TABLET_WIDTH } }>
<Navigation
isMenu={ false }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
{ isMenuOpen &&
<Media query={ { maxWidth: MAX_MOBILE_WIDTH } }>
<Navigation
isMenu={ true }
widgets={ widgets }
handleNavigationClick={ this.handleNavigationClick }
/>
</Media>
}
</div>
{ isOverlayShown &&
<div className={ overLayCss } onClick={ this.handleOverlayClick } />
} }
</div> </div>
); );

View File

@ -6,7 +6,7 @@ import * as Actions from 'app/components/PokemonExplorer/actions';
import { IPokemonExplorerState, PokemonExplorerActionTypes } from 'app/components/PokemonExplorer/types'; import { IPokemonExplorerState, PokemonExplorerActionTypes } from 'app/components/PokemonExplorer/types';
export const initialState : IPokemonExplorerState = { export const initialState : IPokemonExplorerState = {
isLoading: false, isLoading: true,
leaguePokemon: null, leaguePokemon: null,
widgets: { widgets: {
types: false, types: false,

View File

@ -0,0 +1,61 @@
@import '~styles/Variables.scss';
.wrapper {
display: flex;
flex-wrap: wrap;
width: 480px;
margin: 0 auto;
@media only screen and (max-width: $max-mobile-width) {
flex-direction: column;
}
:global(.icon) {
animation: bounce 0.6s both linear infinite;
@media only screen and (max-width: $max-mobile-width) {
margin: 0 auto;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
&:nth-child(2) {
animation-delay: 0.2s;
}
&:nth-child(3) {
animation-delay: 0.4s;
}
}
}
.loading {
width: 100%;
text-align: center;
margin-top: 0.5em;
&::after {
position: absolute;
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis steps(4, end) 900ms infinite;
content: "\2026"; // ascii code for the ellipsis character
width: 0;
@keyframes ellipsis {
to {
width: 1.25em;
}
}
}
}

View File

@ -0,0 +1,6 @@
// This file is automatically generated.
// Please do not change this file!
export const bounce: string;
export const ellipsis: string;
export const loading: string;
export const wrapper: string;