Video's and video challenge renders
This commit is contained in:
@ -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 (
|
||||
<div>
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<HikesMap hikes={ hikes }/>
|
||||
|
@ -90,4 +90,4 @@ export class Lecture extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { })(Lecture);
|
||||
export default connect(mapStateToProps)(Lecture);
|
||||
|
@ -17,7 +17,7 @@ export default React.createClass({
|
||||
const vidElements = hikes.map(({ title, dashedName}) => {
|
||||
return (
|
||||
<ListGroupItem key={ dashedName }>
|
||||
<Link to={ `/hikes/${dashedName}` }>
|
||||
<Link to={ `/videos/${dashedName}` }>
|
||||
<h3>{ title }</h3>
|
||||
</Link>
|
||||
</ListGroupItem>
|
||||
|
@ -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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
})
|
||||
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 (
|
||||
<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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
|
@ -32,7 +32,6 @@ export default ({ services }) => ({ dispatch }) => next => {
|
||||
|
||||
const currentHike = getCurrentHike(hikes, dashedName);
|
||||
|
||||
console.log('foo', currentHike);
|
||||
return fetchHikesCompleted(hikes, currentHike);
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -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 ]
|
||||
|
@ -7,7 +7,7 @@ const types = [
|
||||
'releaseQuestion',
|
||||
'moveQuestion',
|
||||
|
||||
'answer',
|
||||
'answerQuestion',
|
||||
|
||||
'startShake',
|
||||
'endShake',
|
||||
|
@ -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;
|
||||
|
@ -25,7 +25,7 @@ export default function hikesService(app) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
cb(null, hikes);
|
||||
cb(null, hikes.map(hike => hike.toJSON()));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user