Move flux contianer to top level hike comp

Removes all the lower level hikes
Finishing questions takes you to next hike
BUG: sometimes it takes you an incorrect next challenge
This commit is contained in:
Berkeley Martinez
2015-07-17 22:33:46 -07:00
parent 5da04b14d3
commit b945fc94a4
6 changed files with 267 additions and 318 deletions

View File

@ -1,32 +1,54 @@
import React, { PropTypes } from 'react';
import stampit from 'react-stampit';
import { Row } from 'react-bootstrap';
import { contain } from 'thundercats-react';
// import debugFactory from 'debug';
import HikesMap from './Map.jsx';
// const debug = debugFactory('freecc:hikes');
export default stampit(React, {
displayName: 'Hikes',
propTypes: {
children: PropTypes.element
export default contain(
{
store: 'hikesStore',
fetchAction: 'hikesActions.fetchHikes',
getPayload: ({ hikes, params }) => ({
isPrimed: (hikes && !!hikes.length),
dashedName: params.dashedName
})
},
stampit(React, {
displayName: 'Hikes',
renderMap() {
return (
<HikesMap />
);
},
propTypes: {
children: PropTypes.element,
currentHike: PropTypes.object,
hikes: PropTypes.array
},
render() {
return (
<div>
<Row>
{ this.props.children || this.renderMap() }
</Row>
</div>
);
}
});
renderMap(hikes) {
return (
<HikesMap hikes={ hikes }/>
);
},
renderChild(children, hikes, currentHike) {
if (!children) {
return null;
}
return React.cloneElement(children, { hikes, currentHike });
},
render() {
const { hikes, children, currentHike } = this.props;
return (
<div>
<Row>
{ this.renderChild(children, hikes, currentHike) ||
this.renderMap(hikes) }
</Row>
</div>
);
}
})
);

View File

@ -1,94 +1,71 @@
import React, { PropTypes } from 'react';
import { Button, Col, Row, Panel } from 'react-bootstrap';
import { Navigation } from 'react-router';
import { contain } from 'thundercats-react';
import stampit from 'react-stampit';
import Vimeo from 'react-vimeo';
import debugFactory from 'debug';
const debug = debugFactory('freecc:hikes');
export default contain(
{
store: 'hikesStore',
fetchAction: 'hikesActions.fetchCurrentHike',
getPayload({ currentHike, params: { dashedName } }) {
const filterRegex = new RegExp(dashedName, 'i');
if (currentHike && filterRegex.test(currentHike.dashedName)) {
return {
isPrimed: true,
dashedName
};
}
return {
isPrimed: false,
dashedName: dashedName
};
}
export default stampit(React, {
displayName: 'Lecture',
propTypes: {
currentHike: PropTypes.object,
params: PropTypes.object
},
stampit(React, {
displayName: 'Lecture',
propTypes: {
currentHike: PropTypes.object,
dashedName: PropTypes.string,
params: PropTypes.object
},
handleError: debug,
handleError: debug,
handleFinish() {
debug('loading questions');
const { dashedName } = this.props.params;
this.transitionTo(`/hikes/${dashedName}/questions/1`);
},
handleFinish() {
debug('loading questions');
const { dashedName } = this.props.params;
this.transitionTo(`/hikes/${dashedName}/questions/1`);
},
renderTranscript(transcript, dashedName) {
return transcript.map((line, index) => (
<p key={ dashedName + index }>{ line }</p>
));
},
renderTranscript(transcript) {
return transcript.map((line, index) => (
<p key={ index }>{ line }</p>
)
);
},
render() {
const {
title,
challengeSeed = ['1'],
description = []
} = this.props.currentHike;
const { dashedName } = this.props.params;
render() {
const {
title,
challengeSeed = ['1'],
description = []
} = this.props.currentHike;
const [ id ] = challengeSeed;
const [ id ] = challengeSeed;
const videoTitle = <h2>{ title }</h2>;
return (
<Col xs={ 12 }>
<Row>
<Panel className={ 'text-center' } title={ videoTitle }>
<Vimeo
onError={ this.handleError }
onFinish= { ::this.handleFinish }
videoId={ id } />
const videoTitle = <h2>{ title }</h2>;
return (
<Col xs={ 12 }>
<Row>
<Panel className={ 'text-center' } title={ videoTitle }>
<Vimeo
onError={ this.handleError }
onFinish= { ::this.handleFinish }
videoId={ id } />
</Panel>
</Row>
<Row>
<Col xs={ 12 }>
<Panel>
{ this.renderTranscript(description, dashedName) }
</Panel>
</Row>
<Row>
<Col xs={ 12 }>
<Panel>
<p>
{ this.renderTranscript(description) }
</p>
</Panel>
<Panel>
<Button
block={ true }
bsSize='large'
onClick={ ::this.handleFinish }>
Take me to the Questions
</Button>
</Panel>
</Col>
</Row>
</Col>
);
}
}).compose(Navigation)
);
<Panel>
<Button
block={ true }
bsSize='large'
onClick={ ::this.handleFinish }>
Take me to the Questions
</Button>
</Panel>
</Col>
</Row>
</Col>
);
}
}).compose(Navigation);

View File

@ -1,47 +1,39 @@
import React, { PropTypes } from 'react';
import stampit from 'react-stampit';
import { Link } from 'react-router';
import { contain } from 'thundercats-react';
import { ListGroup, ListGroupItem, Panel } from 'react-bootstrap';
export default contain(
{
store: 'hikesStore',
fetchAction: 'hikesActions.fetchHikes',
getPayload: ({ hikes }) => ({ isPrimed: (hikes && !!hikes.length) })
export default stampit(React, {
displayName: 'HikesMap',
propTypes: {
hikes: PropTypes.array
},
stampit(React, {
displayName: 'HikesMap',
propTypes: {
hikes: PropTypes.array
},
render() {
const {
hikes
} = this.props;
const vidElements = hikes.map(({ title, dashedName}) => {
return (
<ListGroupItem key={ dashedName }>
<Link to={ `/hikes/${dashedName}` }>
<h3>{ title }</h3>
</Link>
</ListGroupItem>
);
});
render() {
const {
hikes
} = this.props;
const vidElements = hikes.map(({ title, dashedName}) => {
return (
<div>
<Panel>
<h2>Welcome To Hikes!</h2>
</Panel>
<ListGroup>
{ vidElements }
</ListGroup>
</div>
<ListGroupItem key={ dashedName }>
<Link to={ `/hikes/${dashedName}` }>
<h3>{ title }</h3>
</Link>
</ListGroupItem>
);
}
})
);
});
return (
<div>
<Panel>
<h2>Welcome To Hikes!</h2>
</Panel>
<ListGroup>
{ vidElements }
</ListGroup>
</div>
);
}
});

View File

@ -1,7 +1,6 @@
import React, { PropTypes } from 'react';
import { Navigation, TransitionHook } from 'react-router';
import stampit from 'react-stampit';
import { contain } from 'thundercats-react';
import debugFactory from 'debug';
import {
Button,
@ -13,145 +12,121 @@ import {
const debug = debugFactory('freecc:hikes');
export default contain(
{
store: 'hikesStore',
map({ currentHike }) {
const { tests = [] } = currentHike;
export default stampit(React, {
state: { showInfo: false },
displayName: 'Question',
return { currentHike, tests };
},
fetchAction: 'hikesActions.fetchCurrentHike',
getPayload({ currentHike, params: { dashedName } }) {
const filterRegex = new RegExp(dashedName, 'i');
if (currentHike && filterRegex.test(currentHike.dashedName)) {
return {
isPrimed: true,
dashedName
};
}
return {
isPrimed: false,
dashedName: dashedName
};
}
propTypes: {
currentHike: PropTypes.object,
dashedName: PropTypes.string,
hikes: PropTypes.array,
params: PropTypes.object
},
stampit(React, {
state: { showInfo: false },
displayName: 'Question',
propTypes: {
currentHike: PropTypes.object,
dashedName: PropTypes.string,
params: PropTypes.object,
tests: PropTypes.array
},
onAnswer(answer, userAnswer, info, e) {
if (e && e.preventDefault) {
e.preventDefault();
}
if (answer === userAnswer) {
debug('correct answer!');
this.setState({ showInfo: true });
}
return debug('incorrect');
},
onCorrectAnswer() {
const { hikes, currentHike } = this.props;
const { dashedName, number } = this.props.params;
const { difficulty, tests } = currentHike;
const nextQuestionIndex = +number;
this.setState({ showInfo: false }, () => {
if (tests[nextQuestionIndex]) {
return this.transitionTo(
`/hikes/${ dashedName }/questions/${ nextQuestionIndex + 1 }`
);
}
// next questions does not exit
// find next hike
//
const nextHike = hikes
// hikes is in oder of difficulty, lets get reverse order
.reverse()
// now lets find the hike with the difficulty right above this one
.reduce((lowerHike, hike) => {
if (hike.difficulty > difficulty) {
return hike;
}
return lowerHike;
}, null);
if (nextHike) {
return this.transitionTo(`${ nextHike.dashedName }`);
}
debug('next Hike was not found');
});
},
routerWillLeave(/* nextState, router, cb[optional] */) {
// TODO(berks): do animated transitions here stuff here
},
renderInfo(showInfo, info) {
return (
<Modal
backdrop={ false }
onHide={ ::this.onCorrectAnswer }
show={ showInfo }>
<Modal.Body>
<h3>
{ info || 'correct!' }
</h3>
</Modal.Body>
<Modal.Footer>
<Button
block={ true }
bsSize='large'
onClick={ ::this.onCorrectAnswer }>
To next questions
</Button>
</Modal.Footer>
</Modal>
);
},
render() {
const { showInfo } = this.state;
const { tests } = this.props;
const { number = '1' } = this.props.params;
const [question, answer, info] = tests[number - 1] || [];
return (
<Col
xs={ 8 }
xsOffset={ 2 }>
<Row>
<Panel>
<p>{ question }</p>
</Panel>
{ this.renderInfo(showInfo, info) }
<Panel>
<Button
bsSize='large'
className='pull-left'
onClick={ this.onAnswer.bind(this, answer, false, info) }>
false
</Button>
<Button
bsSize='large'
className='pull-right'
onClick={ this.onAnswer.bind(this, answer, true, info) }>
true
</Button>
</Panel>
</Row>
</Col>
);
onAnswer(answer, userAnswer, info, e) {
if (e && e.preventDefault) {
e.preventDefault();
}
})
.compose(Navigation)
.compose(TransitionHook)
);
if (answer === userAnswer) {
debug('correct answer!');
this.setState({ showInfo: true });
}
return debug('incorrect');
},
onCorrectAnswer() {
const { hikes, currentHike } = this.props;
const { dashedName, number } = this.props.params;
const { difficulty, tests } = currentHike;
const nextQuestionIndex = +number;
this.setState({ showInfo: false }, () => {
if (tests[nextQuestionIndex]) {
return this.transitionTo(
`/hikes/${ dashedName }/questions/${ nextQuestionIndex + 1 }`
);
}
// next questions does not exit
// find next hike
//
const nextHike = hikes
// hikes is in oder of difficulty, lets get reverse order
.reverse()
// now lets find the hike with the difficulty right above this one
.reduce((lowerHike, hike) => {
if (hike.difficulty > difficulty) {
return hike;
}
return lowerHike;
}, null);
if (nextHike) {
return this.transitionTo(`/hikes/${ nextHike.dashedName }`);
}
debug('next Hike was not found');
});
},
routerWillLeave(/* nextState, router, cb[optional] */) {
// TODO(berks): do animated transitions here stuff here
},
renderInfo(showInfo, info) {
return (
<Modal
backdrop={ false }
onHide={ ::this.onCorrectAnswer }
show={ showInfo }>
<Modal.Body>
<h3>
{ info || 'correct!' }
</h3>
</Modal.Body>
<Modal.Footer>
<Button
block={ true }
bsSize='large'
onClick={ ::this.onCorrectAnswer }>
To next questions
</Button>
</Modal.Footer>
</Modal>
);
},
render() {
const { showInfo } = this.state;
const { currentHike: { tests = [] } } = this.props;
const { number = '1' } = this.props.params;
const [question, answer, info] = tests[number - 1] || [];
return (
<Col
xs={ 8 }
xsOffset={ 2 }>
<Row>
<Panel>
<p>{ question }</p>
</Panel>
{ this.renderInfo(showInfo, info) }
<Panel>
<Button
bsSize='large'
className='pull-left'
onClick={ this.onAnswer.bind(this, answer, false, info) }>
false
</Button>
<Button
bsSize='large'
className='pull-right'
onClick={ this.onAnswer.bind(this, answer, true, info) }>
true
</Button>
</Panel>
</Row>
</Col>
);
}
})
.compose(Navigation)
.compose(TransitionHook);

View File

@ -8,28 +8,42 @@ const service = new Fetchr({
xhrPath: '/services'
});
function getCurrentHike(hikes =[{}], dashedName, currentHike) {
if (!dashedName) {
return hikes[0];
}
const filterRegex = new RegExp(dashedName, 'i');
return hikes
.filter(({ dashedName }) => {
return filterRegex.test(dashedName);
})
.reduce((throwAway, hike) => {
return hike;
}, currentHike || {});
}
export default Actions({
// start fetching hikes
fetchHikes: null,
// set hikes on store
setHikes: null,
fetchCurrentHike: null,
setCurrentHike: null
setHikes: null
})
.refs({ displayName: 'HikesActions' })
.init(({ instance }) => {
// set up hikes fetching
instance.fetchHikes.subscribe(
({ isPrimed }) => {
({ isPrimed, dashedName }) => {
if (isPrimed) {
return instance.setHikes({
transform: (oldState) => {
const { hikes } = oldState;
const newState = {
currentHike: (oldState.currentHike || hikes[0] || {})
};
return assign({}, oldState, newState);
const { hikes, currentContext } = oldState;
const currentHike = getCurrentHike(
hikes,
dashedName,
currentContext
);
return assign({}, oldState, { currentHike });
}
});
}
@ -40,38 +54,10 @@ export default Actions({
instance.setHikes({
set: {
hikes: hikes,
currentHike: hikes[0] || {}
currentHike: getCurrentHike(hikes, dashedName)
}
});
});
}
);
instance.fetchCurrentHike.subscribe(({ isPrimed, dashedName }) => {
if (isPrimed) {
return instance.setCurrentHike({
transform: (oldState) => {
const { hikes } = oldState;
const filterRegex = new RegExp(dashedName, 'i');
const potentialHike = hikes
.filter(({ dashedName }) => {
return filterRegex.test(dashedName);
})
.reduce((throwAway, hike) => {
return hike;
});
// TODO(berks): do something when potential hike does not exist
return assign({}, oldState, { currentHike: potentialHike });
}
});
}
service.read('hikes', { dashedName }, null, (err, hikes) => {
if (err) {
debug('error occurred fetching hike', err);
}
const [currentHike] = hikes;
return instance.setCurrentHike({ set: { currentHike } });
});
});
});

View File

@ -1,6 +1,5 @@
import { Store } from 'thundercats';
const { fromMany } = Store;
const initialValue = {
hikes: [],
currentHike: {}
@ -10,11 +9,9 @@ export default Store(initialValue)
.refs({ displayName: 'HikesStore'})
.init(({ instance, args }) => {
const [cat] = args;
let {
setHikes,
setCurrentHike
} = cat.getActions('hikesActions');
instance.register(fromMany(setHikes, setCurrentHike));
let { setHikes } = cat.getActions('hikesActions');
instance.register(setHikes);
return instance;
});