Video's and video challenge renders

This commit is contained in:
Berkeley Martinez
2016-02-04 12:40:49 -08:00
parent 8ef3fdb6a0
commit 00187628a4
13 changed files with 217 additions and 200 deletions

View File

@ -63,6 +63,7 @@ export class FreeCodeCamp extends React.Component {
render() { render() {
const { username, points, picture } = this.props; const { username, points, picture } = this.props;
const navProps = { username, points, picture }; const navProps = { username, points, picture };
console.log('app', this.props.children);
return ( return (
<div> <div>

View File

@ -17,6 +17,7 @@ const mapStateToProps = createSelector(
}; };
} }
); );
// export plain component for testing // export plain component for testing
export class Hike extends React.Component { export class Hike extends React.Component {
static displayName = 'Hike'; static displayName = 'Hike';
@ -71,4 +72,4 @@ export class Hike extends React.Component {
} }
// export redux aware component // export redux aware component
export default connect(mapStateToProps, { resetHike }); export default connect(mapStateToProps, { resetHike })(Hike);

View File

@ -21,10 +21,11 @@ const mapStateToProps = createSelector(
return { hikes: [] }; return { hikes: [] };
} }
return { return {
hikes: hikes.results.map(dashedName => hikes.enitites[dashedName]) hikes: hikes.results.map(dashedName => hikes.entities[dashedName])
}; };
} }
); );
const fetchOptions = { const fetchOptions = {
fetchAction: 'fetchHikes', fetchAction: 'fetchHikes',
@ -50,8 +51,6 @@ export class Hikes extends React.Component {
updateTitle('Hikes'); updateTitle('Hikes');
} }
shouldComponentUpdate = shouldComponentUpdate;
renderMap(hikes) { renderMap(hikes) {
return ( return (
<HikesMap hikes={ hikes }/> <HikesMap hikes={ hikes }/>

View File

@ -90,4 +90,4 @@ export class Lecture extends React.Component {
} }
} }
export default connect(mapStateToProps, { })(Lecture); export default connect(mapStateToProps)(Lecture);

View File

@ -17,7 +17,7 @@ export default React.createClass({
const vidElements = hikes.map(({ title, dashedName}) => { const vidElements = hikes.map(({ title, dashedName}) => {
return ( return (
<ListGroupItem key={ dashedName }> <ListGroupItem key={ dashedName }>
<Link to={ `/hikes/${dashedName}` }> <Link to={ `/videos/${dashedName}` }>
<h3>{ title }</h3> <h3>{ title }</h3>
</Link> </Link>
</ListGroupItem> </ListGroupItem>

View File

@ -1,177 +1,188 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { spring, Motion } from 'react-motion'; import { spring, Motion } from 'react-motion';
import { contain } from 'thundercats-react'; import { connect } from 'react-redux';
import { Button, Col, Row } from 'react-bootstrap'; import { Button, Col, Row } from 'react-bootstrap';
import { createSelector } from 'reselect';
import {
answerQuestion,
moveQuestion,
releaseQuestion,
grabQuestion
} from '../redux/actions';
const answerThreshold = 100; const answerThreshold = 100;
const actionsToBind = {
answerQuestion,
moveQuestion,
releaseQuestion,
grabQuestion
};
export default contain( const mapStateToProps = createSelector(
{ state => state.hikesApp.hikes.entities,
store: 'appStore', state => state.hikesApp.hikes.results,
actions: ['hikesActions'], state => state.hikesApp.ui,
map({ hikesApp, username }) { state => state.app.isSignedIn,
const { (hikesMap, hikesByDashname, ui, isSignedIn) => {
currentHike, const {
currentQuestion = 1, currentQuestion = 1,
mouse = [0, 0], mouse = [ 0, 0 ],
isCorrect = false, delta = [ 0, 0 ],
delta = [0, 0], isCorrect = false,
isPressed = false, isPressed = false,
shake = false shouldShakeQuestion = false
} = hikesApp; } = ui;
return {
hike: currentHike,
currentQuestion,
mouse,
isCorrect,
delta,
isPressed,
shake,
isSignedIn: !!username
};
}
},
React.createClass({
displayName: 'Questions',
propTypes: { return {
hike: PropTypes.object, currentQuestion,
currentQuestion: PropTypes.number, isCorrect,
mouse: PropTypes.array, mouse,
isCorrect: PropTypes.bool, delta,
delta: PropTypes.array, isPressed,
isPressed: PropTypes.bool, shouldShakeQuestion,
shake: PropTypes.bool, isSignedIn
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 (
<article
className={ shake ? 'animated swing shake' : '' }
onMouseDown={ hikesActions.grabQuestion }
onMouseLeave={ mouseUp }
onMouseMove={ this.handleMouseMove }
onMouseUp={ mouseUp }
onTouchEnd={ mouseUp }
onTouchMove={ this.handleMouseMove }
onTouchStart={ hikesActions.grabQuestion }
style={ style }>
<h4>Question { number }</h4>
<p>{ question }</p>
</article>
);
};
},
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 (
<Col
onMouseUp={ e => this.handleMouseUp(e, answer, info) }
xs={ 8 }
xsOffset={ 2 }>
<Row>
<Motion style={{ x: spring(x, { stiffness: 120, damping: 10 }) }}>
{ questionElement }
</Motion>
<div className='spacer' />
<hr />
<div>
<Button
bsSize='large'
bsStyle='primary'
className='pull-left'
onClick={ this.onAnswer(answer, false, info) }>
false
</Button>
<Button
bsSize='large'
bsStyle='primary'
className='pull-right'
onClick={ this.onAnswer(answer, true, info) }>
true
</Button>
</div>
</Row>
</Col>
);
}
})
); );
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 (
<article
className={ shouldShakeQuestion ? 'animated swing shake' : '' }
onMouseDown={ grabQuestion }
onMouseLeave={ mouseUp }
onMouseMove={ this.handleMouseMove(isPressed, this.props) }
onMouseUp={ mouseUp }
onTouchEnd={ mouseUp }
onTouchMove={ this.handleMouseMove(isPressed, this.props) }
onTouchStart={ grabQuestion }
style={ style }>
<h4>Question { number }</h4>
<p>{ question }</p>
</article>
);
};
}
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 (
<Col
onMouseUp={ e => this.handleMouseUp(e, answer, info) }
xs={ 8 }
xsOffset={ 2 }>
<Row>
<Motion style={{ x: spring(x, { stiffness: 120, damping: 10 }) }}>
{ questionElement }
</Motion>
<div className='spacer' />
<hr />
<div>
<Button
bsSize='large'
bsStyle='primary'
className='pull-left'
onClick={ this.onAnswer(answer, false, info) }>
false
</Button>
<Button
bsSize='large'
bsStyle='primary'
className='pull-right'
onClick={ this.onAnswer(answer, true, info) }>
true
</Button>
</div>
</Row>
</Col>
);
}
}
export default connect(mapStateToProps, actionsToBind)(Question);

View File

@ -43,7 +43,7 @@ export const moveQuestion = createAction(
// info: String, // info: String,
// threshold: Number // threshold: Number
// }) => Action // }) => Action
export const answer = createAction(types.answer); export const answerQuestion = createAction(types.answerQuestion);
export const startShake = createAction(types.startShake); export const startShake = createAction(types.startShake);
export const endShake = createAction(types.primeNextQuestion); export const endShake = createAction(types.primeNextQuestion);

View File

@ -10,7 +10,7 @@ import { postJSON$ } from '../../../../utils/ajax-stream';
export default () => ({ getState, dispatch }) => next => { export default () => ({ getState, dispatch }) => next => {
return function answerSaga(action) { return function answerSaga(action) {
if (types.answer !== action.type) { if (types.answerQuestion !== action.type) {
return next(action); return next(action);
} }
@ -56,14 +56,11 @@ export default () => ({ getState, dispatch }) => next => {
// incorrect question // incorrect question
if (answer !== finalAnswer) { if (answer !== finalAnswer) {
if (info) { if (info) {
dispatch({ dispatch(makeToast({
type: 'makeToast', title: 'Hint',
payload: { message: info,
title: 'Hint', type: 'info'
message: info, }));
type: 'info'
}
});
} }
return Observable return Observable
@ -100,7 +97,7 @@ export default () => ({ getState, dispatch }) => next => {
}) })
.catch(error => { .catch(error => {
return Observable.just({ return Observable.just({
type: 'error', type: 'app.error',
error error
}); });
}); });

View File

@ -32,7 +32,6 @@ export default ({ services }) => ({ dispatch }) => next => {
const currentHike = getCurrentHike(hikes, dashedName); const currentHike = getCurrentHike(hikes, dashedName);
console.log('foo', currentHike);
return fetchHikesCompleted(hikes, currentHike); return fetchHikesCompleted(hikes, currentHike);
}) })
.catch(error => { .catch(error => {

View File

@ -7,16 +7,27 @@ const initialState = {
results: [], results: [],
entities: {} entities: {}
}, },
// lecture state // ui
// hike dashedName
currentHike: '', 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( export default handleActions(
{ {
[types.toggleQuestion]: state => ({ [types.toggleQuestion]: state => ({
...state, ...state,
showQuestions: !state.showQuestions, shouldShowQuestions: !state.shouldShowQuestions,
currentQuestion: 1 currentQuestion: 1
}), }),
@ -38,13 +49,13 @@ export default handleActions(
[types.resetHike]: state => ({ [types.resetHike]: state => ({
...state, ...state,
currentQuestion: 1, currentQuestion: 1,
showQuestions: false, shouldShowQuestions: false,
mouse: [0, 0], mouse: [0, 0],
delta: [0, 0] delta: [0, 0]
}), }),
[types.startShake]: state => ({ ...state, shake: true }), [types.startShake]: state => ({ ...state, shouldShakeQuestion: true }),
[types.endShake]: state => ({ ...state, shake: false }), [types.endShake]: state => ({ ...state, shouldShakeQuestion: false }),
[types.primeNextQuestion]: (state, { payload: userAnswer }) => ({ [types.primeNextQuestion]: (state, { payload: userAnswer }) => ({
...state, ...state,
@ -68,7 +79,7 @@ export default handleActions(
[types.goToNextHike]: state => ({ [types.goToNextHike]: state => ({
...state, ...state,
currentHike: findNextHike(state.hikes, state.currentHike.id), currentHike: findNextHike(state.hikes, state.currentHike),
showQuestions: false, showQuestions: false,
currentQuestion: 1, currentQuestion: 1,
mouse: [ 0, 0 ] mouse: [ 0, 0 ]

View File

@ -7,7 +7,7 @@ const types = [
'releaseQuestion', 'releaseQuestion',
'moveQuestion', 'moveQuestion',
'answer', 'answerQuestion',
'startShake', 'startShake',
'endShake', 'endShake',

View File

@ -1,5 +1,6 @@
import React, { PropTypes, createElement } from 'react'; import React, { PropTypes, createElement } from 'react';
import { Observable, CompositeDisposable } from 'rx'; import { Observable, CompositeDisposable } from 'rx';
import shouldComponentUpdate from 'react-pure-render/function';
import debug from 'debug'; import debug from 'debug';
// interface contain { // interface contain {
@ -179,10 +180,7 @@ export default function contain(options = {}, Component) {
} }
} }
shouldComponentUpdate() { shouldComponentUpdate = shouldComponentUpdate;
// props should be immutable
return false;
}
render() { render() {
const { props } = this; const { props } = this;

View File

@ -25,7 +25,7 @@ export default function hikesService(app) {
if (err) { if (err) {
return cb(err); return cb(err);
} }
cb(null, hikes); cb(null, hikes.map(hike => hike.toJSON()));
}); });
} }
}; };