Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
362 lines (325 sloc) 8.31 KB
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]
}
};
}
};
}
});
Something went wrong with that request. Please try again.