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