From 00187628a4e4868b8240736e14652a4d37a4c560 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 4 Feb 2016 12:40:49 -0800 Subject: [PATCH] Video's and video challenge renders --- common/app/App.jsx | 1 + common/app/routes/Hikes/components/Hike.jsx | 3 +- common/app/routes/Hikes/components/Hikes.jsx | 5 +- .../app/routes/Hikes/components/Lecture.jsx | 2 +- common/app/routes/Hikes/components/Map.jsx | 2 +- .../app/routes/Hikes/components/Questions.jsx | 349 +++++++++--------- common/app/routes/Hikes/redux/actions.js | 2 +- common/app/routes/Hikes/redux/answer-saga.js | 17 +- .../routes/Hikes/redux/fetch-hikes-saga.js | 1 - common/app/routes/Hikes/redux/reducer.js | 25 +- common/app/routes/Hikes/redux/types.js | 2 +- common/app/utils/professor-x.js | 6 +- server/services/hikes.js | 2 +- 13 files changed, 217 insertions(+), 200 deletions(-) diff --git a/common/app/App.jsx b/common/app/App.jsx index 0afb196a5b..2041bcf47d 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -63,6 +63,7 @@ export class FreeCodeCamp extends React.Component { render() { const { username, points, picture } = this.props; const navProps = { username, points, picture }; + console.log('app', this.props.children); return (
diff --git a/common/app/routes/Hikes/components/Hike.jsx b/common/app/routes/Hikes/components/Hike.jsx index 6f75ae7f9b..55c3ac623f 100644 --- a/common/app/routes/Hikes/components/Hike.jsx +++ b/common/app/routes/Hikes/components/Hike.jsx @@ -17,6 +17,7 @@ const mapStateToProps = createSelector( }; } ); + // export plain component for testing export class Hike extends React.Component { static displayName = 'Hike'; @@ -71,4 +72,4 @@ export class Hike extends React.Component { } // export redux aware component -export default connect(mapStateToProps, { resetHike }); +export default connect(mapStateToProps, { resetHike })(Hike); diff --git a/common/app/routes/Hikes/components/Hikes.jsx b/common/app/routes/Hikes/components/Hikes.jsx index 17c027a4de..7c3aa04d4a 100644 --- a/common/app/routes/Hikes/components/Hikes.jsx +++ b/common/app/routes/Hikes/components/Hikes.jsx @@ -21,10 +21,11 @@ const mapStateToProps = createSelector( return { hikes: [] }; } return { - hikes: hikes.results.map(dashedName => hikes.enitites[dashedName]) + hikes: hikes.results.map(dashedName => hikes.entities[dashedName]) }; } ); + const fetchOptions = { fetchAction: 'fetchHikes', @@ -50,8 +51,6 @@ export class Hikes extends React.Component { updateTitle('Hikes'); } - shouldComponentUpdate = shouldComponentUpdate; - renderMap(hikes) { return ( diff --git a/common/app/routes/Hikes/components/Lecture.jsx b/common/app/routes/Hikes/components/Lecture.jsx index 1aac07f492..688cdc4b08 100644 --- a/common/app/routes/Hikes/components/Lecture.jsx +++ b/common/app/routes/Hikes/components/Lecture.jsx @@ -90,4 +90,4 @@ export class Lecture extends React.Component { } } -export default connect(mapStateToProps, { })(Lecture); +export default connect(mapStateToProps)(Lecture); diff --git a/common/app/routes/Hikes/components/Map.jsx b/common/app/routes/Hikes/components/Map.jsx index 5d4fc98dfe..81d77432c0 100644 --- a/common/app/routes/Hikes/components/Map.jsx +++ b/common/app/routes/Hikes/components/Map.jsx @@ -17,7 +17,7 @@ export default React.createClass({ const vidElements = hikes.map(({ title, dashedName}) => { return ( - +

{ title }

diff --git a/common/app/routes/Hikes/components/Questions.jsx b/common/app/routes/Hikes/components/Questions.jsx index 1982391d92..7404abd5ed 100644 --- a/common/app/routes/Hikes/components/Questions.jsx +++ b/common/app/routes/Hikes/components/Questions.jsx @@ -1,177 +1,188 @@ import React, { PropTypes } from 'react'; import { spring, Motion } from 'react-motion'; -import { contain } from 'thundercats-react'; +import { connect } from 'react-redux'; import { Button, Col, Row } from 'react-bootstrap'; +import { createSelector } from 'reselect'; + +import { + answerQuestion, + moveQuestion, + releaseQuestion, + grabQuestion +} from '../redux/actions'; const answerThreshold = 100; +const actionsToBind = { + answerQuestion, + moveQuestion, + releaseQuestion, + grabQuestion +}; -export default contain( - { - store: 'appStore', - actions: ['hikesActions'], - map({ hikesApp, username }) { - const { - currentHike, - currentQuestion = 1, - mouse = [0, 0], - isCorrect = false, - delta = [0, 0], - isPressed = false, - shake = false - } = hikesApp; - return { - hike: currentHike, - currentQuestion, - mouse, - isCorrect, - delta, - isPressed, - shake, - isSignedIn: !!username - }; - } - }, - React.createClass({ - displayName: 'Questions', +const mapStateToProps = createSelector( + state => state.hikesApp.hikes.entities, + state => state.hikesApp.hikes.results, + state => state.hikesApp.ui, + state => state.app.isSignedIn, + (hikesMap, hikesByDashname, ui, isSignedIn) => { + const { + currentQuestion = 1, + mouse = [ 0, 0 ], + delta = [ 0, 0 ], + isCorrect = false, + isPressed = false, + shouldShakeQuestion = false + } = ui; - propTypes: { - hike: PropTypes.object, - currentQuestion: PropTypes.number, - mouse: PropTypes.array, - isCorrect: PropTypes.bool, - delta: PropTypes.array, - isPressed: PropTypes.bool, - shake: PropTypes.bool, - isSignedIn: PropTypes.bool, - hikesActions: PropTypes.object - }, - - handleMouseUp(e, answer, info) { - e.stopPropagation(); - if (!this.props.isPressed) { - return null; - } - - const { - hike, - currentQuestion, - isSignedIn, - delta - } = this.props; - - this.props.hikesActions.releaseQuestion(); - this.props.hikesActions.answer({ - e, - answer, - hike, - delta, - currentQuestion, - isSignedIn, - info, - threshold: answerThreshold - }); - }, - - handleMouseMove(e) { - if (!this.props.isPressed) { - return null; - } - const { delta, hikesActions } = this.props; - - hikesActions.moveQuestion({ e, delta }); - }, - - onAnswer(answer, userAnswer, info) { - const { isSignedIn, hike, currentQuestion, hikesActions } = this.props; - return (e) => { - if (e && e.preventDefault) { - e.preventDefault(); - } - - return hikesActions.answer({ - answer, - userAnswer, - currentQuestion, - hike, - info, - isSignedIn - }); - }; - }, - - renderQuestion(number, question, answer, shake, info) { - const { hikesActions } = this.props; - const mouseUp = e => this.handleMouseUp(e, answer, info); - return ({ x }) => { - const style = { - WebkitTransform: `translate3d(${ x }px, 0, 0)`, - transform: `translate3d(${ x }px, 0, 0)` - }; - return ( -
-

Question { number }

-

{ question }

-
- ); - }; - }, - - render() { - const { - hike: { tests = [] } = {}, - mouse: [x], - currentQuestion, - shake - } = this.props; - - const [ question, answer, info ] = tests[currentQuestion - 1] || []; - const questionElement = this.renderQuestion( - currentQuestion, - question, - answer, - shake, - info - ); - - return ( - this.handleMouseUp(e, answer, info) } - xs={ 8 } - xsOffset={ 2 }> - - - { questionElement } - -
-
-
- - -
- - - ); - } - }) + return { + currentQuestion, + isCorrect, + mouse, + delta, + isPressed, + shouldShakeQuestion, + isSignedIn + }; + } ); + +class Question extends React.Component { + static displayName = 'Questions'; + + static propTypes = { + // actions + answerQuestion: PropTypes.func, + releaseQuestion: PropTypes.func, + moveQuestion: PropTypes.func, + grabQuestion: PropTypes.func, + // ui state + tests: PropTypes.array, + mouse: PropTypes.array, + delta: PropTypes.array, + isCorrect: PropTypes.bool, + isPressed: PropTypes.bool, + isSignedIn: PropTypes.bool, + currentQuestion: PropTypes.number, + shouldShakeQuestion: PropTypes.bool + }; + + handleMouseUp(e, answer, info) { + e.stopPropagation(); + if (!this.props.isPressed) { + return null; + } + + const { + releaseQuestion, + answerQuestion + } = this.props; + + releaseQuestion(); + answerQuestion({ + e, + answer, + info, + threshold: answerThreshold + }); + } + + handleMouseMove(isPressed, { delta, moveQuestion }) { + if (!isPressed) { + return null; + } + return e => moveQuestion({ e, delta }); + } + + onAnswer(answer, userAnswer, info) { + const { isSignedIn, answerQuestion } = this.props; + return e => { + if (e && e.preventDefault) { + e.preventDefault(); + } + + return answerQuestion({ + answer, + userAnswer, + info, + isSignedIn + }); + }; + } + + renderQuestion(number, question, answer, shouldShakeQuestion, info) { + const { grabQuestion, isPressed } = this.props; + const mouseUp = e => this.handleMouseUp(e, answer, info); + return ({ x }) => { + const style = { + WebkitTransform: `translate3d(${ x }px, 0, 0)`, + transform: `translate3d(${ x }px, 0, 0)` + }; + return ( +
+

Question { number }

+

{ question }

+
+ ); + }; + } + + render() { + const { + tests = [], + mouse: [x], + currentQuestion, + shouldShakeQuestion + } = this.props; + + const [ question, answer, info ] = tests[currentQuestion - 1] || []; + const questionElement = this.renderQuestion( + currentQuestion, + question, + answer, + shouldShakeQuestion, + info + ); + + return ( + this.handleMouseUp(e, answer, info) } + xs={ 8 } + xsOffset={ 2 }> + + + { questionElement } + +
+
+
+ + +
+ + + ); + } +} + +export default connect(mapStateToProps, actionsToBind)(Question); diff --git a/common/app/routes/Hikes/redux/actions.js b/common/app/routes/Hikes/redux/actions.js index a2aec2358a..51eabf90ca 100644 --- a/common/app/routes/Hikes/redux/actions.js +++ b/common/app/routes/Hikes/redux/actions.js @@ -43,7 +43,7 @@ export const moveQuestion = createAction( // info: String, // threshold: Number // }) => Action -export const answer = createAction(types.answer); +export const answerQuestion = createAction(types.answerQuestion); export const startShake = createAction(types.startShake); export const endShake = createAction(types.primeNextQuestion); diff --git a/common/app/routes/Hikes/redux/answer-saga.js b/common/app/routes/Hikes/redux/answer-saga.js index 0c4cd39211..c9c2500899 100644 --- a/common/app/routes/Hikes/redux/answer-saga.js +++ b/common/app/routes/Hikes/redux/answer-saga.js @@ -10,7 +10,7 @@ import { postJSON$ } from '../../../../utils/ajax-stream'; export default () => ({ getState, dispatch }) => next => { return function answerSaga(action) { - if (types.answer !== action.type) { + if (types.answerQuestion !== action.type) { return next(action); } @@ -56,14 +56,11 @@ export default () => ({ getState, dispatch }) => next => { // incorrect question if (answer !== finalAnswer) { if (info) { - dispatch({ - type: 'makeToast', - payload: { - title: 'Hint', - message: info, - type: 'info' - } - }); + dispatch(makeToast({ + title: 'Hint', + message: info, + type: 'info' + })); } return Observable @@ -100,7 +97,7 @@ export default () => ({ getState, dispatch }) => next => { }) .catch(error => { return Observable.just({ - type: 'error', + type: 'app.error', error }); }); diff --git a/common/app/routes/Hikes/redux/fetch-hikes-saga.js b/common/app/routes/Hikes/redux/fetch-hikes-saga.js index 07d482f358..5315a38ae8 100644 --- a/common/app/routes/Hikes/redux/fetch-hikes-saga.js +++ b/common/app/routes/Hikes/redux/fetch-hikes-saga.js @@ -32,7 +32,6 @@ export default ({ services }) => ({ dispatch }) => next => { const currentHike = getCurrentHike(hikes, dashedName); - console.log('foo', currentHike); return fetchHikesCompleted(hikes, currentHike); }) .catch(error => { diff --git a/common/app/routes/Hikes/redux/reducer.js b/common/app/routes/Hikes/redux/reducer.js index e6a49f0964..d0a95d7e15 100644 --- a/common/app/routes/Hikes/redux/reducer.js +++ b/common/app/routes/Hikes/redux/reducer.js @@ -7,16 +7,27 @@ const initialState = { results: [], entities: {} }, - // lecture state + // ui + // hike dashedName currentHike: '', - showQuestions: false + // 1 indexed + currentQuestion: 1, + // [ xPosition, yPosition ] + mouse: [ 0, 0 ], + // change in mouse position since pressed + // [ xDelta, yDelta ] + delta: [ 0, 0 ], + isPressed: false, + isCorrect: false, + shouldShakeQuestion: false, + shouldShowQuestions: false }; export default handleActions( { [types.toggleQuestion]: state => ({ ...state, - showQuestions: !state.showQuestions, + shouldShowQuestions: !state.shouldShowQuestions, currentQuestion: 1 }), @@ -38,13 +49,13 @@ export default handleActions( [types.resetHike]: state => ({ ...state, currentQuestion: 1, - showQuestions: false, + shouldShowQuestions: false, mouse: [0, 0], delta: [0, 0] }), - [types.startShake]: state => ({ ...state, shake: true }), - [types.endShake]: state => ({ ...state, shake: false }), + [types.startShake]: state => ({ ...state, shouldShakeQuestion: true }), + [types.endShake]: state => ({ ...state, shouldShakeQuestion: false }), [types.primeNextQuestion]: (state, { payload: userAnswer }) => ({ ...state, @@ -68,7 +79,7 @@ export default handleActions( [types.goToNextHike]: state => ({ ...state, - currentHike: findNextHike(state.hikes, state.currentHike.id), + currentHike: findNextHike(state.hikes, state.currentHike), showQuestions: false, currentQuestion: 1, mouse: [ 0, 0 ] diff --git a/common/app/routes/Hikes/redux/types.js b/common/app/routes/Hikes/redux/types.js index c96ae0e8e5..d04ab1c359 100644 --- a/common/app/routes/Hikes/redux/types.js +++ b/common/app/routes/Hikes/redux/types.js @@ -7,7 +7,7 @@ const types = [ 'releaseQuestion', 'moveQuestion', - 'answer', + 'answerQuestion', 'startShake', 'endShake', diff --git a/common/app/utils/professor-x.js b/common/app/utils/professor-x.js index ca1522ac0c..d1b2512858 100644 --- a/common/app/utils/professor-x.js +++ b/common/app/utils/professor-x.js @@ -1,5 +1,6 @@ import React, { PropTypes, createElement } from 'react'; import { Observable, CompositeDisposable } from 'rx'; +import shouldComponentUpdate from 'react-pure-render/function'; import debug from 'debug'; // interface contain { @@ -179,10 +180,7 @@ export default function contain(options = {}, Component) { } } - shouldComponentUpdate() { - // props should be immutable - return false; - } + shouldComponentUpdate = shouldComponentUpdate; render() { const { props } = this; diff --git a/server/services/hikes.js b/server/services/hikes.js index e672d857f3..456cd80f4a 100644 --- a/server/services/hikes.js +++ b/server/services/hikes.js @@ -25,7 +25,7 @@ export default function hikesService(app) { if (err) { return cb(err); } - cb(null, hikes); + cb(null, hikes.map(hike => hike.toJSON())); }); } };