2016-01-27 11:34:44 -08:00
|
|
|
import { Observable } from 'rx';
|
2016-02-05 16:40:02 -08:00
|
|
|
import { push } from 'react-router-redux';
|
2016-01-27 11:34:44 -08:00
|
|
|
|
|
|
|
import types from './types';
|
|
|
|
import { getMouse } from './utils';
|
|
|
|
|
|
|
|
import { makeToast, updatePoints } from '../../../redux/actions';
|
|
|
|
import { hikeCompleted, goToNextHike } from './actions';
|
|
|
|
import { postJSON$ } from '../../../../utils/ajax-stream';
|
2016-02-05 16:40:02 -08:00
|
|
|
import { getCurrentHike } from './selectors';
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
function handleAnswer(getState, dispatch, next, action) {
|
|
|
|
const {
|
|
|
|
e,
|
|
|
|
answer,
|
|
|
|
userAnswer,
|
|
|
|
info,
|
|
|
|
threshold
|
|
|
|
} = action.payload;
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
const state = getState();
|
|
|
|
const { id, name, challengeType, tests } = getCurrentHike(state);
|
|
|
|
const {
|
|
|
|
app: { isSignedIn },
|
|
|
|
hikesApp: {
|
|
|
|
currentQuestion,
|
|
|
|
delta = [ 0, 0 ]
|
|
|
|
}
|
|
|
|
} = state;
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
let finalAnswer;
|
|
|
|
// drag answer, compute response
|
|
|
|
if (typeof userAnswer === 'undefined') {
|
|
|
|
const [positionX] = getMouse(e, delta);
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
// question released under threshold
|
|
|
|
if (Math.abs(positionX) < threshold) {
|
|
|
|
return next(action);
|
2016-01-27 11:34:44 -08:00
|
|
|
}
|
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
if (positionX >= threshold) {
|
|
|
|
finalAnswer = true;
|
|
|
|
}
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
if (positionX <= -threshold) {
|
|
|
|
finalAnswer = false;
|
2016-01-27 11:34:44 -08:00
|
|
|
}
|
2016-02-05 16:40:02 -08:00
|
|
|
} else {
|
|
|
|
finalAnswer = userAnswer;
|
|
|
|
}
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
// incorrect question
|
|
|
|
if (answer !== finalAnswer) {
|
|
|
|
if (info) {
|
|
|
|
dispatch(makeToast({
|
|
|
|
title: 'Hint',
|
|
|
|
message: info,
|
|
|
|
type: 'info'
|
|
|
|
}));
|
2016-01-27 11:34:44 -08:00
|
|
|
}
|
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
return Observable
|
|
|
|
.just({ type: types.endShake })
|
|
|
|
.delay(500)
|
|
|
|
.startWith({ type: types.startShake })
|
|
|
|
.doOnNext(dispatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tests[currentQuestion]) {
|
|
|
|
return Observable
|
|
|
|
.just({ type: types.goToNextQuestion })
|
|
|
|
.delay(300)
|
|
|
|
.startWith({ type: types.primeNextQuestion })
|
|
|
|
.doOnNext(dispatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
let updateUser$;
|
|
|
|
if (isSignedIn) {
|
|
|
|
const body = { id, name, challengeType };
|
|
|
|
updateUser$ = postJSON$('/completed-challenge', body)
|
|
|
|
// if post fails, will retry once
|
|
|
|
.retry(3)
|
|
|
|
.flatMap(({ alreadyCompleted, points }) => {
|
|
|
|
return Observable.of(
|
|
|
|
makeToast({
|
|
|
|
message:
|
|
|
|
'Challenge saved.' +
|
|
|
|
(alreadyCompleted ? '' : ' First time Completed!'),
|
|
|
|
title: 'Saved',
|
|
|
|
type: 'info'
|
|
|
|
}),
|
|
|
|
updatePoints(points),
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
return Observable.just({
|
|
|
|
type: 'app.error',
|
|
|
|
error
|
2016-01-27 11:34:44 -08:00
|
|
|
});
|
2016-02-05 16:40:02 -08:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
updateUser$ = Observable.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
const challengeCompleted$ = Observable.of(
|
|
|
|
goToNextHike(),
|
|
|
|
makeToast({
|
|
|
|
title: 'Congratulations!',
|
|
|
|
message: 'Hike completed.' + (isSignedIn ? ' Saving...' : ''),
|
|
|
|
type: 'success'
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
return Observable.merge(challengeCompleted$, updateUser$)
|
|
|
|
.delay(300)
|
|
|
|
.startWith(hikeCompleted(finalAnswer))
|
|
|
|
.catch(error => Observable.just({
|
|
|
|
type: 'error',
|
|
|
|
error
|
|
|
|
}))
|
|
|
|
.doOnNext(dispatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default () => ({ getState, dispatch }) => next => {
|
|
|
|
return function answerSaga(action) {
|
|
|
|
if (action.type === types.answerQuestion) {
|
|
|
|
return handleAnswer(getState, dispatch, next, action);
|
2016-01-27 11:34:44 -08:00
|
|
|
}
|
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
// let goToNextQuestion hit reducers first
|
|
|
|
const result = next(action);
|
|
|
|
if (action.type === types.goToNextHike) {
|
|
|
|
const { hikesApp: { currentHike } } = getState();
|
|
|
|
// if no next hike currentHike will equal '' which is falsy
|
|
|
|
if (currentHike) {
|
|
|
|
dispatch(push(`/videos/${currentHike}`));
|
|
|
|
} else {
|
|
|
|
dispatch(push('/map'));
|
|
|
|
}
|
|
|
|
}
|
2016-01-27 11:34:44 -08:00
|
|
|
|
2016-02-05 16:40:02 -08:00
|
|
|
return result;
|
2016-01-27 11:34:44 -08:00
|
|
|
};
|
|
|
|
};
|