fix(challenge): Handle slow network connections gracefully

This commit is contained in:
Stuart Taylor
2018-03-02 12:06:58 +00:00
parent 3b74a20910
commit c17e3ce1d7
13 changed files with 105 additions and 54 deletions

View File

@ -196,13 +196,12 @@ export default composeReducers(
app.fetchChallenges.complete, app.fetchChallenges.complete,
map.fetchMapUi.complete map.fetchMapUi.complete
) )
]: (state, { payload }) => { ]: (state, { payload: { entities } }) => merge({}, state, entities),
const {entities: { block } } = payload; [app.fetchChallenges.complete]:
return { (state, { payload: { entities: { block }}}) => ({
...merge(state, payload.entities), ...state,
fullBlocks: union(state.fullBlocks, [ Object.keys(block)[0] ]) fullBlocks: union(state.fullBlocks, [ Object.keys(block)[0] ])
}; }),
},
[ [
challenges.submitChallenge.complete challenges.submitChallenge.complete
]: (state, { payload: { username, points, challengeInfo } }) => ({ ]: (state, { payload: { username, points, challengeInfo } }) => ({

View File

@ -27,20 +27,20 @@ LoaderCircle.displayName = 'LoaderCircle';
const animationProps = [ const animationProps = [
{ {
delay: '-1.5s', delay: '0.24s',
origin: '1% 1%' origin: '0% 0%'
}, },
{ {
delay: '-1s', delay: '0.95s',
origin: '1% 99%' origin: '0% 100%'
}, },
{ {
delay: '-0.5s', delay: '0.67s',
origin: '99% 1%' origin: '100% 0%'
}, },
{ {
delay: '0s', delay: '1.33s',
origin: '99% 99%' origin: '100% 100%'
} }
]; ];

View File

@ -28,13 +28,18 @@ export default `
transform: scale(0.1); transform: scale(0.1);
opacity: 0; opacity: 0;
} }
50% {
-webkit-transform: scale(0.8);
transform: scale(0.8);
opacity: 0.8;
}
70% { 70% {
opacity: 1; opacity: 1;
} }
100% { 100% {
opacity: 0.0; opacity: 0;
-webkit-transform: scale(1); -webkit-transform: scale(1.2);
transform: scale(1); transform: scale(1.2);
} }
} }
@ -55,14 +60,15 @@ export default `
} }
.innerCircle { .innerCircle {
-webkit-animation-duration: 2s; -webkit-animation-duration: 2s;
animation-duration: 2s; animation-duration: 2s;
-webkit-animation-iteration-count: infinite; -webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite; animation-iteration-count: infinite;
-webkit-animation-name: overlay-loader; -webkit-animation-name: overlay-loader;
animation-name: overlay-loader; animation-name: overlay-loader;
-webkit-animation-timing-function: ease-out; -webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out; animation-timing-function: ease-out;
opacity: 0;
} }
`; `;

View File

@ -45,7 +45,7 @@ const mapStateToProps = createSelector(
paramsSelector, paramsSelector,
fullBlocksSelector, fullBlocksSelector,
( (
{ dashedName, isTranslated }, { dashedName, isTranslated, description },
{ viewType, title }, { viewType, title },
params, params,
blocks blocks
@ -54,6 +54,7 @@ const mapStateToProps = createSelector(
challenge: dashedName, challenge: dashedName,
isTranslated, isTranslated,
params, params,
showLoading: !description || description.length === 0,
title, title,
viewType viewType
}) })
@ -71,6 +72,7 @@ const propTypes = {
dashedName: PropTypes.string, dashedName: PropTypes.string,
lang: PropTypes.string.isRequired lang: PropTypes.string.isRequired
}), }),
showLoading: PropTypes.bool,
title: PropTypes.string, title: PropTypes.string,
updateSuccessMessage: PropTypes.func.isRequired, updateSuccessMessage: PropTypes.func.isRequired,
updateTitle: PropTypes.func.isRequired, updateTitle: PropTypes.func.isRequired,
@ -113,9 +115,9 @@ export class Show extends PureComponent {
} }
render() { render() {
const { viewType } = this.props; const { viewType, showLoading } = this.props;
const View = views[viewType] || Classic; const View = views[viewType] || Classic;
return <View />; return <View showLoading={ showLoading } />;
} }
} }

View File

@ -30,13 +30,6 @@ import { descriptionRegex } from './utils';
import { challengeSelector } from '../../redux'; import { challengeSelector } from '../../redux';
import { makeToast } from '../../Toasts/redux'; import { makeToast } from '../../Toasts/redux';
const mapDispatchToProps = {
makeToast,
executeChallenge,
updateHint,
openHelpModal,
unlockUntrustedCode
};
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
challengeSelector, challengeSelector,
challengeMetaSelector, challengeMetaSelector,
@ -62,6 +55,15 @@ const mapStateToProps = createSelector(
isCodeLocked isCodeLocked
}) })
); );
const mapDispatchToProps = {
makeToast,
executeChallenge,
updateHint,
openHelpModal,
unlockUntrustedCode
};
const propTypes = { const propTypes = {
description: PropTypes.arrayOf(PropTypes.string), description: PropTypes.arrayOf(PropTypes.string),
executeChallenge: PropTypes.func, executeChallenge: PropTypes.func,
@ -95,7 +97,8 @@ export class SidePanel extends PureComponent {
this.descriptionTop = node; this.descriptionTop = node;
} }
renderDescription(description = [ 'Happy Coding!' ]) { renderDescription() {
const { description = [ 'Happy Coding!' ] } = this.props;
return description.map((line, index) => { return description.map((line, index) => {
if (descriptionRegex.test(line)) { if (descriptionRegex.test(line)) {
return ( return (
@ -118,7 +121,6 @@ export class SidePanel extends PureComponent {
render() { render() {
const { const {
title, title,
description,
tests = [], tests = [],
output, output,
hint, hint,
@ -142,7 +144,7 @@ export class SidePanel extends PureComponent {
{ title } { title }
</ChallengeTitle> </ChallengeTitle>
<ChallengeDescription> <ChallengeDescription>
{ this.renderDescription(description) } { this.renderDescription() }
</ChallengeDescription> </ChallengeDescription>
</div> </div>
<ToolPanel <ToolPanel

View File

@ -7,6 +7,7 @@ import { addNS } from 'berkeleys-redux-utils';
import ns from './ns.json'; import ns from './ns.json';
import Editor from './Editor.jsx'; import Editor from './Editor.jsx';
import { OverlayLoader } from '../../../../helperComponents';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import { showPreviewSelector, types } from '../../redux'; import { showPreviewSelector, types } from '../../redux';
import SidePanel from '../../Side-Panel.jsx'; import SidePanel from '../../Side-Panel.jsx';
@ -21,7 +22,8 @@ const createModernEditorToggleType = fileKey =>
const getFirstFileKey = _.flow(_.values, _.first, _.property('key')); const getFirstFileKey = _.flow(_.values, _.first, _.property('key'));
const propTypes = { const propTypes = {
nameToFileKey: PropTypes.object nameToFileKey: PropTypes.object,
showLoading: PropTypes.bool
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@ -83,9 +85,12 @@ const nameToComponent = {
Preview: Preview Preview: Preview
}; };
export function ShowModern({ nameToFileKey }) { export function ShowModern({ nameToFileKey, showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes <Panes
render={ name => { render={ name => {
const Comp = nameToComponent[name]; const Comp = nameToComponent[name];

View File

@ -1,13 +1,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { addNS } from 'berkeleys-redux-utils'; import { addNS } from 'berkeleys-redux-utils';
import { OverlayLoader } from '../../../../helperComponents';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import BackEnd from './Back-End.jsx'; import BackEnd from './Back-End.jsx';
import { types } from '../../redux'; import { types } from '../../redux';
import Panes from '../../../../Panes'; import Panes from '../../../../Panes';
import _Map from '../../../../Map'; import _Map from '../../../../Map';
const propTypes = {}; const propTypes = {
showLoading: PropTypes.bool
};
export const mapStateToPanes = addNS( export const mapStateToPanes = addNS(
'backend', 'backend',
@ -27,9 +31,12 @@ const renderPane = name => {
return Comp ? <Comp /> : <span>Pane { name } not found</span>; return Comp ? <Comp /> : <span>Pane { name } not found</span>;
}; };
export default function ShowBackEnd() { export default function ShowBackEnd({ showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes render={ renderPane } /> <Panes render={ renderPane } />
</ChildContainer> </ChildContainer>
); );

View File

@ -1,15 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { addNS } from 'berkeleys-redux-utils'; import { addNS } from 'berkeleys-redux-utils';
import Editor from './Editor.jsx'; import Editor from './Editor.jsx';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import { OverlayLoader } from '../../../../helperComponents';
import { types, showPreviewSelector } from '../../redux'; import { types, showPreviewSelector } from '../../redux';
import Preview from '../../Preview.jsx'; import Preview from '../../Preview.jsx';
import SidePanel from '../../Side-Panel.jsx'; import SidePanel from '../../Side-Panel.jsx';
import Panes from '../../../../Panes'; import Panes from '../../../../Panes';
import _Map from '../../../../Map'; import _Map from '../../../../Map';
const propTypes = {}; const propTypes = {
showLoading: PropTypes.bool
};
export const mapStateToPanes = addNS( export const mapStateToPanes = addNS(
'classic', 'classic',
@ -39,9 +43,12 @@ const renderPane = name => {
return Comp ? <Comp /> : <span>Pane for { name } not found</span>; return Comp ? <Comp /> : <span>Pane for { name } not found</span>;
}; };
export default function ShowClassic() { export default function ShowClassic({ showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes render={ renderPane }/> <Panes render={ renderPane }/>
</ChildContainer> </ChildContainer>
); );

View File

@ -1,14 +1,18 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { addNS } from 'berkeleys-redux-utils'; import { addNS } from 'berkeleys-redux-utils';
import ns from './ns.json'; import ns from './ns.json';
import Main from './Project.jsx'; import Main from './Project.jsx';
import { OverlayLoader } from '../../../../helperComponents';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import { types } from '../../redux'; import { types } from '../../redux';
import Panes from '../../../../Panes'; import Panes from '../../../../Panes';
import _Map from '../../../../Map'; import _Map from '../../../../Map';
const propTypes = {}; const propTypes = {
showLoading: PropTypes.bool
};
export const mapStateToPanes = addNS( export const mapStateToPanes = addNS(
ns, ns,
() => ({ () => ({
@ -27,9 +31,12 @@ const renderPane = name => {
return Comp ? <Comp /> : <span>Pane { name } not found</span>; return Comp ? <Comp /> : <span>Pane { name } not found</span>;
}; };
export default function ShowProject() { export default function ShowProject({ showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes render={ renderPane }/> <Panes render={ renderPane }/>
</ChildContainer> </ChildContainer>
); );

View File

@ -1,14 +1,18 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { addNS } from 'berkeleys-redux-utils'; import { addNS } from 'berkeleys-redux-utils';
import ns from './ns.json'; import ns from './ns.json';
import Main from './Quiz.jsx'; import Main from './Quiz.jsx';
import { OverlayLoader } from '../../../../helperComponents';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import { types } from '../../redux'; import { types } from '../../redux';
import Panes from '../../../../Panes'; import Panes from '../../../../Panes';
import _Map from '../../../../Map'; import _Map from '../../../../Map';
const propTypes = {}; const propTypes = {
showLoading: PropTypes.bool
};
export const mapStateToPanes = addNS( export const mapStateToPanes = addNS(
ns, ns,
() => ({ () => ({
@ -27,9 +31,12 @@ const renderPane = name => {
return Comp ? <Comp /> : <span>Pane { name } not found</span>; return Comp ? <Comp /> : <span>Pane { name } not found</span>;
}; };
export default function ShowQuiz() { export default function ShowQuiz({ showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes render={ renderPane }/> <Panes render={ renderPane }/>
</ChildContainer> </ChildContainer>
); );

View File

@ -1,14 +1,18 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { addNS } from 'berkeleys-redux-utils'; import { addNS } from 'berkeleys-redux-utils';
import ns from './ns.json'; import ns from './ns.json';
import Step from './Step.jsx'; import Step from './Step.jsx';
import { OverlayLoader } from '../../../../helperComponents';
import ChildContainer from '../../Child-Container.jsx'; import ChildContainer from '../../Child-Container.jsx';
import { types } from '../../redux'; import { types } from '../../redux';
import Panes from '../../../../Panes'; import Panes from '../../../../Panes';
import _Map from '../../../../Map'; import _Map from '../../../../Map';
const propTypes = {}; const propTypes = {
showLoading: PropTypes.bool
};
export const mapStateToPanes = addNS( export const mapStateToPanes = addNS(
ns, ns,
() => ({ () => ({
@ -27,9 +31,12 @@ const renderPane = name => {
return Comp ? <Comp /> : <span>Pane { name } not found</span>; return Comp ? <Comp /> : <span>Pane { name } not found</span>;
}; };
export default function ShowStep() { export default function ShowStep({ showLoading }) {
return ( return (
<ChildContainer isFullWidth={ true }> <ChildContainer isFullWidth={ true }>
{
showLoading ? <OverlayLoader /> : null
}
<Panes render={ renderPane }/> <Panes render={ renderPane }/>
</ChildContainer> </ChildContainer>
); );

View File

@ -31,7 +31,7 @@ const mapStateToProps = createSelector(
actionCompletedSelector, actionCompletedSelector,
lightBoxSelector, lightBoxSelector,
( (
{ description = [] }, { description = [['', '', 'Happy Coding!', '']] },
currentIndex, currentIndex,
previousIndex, previousIndex,
isActionCompleted, isActionCompleted,

View File

@ -58,7 +58,8 @@ export default function mapUiService(app) {
block, block,
isLocked, isLocked,
isComingSoon, isComingSoon,
isBeta isBeta,
challengeType
} = challenge; } = challenge;
map[dashedName] = { map[dashedName] = {
dashedName, dashedName,
@ -68,7 +69,8 @@ export default function mapUiService(app) {
block, block,
isLocked, isLocked,
isComingSoon, isComingSoon,
isBeta isBeta,
challengeType
}; };
return map; return map;
}, {}); }, {});