Files
freeCodeCamp/common/app/routes/challenges/components/step/Step.jsx

288 lines
6.6 KiB
JavaScript
Raw Normal View History

import React, { PropTypes } from 'react';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
2016-03-05 21:06:04 -08:00
import PureComponent from 'react-pure-render/component';
import LightBox from 'react-images';
2016-06-08 18:48:30 -07:00
import {
2016-10-22 20:09:23 +01:00
closeLightBoxImage,
2016-06-08 18:48:30 -07:00
completeAction,
openLightBoxImage,
2016-10-22 20:09:23 +01:00
stepBackward,
stepForward,
submitChallenge,
updateUnlockedSteps
2016-06-08 18:48:30 -07:00
} from '../../redux/actions';
2016-05-10 13:17:57 -07:00
import { challengeSelector } from '../../redux/selectors';
import { Button, Col, Image, Row } from 'react-bootstrap';
const mapStateToProps = createSelector(
2016-05-10 13:17:57 -07:00
challengeSelector,
2016-06-08 18:48:30 -07:00
state => state.challengesApp.currentIndex,
state => state.challengesApp.previousIndex,
state => state.challengesApp.isActionCompleted,
state => state.challengesApp.isLightBoxOpen,
2016-06-08 18:48:30 -07:00
(
{
challenge: { description = [] }
},
currentIndex,
previousIndex,
isActionCompleted,
isLightBoxOpen
2016-06-08 18:48:30 -07:00
) => ({
currentIndex,
isActionCompleted,
isLightBoxOpen,
2016-06-08 18:48:30 -07:00
step: description[currentIndex],
steps: description,
numOfSteps: description.length,
2016-09-27 19:56:03 -07:00
isLastStep: currentIndex + 1 >= description.length
})
);
const dispatchActions = {
2016-10-22 20:09:23 +01:00
closeLightBoxImage,
2016-06-08 18:48:30 -07:00
completeAction,
openLightBoxImage,
2016-10-22 20:09:23 +01:00
stepBackward,
stepForward,
submitChallenge,
updateUnlockedSteps
};
const propTypes = {
closeLightBoxImage: PropTypes.func.isRequired,
completeAction: PropTypes.func.isRequired,
currentIndex: PropTypes.number,
isActionCompleted: PropTypes.bool,
isLastStep: PropTypes.bool,
isLightBoxOpen: PropTypes.bool,
numOfSteps: PropTypes.number,
openLightBoxImage: PropTypes.func.isRequired,
step: PropTypes.array,
steps: PropTypes.array,
stepBackward: PropTypes.func,
stepForward: PropTypes.func,
submitChallenge: PropTypes.func.isRequired,
updateUnlockedSteps: PropTypes.func.isRequired
};
export class StepChallenge extends PureComponent {
2016-06-08 15:40:32 -07:00
constructor(...args) {
super(...args);
this.handleLightBoxOpen = this.handleLightBoxOpen.bind(this);
2016-06-08 15:40:32 -07:00
}
handleLightBoxOpen(e) {
if (!(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.props.openLightBoxImage();
}
}
2016-10-22 20:09:23 +01:00
componentWillMount() {
const { updateUnlockedSteps } = this.props;
updateUnlockedSteps([]);
}
componentWillUnmount() {
const { updateUnlockedSteps } = this.props;
updateUnlockedSteps([]);
}
componentWillReceiveProps(nextProps) {
const { steps, updateUnlockedSteps } = this.props;
if (nextProps.steps !== steps) {
updateUnlockedSteps([]);
}
}
2016-06-08 18:48:30 -07:00
renderActionButton(action, completeAction) {
const isApiAction = action === '#';
const buttonCopy = isApiAction ?
'Confirm' :
'Open link in new tab ';
if (!action) {
return null;
}
return (
<div>
<Button
block={ true }
bsSize='large'
bsStyle='primary'
href={ action }
2016-06-08 18:48:30 -07:00
onClick={ completeAction }
2016-06-08 15:40:32 -07:00
target='_blank'
>
2016-06-08 18:48:30 -07:00
{ buttonCopy } (this unlocks the next step)
</Button>
<div className='spacer' />
</div>
);
}
renderBackButton(index, stepBackward) {
if (index === 0) {
return (
<Col
className='hidden-xs'
2016-06-08 15:40:32 -07:00
md={ 4 }
>
{ ' ' }
</Col>
);
}
return (
<Button
bsSize='large'
bsStyle='primary'
className='col-sm-4 col-xs-12'
onClick={ stepBackward }
2016-06-08 15:40:32 -07:00
>
Go to my previous step
</Button>
);
}
renderNextButton(hasAction, isLastStep, isCompleted, stepForward) {
const btnClass = classnames({
'col-sm-4 col-xs-12': true,
disabled: hasAction && !isCompleted
});
return (
<Button
bsSize='large'
bsStyle='primary'
className={ btnClass }
2016-06-08 18:48:30 -07:00
disabled={ hasAction && !isCompleted }
onClick={ stepForward }
2016-06-08 15:40:32 -07:00
>
{ isLastStep ? 'Finish challenge' : 'Go to my next step'}
</Button>
);
}
2016-06-08 18:48:30 -07:00
renderStep({
step,
currentIndex,
numOfSteps,
isActionCompleted,
completeAction,
isLastStep,
stepForward,
stepBackward
2016-06-08 18:48:30 -07:00
}) {
if (!Array.isArray(step)) {
return null;
}
const [imgUrl, imgAlt, info, action] = step;
return (
<div
className=''
2016-06-08 15:40:32 -07:00
key={ imgUrl }
>
<a
href={ imgUrl }
onClick={ this.handleLightBoxOpen }
target='_blank'
>
<Image
alt={ imgAlt }
className='center-block'
responsive={ true }
2016-06-08 15:40:32 -07:00
src={ imgUrl }
/>
</a>
<Row>
<div className='spacer' />
<Col
md={ 8 }
mdOffset={ 2 }
sm={ 10 }
smOffset={ 1 }
2016-06-08 15:40:32 -07:00
xs={ 12 }
>
<p
className='challenge-step-description'
2016-06-08 15:40:32 -07:00
dangerouslySetInnerHTML={{ __html: info }}
/>
</Col>
</Row>
<div className='spacer' />
<div className='challenge-button-block'>
2016-06-08 18:48:30 -07:00
{ this.renderActionButton(action, completeAction) }
{ this.renderBackButton(currentIndex, stepBackward) }
<Col
className='challenge-step-counter large-p text-center'
sm={ 4 }
2016-06-08 15:40:32 -07:00
xs={ 12 }
>
2016-06-08 18:48:30 -07:00
( { currentIndex + 1 } / { numOfSteps })
</Col>
{
this.renderNextButton(
!!action,
isLastStep,
isActionCompleted,
stepForward
)
}
</div>
<div className='clearfix' />
</div>
);
}
renderImages(steps) {
// will preload all the image
if (!Array.isArray(steps)) {
return null;
}
return steps.map(([imgUrl, imgAlt]) => (
<div key={ imgUrl }>
<Image
alt={ imgAlt }
responsive={ true }
2016-06-08 15:40:32 -07:00
src={ imgUrl }
/>
</div>
));
}
2016-03-05 21:06:04 -08:00
render() {
const {
step,
steps,
isLightBoxOpen,
closeLightBoxImage
} = this.props;
2016-03-05 21:06:04 -08:00
return (
<Col
md={ 8 }
2016-06-08 15:40:32 -07:00
mdOffset={ 2 }
>
2016-09-27 19:56:03 -07:00
{ this.renderStep(this.props) }
<div className='hidden'>
2016-06-08 18:48:30 -07:00
{ this.renderImages(steps) }
</div>
<LightBox
backdropClosesModal={ true }
images={ [ { src: step[0] } ] }
isOpen={ isLightBoxOpen }
onClose={ closeLightBoxImage }
showImageCount={ false }
/>
<div className='spacer' />
</Col>
2016-03-05 21:06:04 -08:00
);
}
}
2016-10-22 20:09:23 +01:00
StepChallenge.displayName = 'StepChallenge';
StepChallenge.propTypes = propTypes;
export default connect(mapStateToProps, dispatchActions)(StepChallenge);