Permalink
Cannot retrieve contributors at this time
Fetching contributors…

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] | |
} | |
}; | |
} | |
}; | |
} | |
}); |