2017-12-07 16:13:19 -08:00
|
|
|
import _ from 'lodash';
|
2017-07-31 20:04:01 -07:00
|
|
|
import debug from 'debug';
|
2016-06-09 16:02:51 -07:00
|
|
|
import { Observable } from 'rx';
|
2017-07-31 20:04:01 -07:00
|
|
|
import { combineEpics, ofType } from 'redux-epic';
|
|
|
|
|
|
|
|
import {
|
|
|
|
types,
|
|
|
|
|
2017-11-09 17:10:30 -08:00
|
|
|
challengeUpdated,
|
|
|
|
onRouteChallenges,
|
2017-12-07 16:13:19 -08:00
|
|
|
onRouteCurrentChallenge
|
2017-07-31 20:04:01 -07:00
|
|
|
} from './';
|
|
|
|
import { getNS as entitiesSelector } from '../../../entities';
|
2016-06-10 14:01:13 -07:00
|
|
|
import {
|
|
|
|
getNextChallenge,
|
|
|
|
getFirstChallengeOfNextBlock,
|
|
|
|
getFirstChallengeOfNextSuperBlock
|
|
|
|
} from '../utils';
|
2017-07-31 20:04:01 -07:00
|
|
|
import {
|
|
|
|
createErrorObservable,
|
|
|
|
currentChallengeSelector,
|
|
|
|
challengeSelector,
|
|
|
|
superBlocksSelector
|
|
|
|
} from '../../../redux';
|
2017-11-09 17:10:30 -08:00
|
|
|
import { langSelector } from '../../../Router/redux';
|
2017-07-31 20:04:01 -07:00
|
|
|
import { makeToast } from '../../../Toasts/redux';
|
2016-06-09 16:02:51 -07:00
|
|
|
|
2016-07-01 19:05:50 -07:00
|
|
|
const isDev = debug.enabled('fcc:*');
|
2016-06-23 20:05:30 -07:00
|
|
|
|
2017-11-09 17:10:30 -08:00
|
|
|
// When we change challenge, update the current challenge
|
|
|
|
// UI data.
|
2017-07-31 20:04:01 -07:00
|
|
|
export function challengeUpdatedEpic(actions, { getState }) {
|
2017-11-09 17:10:30 -08:00
|
|
|
return actions::ofType(types.onRouteChallenges)
|
|
|
|
// prevent subsequent onRouteChallenges to cause UI to refresh
|
|
|
|
.distinctUntilChanged(({ payload: { dashedName }}) => dashedName)
|
|
|
|
.map(() => challengeSelector(getState()))
|
|
|
|
// if the challenge isn't loaded in the current state,
|
|
|
|
// this will be an empty object
|
|
|
|
// We wait instead for the fetchChallenge.complete to complete the UI state
|
|
|
|
.filter(({ dashedName }) => !!dashedName)
|
2017-12-07 16:13:19 -08:00
|
|
|
// send the challenge to update UI and trigger main iframe to update
|
|
|
|
// use unary to prevent index from being passed to func
|
|
|
|
.map(_.unary(challengeUpdated));
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// used to reset users code on request
|
|
|
|
export function resetChallengeEpic(actions, { getState }) {
|
2017-11-09 17:10:30 -08:00
|
|
|
return actions::ofType(types.clickOnReset)
|
2017-12-07 16:13:19 -08:00
|
|
|
.map(_.flow(getState, challengeSelector, challengeUpdated));
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export function nextChallengeEpic(actions, { getState }) {
|
|
|
|
return actions::ofType(types.moveToNextChallenge)
|
2016-06-09 16:02:51 -07:00
|
|
|
.flatMap(() => {
|
2016-06-10 14:01:13 -07:00
|
|
|
let nextChallenge;
|
|
|
|
// let message = '';
|
|
|
|
// let isNewBlock = false;
|
|
|
|
// let isNewSuperBlock = false;
|
|
|
|
try {
|
|
|
|
const state = getState();
|
2017-07-31 20:04:01 -07:00
|
|
|
const superBlocks = superBlocksSelector(state);
|
|
|
|
const challenge = currentChallengeSelector(state);
|
|
|
|
const entities = entitiesSelector(state);
|
2017-11-09 17:10:30 -08:00
|
|
|
const lang = langSelector(state);
|
2016-07-01 19:05:50 -07:00
|
|
|
nextChallenge = getNextChallenge(challenge, entities, { isDev });
|
2016-06-10 14:01:13 -07:00
|
|
|
// block completed.
|
|
|
|
if (!nextChallenge) {
|
|
|
|
// isNewBlock = true;
|
2016-07-01 19:05:50 -07:00
|
|
|
nextChallenge = getFirstChallengeOfNextBlock(
|
|
|
|
challenge,
|
|
|
|
entities,
|
|
|
|
{ isDev }
|
|
|
|
);
|
2016-06-10 14:01:13 -07:00
|
|
|
}
|
|
|
|
// superBlock completed
|
|
|
|
if (!nextChallenge) {
|
|
|
|
// isNewSuperBlock = true;
|
|
|
|
nextChallenge = getFirstChallengeOfNextSuperBlock(
|
|
|
|
challenge,
|
|
|
|
entities,
|
2016-07-01 19:05:50 -07:00
|
|
|
superBlocks,
|
|
|
|
{ isDev }
|
2016-06-10 14:01:13 -07:00
|
|
|
);
|
|
|
|
}
|
2017-12-07 16:13:19 -08:00
|
|
|
/* // TODO(berks): get this to work
|
2016-06-10 14:01:13 -07:00
|
|
|
if (isNewSuperBlock || isNewBlock) {
|
|
|
|
const getName = isNewSuperBlock ?
|
|
|
|
getCurrentSuperBlockName :
|
|
|
|
getCurrentBlockName;
|
|
|
|
const blockType = isNewSuperBlock ? 'SuperBlock' : 'Block';
|
|
|
|
message =
|
|
|
|
`You've competed the ${getName(challenge, entities)} ${blockType}!`;
|
|
|
|
}
|
|
|
|
message += ' Your next challenge has arrived.';
|
|
|
|
const toast = {
|
2016-06-13 12:26:30 -07:00
|
|
|
// title: isNewSuperBlock || isNewBlock ? randomVerb() : null,
|
2016-06-10 14:01:13 -07:00
|
|
|
message
|
|
|
|
};
|
|
|
|
*/
|
2016-10-31 14:47:31 +00:00
|
|
|
if (nextChallenge.isLocked) {
|
|
|
|
return Observable.of(
|
|
|
|
makeToast({
|
|
|
|
message: 'The next challenge has not been unlocked. ' +
|
|
|
|
'Please revisit the required (*) challenges ' +
|
|
|
|
'that have not been passed yet. ',
|
|
|
|
timeout: 15000
|
|
|
|
}),
|
2017-11-09 17:10:30 -08:00
|
|
|
onRouteCurrentChallenge()
|
2016-10-31 14:47:31 +00:00
|
|
|
);
|
|
|
|
}
|
2016-06-10 14:01:13 -07:00
|
|
|
return Observable.of(
|
2017-11-09 17:10:30 -08:00
|
|
|
// normally we wouldn't need to add the lang as
|
|
|
|
// addLangToRoutesEnhancer should add langs for us, but the way
|
|
|
|
// enhancers/middlewares and RFR orders things this action will not
|
|
|
|
// see addLangToRoutesEnhancer and cause RFR to render NotFound
|
|
|
|
onRouteChallenges({ lang, ...nextChallenge }),
|
2017-07-31 20:04:01 -07:00
|
|
|
makeToast({ message: 'Your next challenge has arrived.' })
|
2016-06-10 14:01:13 -07:00
|
|
|
);
|
|
|
|
} catch (err) {
|
|
|
|
return createErrorObservable(err);
|
|
|
|
}
|
2016-06-09 16:02:51 -07:00
|
|
|
});
|
|
|
|
}
|
2017-07-31 20:04:01 -07:00
|
|
|
|
|
|
|
export default combineEpics(
|
|
|
|
challengeUpdatedEpic,
|
|
|
|
nextChallengeEpic,
|
|
|
|
resetChallengeEpic
|
|
|
|
);
|