362 lines
8.3 KiB
JavaScript
362 lines
8.3 KiB
JavaScript
import _ from 'lodash';
|
|
import { Observable } from 'rx';
|
|
import { Actions } from 'thundercats';
|
|
import debugFactory from 'debug';
|
|
|
|
const debug = debugFactory('fcc:hikes:actions');
|
|
const noOp = { transform: () => {} };
|
|
|
|
function getCurrentHike(hikes = [{}], dashedName, currentHike) {
|
|
if (!dashedName) {
|
|
debug('no dashedName');
|
|
return hikes[0];
|
|
}
|
|
|
|
const filterRegex = new RegExp(dashedName, 'i');
|
|
if (currentHike && filterRegex.test(currentHike.dashedName)) {
|
|
return currentHike;
|
|
}
|
|
|
|
debug('setting new hike');
|
|
return hikes
|
|
.filter(({ dashedName }) => {
|
|
return filterRegex.test(dashedName);
|
|
})
|
|
.reduce((throwAway, hike) => {
|
|
return hike;
|
|
}, currentHike || {});
|
|
}
|
|
|
|
function findNextHike(hikes, id) {
|
|
if (!id) {
|
|
debug('find next hike no id provided');
|
|
return hikes[0];
|
|
}
|
|
const currentIndex = _.findIndex(hikes, ({ id: _id }) => _id === id);
|
|
return hikes[currentIndex + 1] || hikes[0];
|
|
}
|
|
|
|
|
|
function getMouse(e, [dx, dy]) {
|
|
let { pageX, pageY, touches, changedTouches } = e;
|
|
|
|
// touches can be empty on touchend
|
|
if (touches || changedTouches) {
|
|
e.preventDefault();
|
|
// these re-assigns the values of pageX, pageY from touches
|
|
({ pageX, pageY } = touches[0] || changedTouches[0]);
|
|
}
|
|
|
|
return [pageX - dx, pageY - dy];
|
|
}
|
|
|
|
export default Actions({
|
|
refs: { displayName: 'HikesActions' },
|
|
shouldBindMethods: true,
|
|
fetchHikes({ isPrimed, dashedName }) {
|
|
if (isPrimed) {
|
|
return {
|
|
transform: (state) => {
|
|
|
|
const { hikesApp: oldState } = state;
|
|
const currentHike = getCurrentHike(
|
|
oldState.hikes,
|
|
dashedName,
|
|
oldState.currentHike
|
|
);
|
|
|
|
const hikesApp = { ...oldState, currentHike };
|
|
return Object.assign({}, state, { hikesApp });
|
|
}
|
|
};
|
|
}
|
|
|
|
return this.readService$('hikes', null, null)
|
|
.map(hikes => {
|
|
const currentHike = getCurrentHike(hikes, dashedName);
|
|
return {
|
|
transform(state) {
|
|
const hikesApp = { ...state.hikesApp, currentHike, hikes };
|
|
return { ...state, hikesApp };
|
|
}
|
|
};
|
|
})
|
|
.catch(err => Observable.just({
|
|
transform(state) { return { ...state, err }; }
|
|
}));
|
|
},
|
|
|
|
toggleQuestions() {
|
|
return {
|
|
transform(state) {
|
|
const hikesApp = {
|
|
...state.hikesApp,
|
|
showQuestions: !state.hikesApp.showQuestions,
|
|
currentQuestion: 1
|
|
};
|
|
return { ...state, hikesApp };
|
|
}
|
|
};
|
|
},
|
|
|
|
grabQuestion(e) {
|
|
let { pageX, pageY, touches } = e;
|
|
if (touches) {
|
|
e.preventDefault();
|
|
// these re-assigns the values of pageX, pageY from touches
|
|
({ pageX, pageY } = touches[0]);
|
|
}
|
|
const delta = [pageX, pageY];
|
|
const mouse = [0, 0];
|
|
|
|
return {
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
isPressed: true,
|
|
delta,
|
|
mouse
|
|
}
|
|
};
|
|
}
|
|
};
|
|
},
|
|
|
|
releaseQuestion() {
|
|
return {
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
isPressed: false,
|
|
mouse: [0, 0]
|
|
}
|
|
};
|
|
}
|
|
};
|
|
},
|
|
|
|
moveQuestion({ e, delta }) {
|
|
const mouse = getMouse(e, delta);
|
|
|
|
return {
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
mouse
|
|
}
|
|
};
|
|
}
|
|
};
|
|
},
|
|
|
|
answer({
|
|
e,
|
|
answer,
|
|
userAnswer,
|
|
hike: { id, name, tests, challengeType },
|
|
currentQuestion,
|
|
isSignedIn,
|
|
delta,
|
|
info,
|
|
threshold
|
|
}) {
|
|
if (typeof userAnswer === 'undefined') {
|
|
const [positionX] = getMouse(e, delta);
|
|
|
|
// question released under threshold
|
|
if (Math.abs(positionX) < threshold) {
|
|
return noOp;
|
|
}
|
|
|
|
if (positionX >= threshold) {
|
|
userAnswer = true;
|
|
}
|
|
|
|
if (positionX <= -threshold) {
|
|
userAnswer = false;
|
|
}
|
|
}
|
|
|
|
// incorrect question
|
|
if (answer !== userAnswer) {
|
|
const startShake = {
|
|
transform(state) {
|
|
const toast = !info ?
|
|
state.toast :
|
|
{
|
|
id: state.toast && state.toast.id ? state.toast.id + 1 : 1,
|
|
title: 'Hint',
|
|
message: info,
|
|
type: 'info'
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
toast,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
shake: true
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
const removeShake = {
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
shake: false
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
return Observable
|
|
.just(removeShake)
|
|
.delay(500)
|
|
.startWith(startShake);
|
|
}
|
|
|
|
// move to next question
|
|
// index 0
|
|
if (tests[currentQuestion]) {
|
|
|
|
return Observable.just({
|
|
transform(state) {
|
|
const hikesApp = {
|
|
...state.hikesApp,
|
|
mouse: [0, 0]
|
|
};
|
|
return { ...state, hikesApp };
|
|
}
|
|
})
|
|
.delay(300)
|
|
.startWith({
|
|
transform(state) {
|
|
|
|
const hikesApp = {
|
|
...state.hikesApp,
|
|
currentQuestion: currentQuestion + 1,
|
|
mouse: [ userAnswer ? 1000 : -1000, 0],
|
|
isPressed: false
|
|
};
|
|
|
|
return { ...state, hikesApp };
|
|
}
|
|
});
|
|
}
|
|
|
|
// challenge completed
|
|
let update$;
|
|
if (isSignedIn) {
|
|
const body = { id, name, challengeType: +challengeType };
|
|
update$ = this.postJSON$('/completed-challenge', body)
|
|
// if post fails, will retry once
|
|
.retry(3)
|
|
.map(({ alreadyCompleted, points }) => ({
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
points,
|
|
toast: {
|
|
message:
|
|
'Challenge saved.' +
|
|
(alreadyCompleted ? '' : ' First time Completed!'),
|
|
title: 'Saved',
|
|
type: 'info',
|
|
id: state.toast && state.toast.id ? state.toast.id + 1 : 1
|
|
}
|
|
};
|
|
}
|
|
}))
|
|
.catch((errObj => {
|
|
const err = new Error(errObj.message);
|
|
err.stack = errObj.stack;
|
|
return {
|
|
transform(state) { return { ...state, err }; }
|
|
};
|
|
}));
|
|
} else {
|
|
update$ = Observable.just({ transform: (() => {}) });
|
|
}
|
|
|
|
const challengeCompleted$ = Observable.just({
|
|
transform(state) {
|
|
const { hikes, currentHike: { id } } = state.hikesApp;
|
|
const currentHike = findNextHike(hikes, id);
|
|
|
|
return {
|
|
...state,
|
|
points: isSignedIn ? state.points + 1 : state.points,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
currentHike,
|
|
showQuestions: false,
|
|
currentQuestion: 1,
|
|
mouse: [0, 0]
|
|
},
|
|
toast: {
|
|
title: 'Congratulations!',
|
|
message: 'Challenge completed.' + (isSignedIn ? ' Saving...' : ''),
|
|
id: state.toast && state.toast.id ?
|
|
state.toast.id + 1 :
|
|
1,
|
|
type: 'success'
|
|
},
|
|
location: {
|
|
action: 'PUSH',
|
|
pathname: currentHike && currentHike.dashedName ?
|
|
`/videos/${ currentHike.dashedName }` :
|
|
'/videos'
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
const correctAnswer = {
|
|
transform(state) {
|
|
return {
|
|
...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
isCorrect: true,
|
|
isPressed: false,
|
|
delta: [0, 0],
|
|
mouse: [ userAnswer ? 1000 : -1000, 0]
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
return Observable.merge(challengeCompleted$, update$)
|
|
.delay(300)
|
|
.startWith(correctAnswer)
|
|
.catch(err => Observable.just({
|
|
transform(state) { return { ...state, err }; }
|
|
}));
|
|
},
|
|
resetHike() {
|
|
return {
|
|
transform(state) {
|
|
return { ...state,
|
|
hikesApp: {
|
|
...state.hikesApp,
|
|
currentQuestion: 1,
|
|
showQuestions: false,
|
|
mouse: [0, 0],
|
|
delta: [0, 0]
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
});
|