Files
freeCodeCamp/packages/learn/src/templates/Challenges/classic/Show.js

311 lines
7.6 KiB
JavaScript
Raw Normal View History

2018-04-06 14:51:52 +01:00
import React, { Fragment, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
2018-09-11 16:10:21 +03:00
import { graphql } from 'gatsby';
2018-04-06 14:51:52 +01:00
import Editor from './Editor';
import Preview from '../components/Preview';
import SidePanel from '../components/Side-Panel';
import Output from '../components/Output';
2018-04-06 14:51:52 +01:00
import CompletionModal from '../components/CompletionModal';
import HelpModal from '../components/HelpModal';
import VideoModal from '../components/VideoModal';
import ResetModal from '../components/ResetModal';
2018-04-06 14:51:52 +01:00
import { randomCompliment } from '../utils/get-words';
import { createGuideUrl } from '../utils';
import { challengeTypes } from '../../../../utils/challengeTypes';
import { ChallengeNode } from '../../../redux/propTypes';
import { dasherize } from '../../../../utils';
2018-04-06 14:51:52 +01:00
import {
createFiles,
challengeFilesSelector,
challengeTestsSelector,
2018-04-06 14:51:52 +01:00
initTests,
updateChallengeMeta,
challengeMounted,
updateSuccessMessage,
consoleOutputSelector
} from '../redux';
2018-04-06 14:51:52 +01:00
import './classic.css';
import decodeHTMLEntities from '../../../../utils/decodeHTMLEntities';
const mapStateToProps = createSelector(
challengeFilesSelector,
challengeTestsSelector,
consoleOutputSelector,
(files, tests, output) => ({
files,
tests,
output
})
);
2018-04-06 14:51:52 +01:00
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
createFiles,
initTests,
updateChallengeMeta,
challengeMounted,
updateSuccessMessage
},
dispatch
);
2018-04-06 14:51:52 +01:00
const propTypes = {
challengeMounted: PropTypes.func.isRequired,
2018-04-06 14:51:52 +01:00
createFiles: PropTypes.func.isRequired,
data: PropTypes.shape({
challengeNode: ChallengeNode
}),
files: PropTypes.shape({
key: PropTypes.string
}),
initTests: PropTypes.func.isRequired,
output: PropTypes.string,
2018-09-11 16:19:11 +03:00
pageContext: PropTypes.shape({
2018-04-06 14:51:52 +01:00
challengeMeta: PropTypes.shape({
nextchallengePath: PropTypes.string
})
}),
tests: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
testString: PropTypes.string
})
),
updateChallengeMeta: PropTypes.func.isRequired,
updateSuccessMessage: PropTypes.func.isRequired
2018-04-06 14:51:52 +01:00
};
class ShowClassic extends PureComponent {
constructor() {
super();
this.resizeProps = {
onStopResize: this.onStopResize.bind(this),
onResize: this.onResize.bind(this)
};
this.state = {
resizing: false
};
}
onResize() {
this.setState({ resizing: true });
}
onStopResize() {
this.setState({ resizing: false });
}
2018-04-06 14:51:52 +01:00
componentDidMount() {
const {
challengeMounted,
2018-04-06 14:51:52 +01:00
createFiles,
initTests,
updateChallengeMeta,
updateSuccessMessage,
2018-05-24 19:45:38 +01:00
data: {
challengeNode: { files, title, fields: { tests }, challengeType }
},
2018-09-11 16:19:11 +03:00
pageContext: { challengeMeta }
2018-04-06 14:51:52 +01:00
} = this.props;
createFiles(files);
initTests(tests);
2018-05-24 19:45:38 +01:00
updateChallengeMeta({ ...challengeMeta, title, challengeType });
updateSuccessMessage(randomCompliment());
challengeMounted(challengeMeta.id);
2018-04-06 14:51:52 +01:00
}
2018-04-06 15:45:49 +01:00
componentDidUpdate(prevProps) {
const { data: { challengeNode: { title: prevTitle } } } = prevProps;
const {
challengeMounted,
2018-04-06 15:45:49 +01:00
createFiles,
initTests,
updateChallengeMeta,
updateSuccessMessage,
2018-04-06 15:45:49 +01:00
data: {
2018-05-24 19:45:38 +01:00
challengeNode: {
files,
title: currentTitle,
fields: { tests },
challengeType
}
2018-04-06 15:45:49 +01:00
},
2018-09-11 16:19:11 +03:00
pageContext: { challengeMeta }
2018-04-06 15:45:49 +01:00
} = this.props;
if (prevTitle !== currentTitle) {
updateSuccessMessage(randomCompliment());
2018-04-06 15:45:49 +01:00
createFiles(files);
initTests(tests);
2018-05-24 19:45:38 +01:00
updateChallengeMeta({
...challengeMeta,
title: currentTitle,
challengeType
});
challengeMounted(challengeMeta.id);
2018-04-06 15:45:49 +01:00
}
}
2018-04-06 14:51:52 +01:00
render() {
const {
data: {
challengeNode: {
challengeType,
fields: { blockName, slug },
2018-04-06 14:51:52 +01:00
title,
description,
videoUrl
2018-04-06 14:51:52 +01:00
}
},
files,
output
2018-04-06 14:51:52 +01:00
} = this.props;
const editors = Object.keys(files)
.map(key => files[key])
.map((file, index) => (
2018-05-24 19:45:38 +01:00
<ReflexContainer key={file.key + index} orientation='horizontal'>
{index !== 0 && (
<ReflexSplitter propagate={true} {...this.resizeProps} />
)}
2018-05-24 19:45:38 +01:00
<ReflexElement
flex={1}
propagateDimensions={true}
renderOnResize={true}
renderOnResizeRate={20}
{...this.resizeProps}
2018-05-24 19:45:38 +01:00
>
2018-04-06 14:51:52 +01:00
<Editor {...file} fileKey={file.key} />
</ReflexElement>
2018-05-24 19:45:38 +01:00
{index + 1 === Object.keys(files).length && (
<ReflexSplitter propagate={true} {...this.resizeProps} />
2018-05-24 19:45:38 +01:00
)}
{index + 1 === Object.keys(files).length ? (
2018-05-24 19:45:38 +01:00
<ReflexElement
flex={0.25}
propagateDimensions={true}
renderOnResize={true}
renderOnResizeRate={20}
{...this.resizeProps}
2018-05-24 19:45:38 +01:00
>
<Output
defaultOutput={`
/**
* Your test output will go here.
*/
`}
output={decodeHTMLEntities(output)}
/>
</ReflexElement>
) : null}
</ReflexContainer>
2018-04-06 14:51:52 +01:00
));
2018-04-11 14:51:47 +01:00
const showPreview =
2018-04-11 14:43:23 +01:00
challengeType === challengeTypes.html ||
2018-04-11 14:51:47 +01:00
challengeType === challengeTypes.modern;
2018-06-07 23:11:28 +01:00
const blockNameTitle = `${blockName}: ${title}`;
2018-04-06 14:51:52 +01:00
return (
<Fragment>
<Helmet title={`${blockNameTitle} | Learn freeCodeCamp`} />
2018-04-06 14:51:52 +01:00
<ReflexContainer orientation='vertical'>
<ReflexElement flex={1} {...this.resizeProps}>
2018-04-06 14:51:52 +01:00
<SidePanel
className='full-height'
description={description}
guideUrl={createGuideUrl(slug)}
section={dasherize(blockName)}
2018-04-06 14:51:52 +01:00
title={blockNameTitle}
videoUrl={videoUrl}
2018-04-06 14:51:52 +01:00
/>
</ReflexElement>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement flex={1} {...this.resizeProps}>
{editors}
</ReflexElement>
{showPreview && (
<ReflexSplitter propagate={true} {...this.resizeProps} />
)}
{showPreview ? (
<ReflexElement flex={0.7} {...this.resizeProps}>
<Preview
className='full-height'
disableIframe={this.state.resizing}
/>
</ReflexElement>
) : null}
2018-04-06 14:51:52 +01:00
</ReflexContainer>
2018-04-06 14:51:52 +01:00
<CompletionModal />
<HelpModal />
<VideoModal videoUrl={videoUrl}/>
<ResetModal />
2018-04-06 14:51:52 +01:00
</Fragment>
);
}
}
ShowClassic.displayName = 'ShowClassic';
ShowClassic.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(ShowClassic);
export const query = graphql`
query ClassicChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
title
description
challengeType
videoUrl
2018-04-06 14:51:52 +01:00
fields {
slug
2018-04-06 14:51:52 +01:00
blockName
tests {
text
testString
}
}
2018-04-11 14:43:23 +01:00
required {
link
raw
src
}
2018-04-06 14:51:52 +01:00
files {
indexhtml {
key
ext
name
contents
head
tail
}
indexjs {
key
ext
name
contents
head
tail
}
2018-04-11 14:43:23 +01:00
indexjsx {
key
ext
name
contents
head
tail
}
2018-04-06 14:51:52 +01:00
}
}
}
`;