Files
freeCodeCamp/client/src/templates/Challenges/classic/Show.js

362 lines
8.3 KiB
JavaScript
Raw Normal View History

import React, { Component } from 'react';
2018-04-06 14:51:52 +01:00
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { createStructuredSelector } from 'reselect';
2018-04-06 14:51:52 +01:00
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
2018-09-11 16:10:21 +03:00
import { graphql } from 'gatsby';
import { first } from 'lodash';
import Media from 'react-media';
2018-04-06 14:51:52 +01:00
import LearnLayout from '../../../components/layouts/Learn';
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';
import MobileLayout from './MobileLayout';
import DesktopLayout from './DesktopLayout';
import ToolPanel from '../components/Tool-Panel';
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';
2018-09-21 08:33:19 +03:00
import '../components/test-frame.css';
2018-04-06 14:51:52 +01:00
const mapStateToProps = createStructuredSelector({
files: challengeFilesSelector,
tests: challengeTestsSelector,
output: consoleOutputSelector
});
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({
2019-01-23 12:24:05 +03:00
nextChallengePath: PropTypes.string
2018-04-06 14:51:52 +01:00
})
}),
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
};
const MAX_MOBILE_WIDTH = 767;
class ShowClassic extends Component {
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-05-24 19:45:38 +01:00
},
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;
2018-04-06 15:45:49 +01:00
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
}
}
getChallenge = () => this.props.data.challengeNode;
getBlockNameTitle() {
2018-04-06 14:51:52 +01:00
const {
fields: { blockName },
title
} = this.getChallenge();
return `${blockName}: ${title}`;
}
getGuideUrl() {
2019-01-23 12:24:05 +03:00
const {
fields: { slug }
} = this.getChallenge();
return createGuideUrl(slug);
}
getVideoUrl = () => this.getChallenge().videoUrl;
getChallengeFile() {
const { files } = this.props;
return first(Object.keys(files).map(key => files[key]));
}
hasPreview() {
const { challengeType } = this.getChallenge();
return (
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern
);
}
renderInstructionsPanel({ showToolPanel }) {
const {
fields: { blockName },
description,
2019-01-23 12:24:05 +03:00
instructions
} = this.getChallenge();
return (
<SidePanel
className='full-height'
description={description}
guideUrl={this.getGuideUrl()}
instructions={instructions}
section={dasherize(blockName)}
showToolPanel={showToolPanel}
title={this.getBlockNameTitle()}
videoUrl={this.getVideoUrl()}
/>
);
}
renderEditor() {
const { files } = this.props;
const challengeFile = first(Object.keys(files).map(key => files[key]));
2019-01-23 12:24:05 +03:00
return (
challengeFile && <Editor {...challengeFile} fileKey={challengeFile.key} />
);
}
renderTestOutput() {
const { output } = this.props;
return (
<Output
2019-01-23 12:24:05 +03:00
defaultOutput={`
/**
* Your test output will go here.
*/
`}
output={output}
/>
);
}
renderPreview() {
2018-04-06 14:51:52 +01:00
return (
2019-01-23 12:24:05 +03:00
<Preview className='full-height' disableIframe={this.state.resizing} />
);
}
renderToolPanel(isMobile) {
return (
<ToolPanel
className='classic-tool-panel'
guideUrl={this.getGuideUrl()}
isMobile={isMobile}
videoUrl={this.getVideoUrl()}
/>
);
}
render() {
return (
<LearnLayout>
<Helmet
title={`Learn ${this.getBlockNameTitle()} | freeCodeCamp.org`}
/>
2019-01-23 12:24:05 +03:00
<Media defaultMatches={false} query={{ maxWidth: MAX_MOBILE_WIDTH }}>
{matches =>
2019-01-23 12:24:05 +03:00
matches ? (
<MobileLayout
editor={this.renderEditor()}
hasPreview={this.hasPreview()}
instructions={this.renderInstructionsPanel({
showToolPanel: false
})}
preview={this.renderPreview()}
testOutput={this.renderTestOutput()}
toolPanel={this.renderToolPanel(true)}
2019-01-23 12:24:05 +03:00
/>
) : (
<DesktopLayout
challengeFile={this.getChallengeFile()}
editor={this.renderEditor()}
hasPreview={this.hasPreview()}
instructions={this.renderInstructionsPanel({
showToolPanel: false
2019-01-23 12:24:05 +03:00
})}
preview={this.renderPreview()}
resizeProps={this.resizeProps}
testOutput={this.renderTestOutput()}
toolPanel={this.renderToolPanel(false)}
2019-01-23 12:24:05 +03:00
/>
)
}
</Media>
2018-04-06 14:51:52 +01:00
<CompletionModal />
<HelpModal />
<VideoModal videoUrl={this.getVideoUrl()} />
<ResetModal />
</LearnLayout>
2018-04-06 14:51:52 +01:00
);
}
}
ShowClassic.displayName = 'ShowClassic';
ShowClassic.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShowClassic);
2018-04-06 14:51:52 +01:00
export const query = graphql`
query ClassicChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
title
description
instructions
2018-04-06 14:51:52 +01:00
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
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
}
}
}
`;