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';
|
2018-05-18 14:54:21 +01:00
|
|
|
import Output from '../components/Output';
|
2018-04-06 14:51:52 +01:00
|
|
|
import CompletionModal from '../components/CompletionModal';
|
2018-05-08 00:29:45 +01:00
|
|
|
import HelpModal from '../components/HelpModal';
|
2018-08-06 09:25:50 -04:00
|
|
|
import VideoModal from '../components/VideoModal';
|
2018-05-09 12:40:09 +01:00
|
|
|
import ResetModal from '../components/ResetModal';
|
2018-04-06 14:51:52 +01:00
|
|
|
|
2018-05-10 23:25:31 +01:00
|
|
|
import { randomCompliment } from '../utils/get-words';
|
2018-06-28 18:53:47 +01:00
|
|
|
import { createGuideUrl } from '../utils';
|
2018-04-08 23:19:50 +01:00
|
|
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
|
|
|
import { ChallengeNode } from '../../../redux/propTypes';
|
2018-07-22 11:29:17 +09:00
|
|
|
import { dasherize } from '../../../../utils';
|
2018-04-06 14:51:52 +01:00
|
|
|
import {
|
|
|
|
createFiles,
|
|
|
|
challengeFilesSelector,
|
2018-05-18 14:54:21 +01:00
|
|
|
challengeTestsSelector,
|
2018-04-06 14:51:52 +01:00
|
|
|
initTests,
|
2018-05-09 12:40:09 +01:00
|
|
|
updateChallengeMeta,
|
2018-05-10 23:25:31 +01:00
|
|
|
challengeMounted,
|
2018-05-18 14:54:21 +01:00
|
|
|
updateSuccessMessage,
|
|
|
|
consoleOutputSelector
|
2018-04-08 23:19:50 +01:00
|
|
|
} from '../redux';
|
2018-04-06 14:51:52 +01:00
|
|
|
|
|
|
|
import './classic.css';
|
|
|
|
|
2018-06-18 10:45:22 -03:00
|
|
|
import decodeHTMLEntities from '../../../../utils/decodeHTMLEntities';
|
|
|
|
|
2018-05-18 14:54:21 +01:00
|
|
|
const mapStateToProps = createSelector(
|
|
|
|
challengeFilesSelector,
|
|
|
|
challengeTestsSelector,
|
|
|
|
consoleOutputSelector,
|
|
|
|
(files, tests, output) => ({
|
|
|
|
files,
|
|
|
|
tests,
|
|
|
|
output
|
|
|
|
})
|
|
|
|
);
|
2018-04-06 14:51:52 +01:00
|
|
|
|
|
|
|
const mapDispatchToProps = dispatch =>
|
2018-05-09 12:40:09 +01:00
|
|
|
bindActionCreators(
|
2018-05-10 23:25:31 +01:00
|
|
|
{
|
|
|
|
createFiles,
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta,
|
|
|
|
challengeMounted,
|
|
|
|
updateSuccessMessage
|
|
|
|
},
|
2018-05-09 12:40:09 +01:00
|
|
|
dispatch
|
|
|
|
);
|
2018-04-06 14:51:52 +01:00
|
|
|
|
|
|
|
const propTypes = {
|
2018-05-09 12:40:09 +01:00
|
|
|
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,
|
2018-05-18 14:54:21 +01:00
|
|
|
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
|
|
|
|
})
|
|
|
|
}),
|
2018-05-18 14:54:21 +01:00
|
|
|
tests: PropTypes.arrayOf(
|
|
|
|
PropTypes.shape({
|
|
|
|
text: PropTypes.string,
|
|
|
|
testString: PropTypes.string
|
|
|
|
})
|
|
|
|
),
|
2018-05-10 23:25:31 +01:00
|
|
|
updateChallengeMeta: PropTypes.func.isRequired,
|
|
|
|
updateSuccessMessage: PropTypes.func.isRequired
|
2018-04-06 14:51:52 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
class ShowClassic extends PureComponent {
|
2018-05-27 09:37:46 +01:00
|
|
|
constructor() {
|
2018-05-29 15:34:50 +01:00
|
|
|
super();
|
2018-05-27 09:37:46 +01:00
|
|
|
|
|
|
|
this.resizeProps = {
|
|
|
|
onStopResize: this.onStopResize.bind(this),
|
|
|
|
onResize: this.onResize.bind(this)
|
2018-05-29 15:34:50 +01:00
|
|
|
};
|
2018-05-27 09:37:46 +01:00
|
|
|
|
|
|
|
this.state = {
|
|
|
|
resizing: false
|
2018-05-29 15:34:50 +01:00
|
|
|
};
|
2018-05-27 09:37:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
onResize() {
|
|
|
|
this.setState({ resizing: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
onStopResize() {
|
|
|
|
this.setState({ resizing: false });
|
|
|
|
}
|
|
|
|
|
2018-04-06 14:51:52 +01:00
|
|
|
componentDidMount() {
|
|
|
|
const {
|
2018-05-09 12:40:09 +01:00
|
|
|
challengeMounted,
|
2018-04-06 14:51:52 +01:00
|
|
|
createFiles,
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta,
|
2018-05-10 23:25:31 +01:00
|
|
|
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 });
|
2018-05-10 23:25:31 +01:00
|
|
|
updateSuccessMessage(randomCompliment());
|
2018-05-09 12:40:09 +01:00
|
|
|
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 {
|
2018-05-09 12:40:09 +01:00
|
|
|
challengeMounted,
|
2018-04-06 15:45:49 +01:00
|
|
|
createFiles,
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta,
|
2018-05-10 23:25:31 +01:00
|
|
|
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) {
|
2018-05-18 19:07:32 +01:00
|
|
|
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
|
|
|
|
});
|
2018-05-09 12:40:09 +01:00
|
|
|
challengeMounted(challengeMeta.id);
|
2018-04-06 15:45:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-06 14:51:52 +01:00
|
|
|
render() {
|
|
|
|
const {
|
|
|
|
data: {
|
|
|
|
challengeNode: {
|
|
|
|
challengeType,
|
2018-06-28 18:53:47 +01:00
|
|
|
fields: { blockName, slug },
|
2018-04-06 14:51:52 +01:00
|
|
|
title,
|
2018-08-06 09:25:50 -04:00
|
|
|
description,
|
|
|
|
videoUrl
|
2018-04-06 14:51:52 +01:00
|
|
|
}
|
|
|
|
},
|
2018-05-18 14:54:21 +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'>
|
2018-05-29 15:34:50 +01:00
|
|
|
{index !== 0 && (
|
|
|
|
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
|
|
|
)}
|
2018-05-24 19:45:38 +01:00
|
|
|
<ReflexElement
|
|
|
|
flex={1}
|
|
|
|
propagateDimensions={true}
|
|
|
|
renderOnResize={true}
|
|
|
|
renderOnResizeRate={20}
|
2018-05-27 09:37:46 +01:00
|
|
|
{...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 && (
|
2018-05-27 09:37:46 +01:00
|
|
|
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
2018-05-24 19:45:38 +01:00
|
|
|
)}
|
2018-05-18 14:54:21 +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}
|
2018-05-27 09:37:46 +01:00
|
|
|
{...this.resizeProps}
|
2018-05-24 19:45:38 +01:00
|
|
|
>
|
2018-05-18 14:54:21 +01:00
|
|
|
<Output
|
|
|
|
defaultOutput={`
|
|
|
|
/**
|
2018-06-01 03:36:42 +05:30
|
|
|
* Your test output will go here.
|
2018-05-18 14:54:21 +01:00
|
|
|
*/
|
|
|
|
`}
|
2018-06-18 10:45:22 -03:00
|
|
|
output={decodeHTMLEntities(output)}
|
2018-05-18 14:54:21 +01:00
|
|
|
/>
|
|
|
|
</ReflexElement>
|
|
|
|
) : null}
|
2018-05-21 21:49:01 +09:00
|
|
|
</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>
|
2018-05-18 14:54:21 +01:00
|
|
|
<Helmet title={`${blockNameTitle} | Learn freeCodeCamp`} />
|
2018-04-06 14:51:52 +01:00
|
|
|
<ReflexContainer orientation='vertical'>
|
2018-05-27 09:37:46 +01:00
|
|
|
<ReflexElement flex={1} {...this.resizeProps}>
|
2018-04-06 14:51:52 +01:00
|
|
|
<SidePanel
|
|
|
|
className='full-height'
|
|
|
|
description={description}
|
2018-06-28 18:53:47 +01:00
|
|
|
guideUrl={createGuideUrl(slug)}
|
2018-07-22 11:29:17 +09:00
|
|
|
section={dasherize(blockName)}
|
2018-04-06 14:51:52 +01:00
|
|
|
title={blockNameTitle}
|
2018-08-06 09:25:50 -04:00
|
|
|
videoUrl={videoUrl}
|
2018-04-06 14:51:52 +01:00
|
|
|
/>
|
|
|
|
</ReflexElement>
|
2018-05-27 09:37:46 +01:00
|
|
|
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
2018-05-29 15:34:50 +01:00
|
|
|
<ReflexElement flex={1} {...this.resizeProps}>
|
|
|
|
{editors}
|
|
|
|
</ReflexElement>
|
2018-06-28 18:53:47 +01:00
|
|
|
{showPreview && (
|
|
|
|
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
|
|
|
)}
|
2018-06-01 03:36:42 +05:30
|
|
|
{showPreview ? (
|
|
|
|
<ReflexElement flex={0.7} {...this.resizeProps}>
|
2018-05-29 15:34:50 +01:00
|
|
|
<Preview
|
|
|
|
className='full-height'
|
|
|
|
disableIframe={this.state.resizing}
|
|
|
|
/>
|
2018-06-01 03:36:42 +05:30
|
|
|
</ReflexElement>
|
|
|
|
) : null}
|
2018-04-06 14:51:52 +01:00
|
|
|
</ReflexContainer>
|
2018-05-18 14:54:21 +01:00
|
|
|
|
2018-04-06 14:51:52 +01:00
|
|
|
<CompletionModal />
|
2018-05-08 00:29:45 +01:00
|
|
|
<HelpModal />
|
2018-08-06 09:25:50 -04:00
|
|
|
<VideoModal videoUrl={videoUrl}/>
|
2018-05-09 12:40:09 +01:00
|
|
|
<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
|
2018-08-06 09:25:50 -04:00
|
|
|
videoUrl
|
2018-04-06 14:51:52 +01:00
|
|
|
fields {
|
2018-06-28 18:53:47 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|