diff --git a/client/src/templates/Challenges/classic/DesktopLayout.js b/client/src/templates/Challenges/classic/DesktopLayout.js new file mode 100644 index 0000000000..51f583e480 --- /dev/null +++ b/client/src/templates/Challenges/classic/DesktopLayout.js @@ -0,0 +1,78 @@ +import React, { Component, Fragment } from 'react'; +import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; +import PropTypes from 'prop-types'; + +const propTypes = { + resizeProps: PropTypes.shape({ + onStopResize: PropTypes.func, + onResize: PropTypes.func + }), + instructions: PropTypes.element, + challengeFile: PropTypes.shape({ + key: PropTypes.string + }), + editor: PropTypes.element, + testOutput: PropTypes.element, + hasPreview: PropTypes.bool, + preview: PropTypes.element +}; + +class DesktopLayout extends Component { + render() { + const { + resizeProps, + instructions, + challengeFile, + editor, + testOutput, + hasPreview, + preview + } = this.props; + return ( + + + {instructions} + + + + {challengeFile && ( + + + {editor} + + + + {testOutput} + + + )} + + {hasPreview && ( + + + + {preview} + + + )} + + ); + } +} + +DesktopLayout.displayName = 'DesktopLayout'; +DesktopLayout.propTypes = propTypes; + +export default DesktopLayout; diff --git a/client/src/templates/Challenges/classic/MobileLayout.js b/client/src/templates/Challenges/classic/MobileLayout.js new file mode 100644 index 0000000000..65f41f9cdc --- /dev/null +++ b/client/src/templates/Challenges/classic/MobileLayout.js @@ -0,0 +1,98 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { TabPane, Tabs } from '@freecodecamp/react-bootstrap'; +import { connect } from 'react-redux'; + +import ToolPanel from '../components/Tool-Panel'; +import { createStructuredSelector } from 'reselect'; +import { + currentTabSelector, + moveToTab, +} from '../redux'; +import { bindActionCreators } from 'redux'; + +const mapStateToProps = createStructuredSelector({ + currentTab: currentTabSelector +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + moveToTab, + }, + dispatch + ); + +const propTypes = { + moveToTab: PropTypes.func, + currentTab: PropTypes.number, + instructions: PropTypes.element, + editor: PropTypes.element, + testOutput: PropTypes.element, + hasPreview: PropTypes.bool, + preview: PropTypes.element, + guideUrl: PropTypes.string, + videoUrl: PropTypes.string +}; + + +class MobileLayout extends Component { + render() { + const { + currentTab, + moveToTab, + instructions, + editor, + testOutput, + hasPreview, + preview, + guideUrl, + videoUrl + } = this.props; + + const editorTabPaneProps = { + mountOnEnter: true, + unmountOnExit: true + }; + + return ( + + moveToTab(key)} + > + + { instructions } + + + + {editor} + + + + + {testOutput} + + + {hasPreview && ( + + {preview} + + )} + + + + ); + } +} + +MobileLayout.displayName = 'MobileLayout'; +MobileLayout.propTypes = propTypes; + +export default connect(mapStateToProps, mapDispatchToProps)(MobileLayout); diff --git a/client/src/templates/Challenges/classic/Show.js b/client/src/templates/Challenges/classic/Show.js index 3aa39fa723..e1a3255ac3 100644 --- a/client/src/templates/Challenges/classic/Show.js +++ b/client/src/templates/Challenges/classic/Show.js @@ -1,13 +1,11 @@ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; -import { createSelector } from 'reselect'; +import { createStructuredSelector } from 'reselect'; import { connect } from 'react-redux'; import Helmet from 'react-helmet'; -import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { graphql } from 'gatsby'; import { first } from 'lodash'; -import { Tabs, TabPane } from '@freecodecamp/react-bootstrap'; import Media from 'react-media'; import LearnLayout from '../../../components/layouts/Learn'; @@ -19,7 +17,8 @@ import CompletionModal from '../components/CompletionModal'; import HelpModal from '../components/HelpModal'; import VideoModal from '../components/VideoModal'; import ResetModal from '../components/ResetModal'; -import ToolPanel from '../components/Tool-Panel'; +import MobileLayout from './MobileLayout'; +import DesktopLayout from './DesktopLayout'; import { randomCompliment } from '../utils/get-words'; import { createGuideUrl } from '../utils'; @@ -30,13 +29,11 @@ import { createFiles, challengeFilesSelector, challengeTestsSelector, - currentTabSelector, initTests, updateChallengeMeta, challengeMounted, updateSuccessMessage, - consoleOutputSelector, - moveToTab + consoleOutputSelector } from '../redux'; import './classic.css'; @@ -44,23 +41,15 @@ import '../components/test-frame.css'; import decodeHTMLEntities from '../../../../utils/decodeHTMLEntities'; -const mapStateToProps = createSelector( - challengeFilesSelector, - challengeTestsSelector, - consoleOutputSelector, - currentTabSelector, - (files, tests, output, currentTab) => ({ - files, - tests, - output, - currentTab - }) -); +const mapStateToProps = createStructuredSelector({ + files: challengeFilesSelector, + tests: challengeTestsSelector, + output: consoleOutputSelector +}); const mapDispatchToProps = dispatch => bindActionCreators( { - moveToTab, createFiles, initTests, updateChallengeMeta, @@ -73,7 +62,6 @@ const mapDispatchToProps = dispatch => const propTypes = { challengeMounted: PropTypes.func.isRequired, createFiles: PropTypes.func.isRequired, - currentTab: PropTypes.number, data: PropTypes.shape({ challengeNode: ChallengeNode }), @@ -81,7 +69,6 @@ const propTypes = { key: PropTypes.string }), initTests: PropTypes.func.isRequired, - moveToTab: PropTypes.func.isRequired, output: PropTypes.string, pageContext: PropTypes.shape({ challengeMeta: PropTypes.shape({ @@ -180,42 +167,30 @@ class ShowClassic extends Component { } } + getChallenge = () => this.props.data.challengeNode; + getBlockNameTitle() { const { - data: { - challengeNode: { - fields: { blockName }, - title - } - } - } = this.props; + fields: { blockName }, + title + } = this.getChallenge(); return `${blockName}: ${title}`; } getGuideUrl() { - const { - data: { - challengeNode: { - fields: { slug } - } - } - } = this.props; + 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 { - data: { - challengeNode: { - challengeType - } - } - } = this.props; + const { challengeType } = this.getChallenge(); return ( challengeType === challengeTypes.html || challengeType === challengeTypes.modern @@ -224,15 +199,11 @@ class ShowClassic extends Component { renderInstructionsPanel({ showToolPanel }) { const { - data: { - challengeNode: { - fields: { blockName }, - description, - instructions, - videoUrl - } - } - } = this.props; + fields: { blockName }, + description, + instructions, + } = this.getChallenge(); + return ( ); } @@ -278,112 +249,7 @@ class ShowClassic extends Component { ); } - renderDesktopLayout() { - const challengeFile = this.getChallengeFile(); - return ( - - - {this.renderInstructionsPanel({ showToolPanel: true })} - - - - {challengeFile && ( - - - {this.renderEditor()} - - - - {this.renderTestOutput()} - - - )} - - {this.hasPreview() && ( - - - - {this.renderPreview()} - - - )} - - ); - } - - renderMobileLayout() { - const { - data: { - challengeNode: { - videoUrl - } - }, - currentTab, - moveToTab - } = this.props; - - const editorTabPaneProps = { - mountOnEnter: true, - unmountOnExit: true - }; - - return ( - - moveToTab(key)} - > - - { this.renderInstructionsPanel({ showToolPanel: false }) } - - - - {this.renderEditor()} - - - - - {this.renderTestOutput()} - - - {this.hasPreview() && ( - - {this.renderPreview()} - - )} - - - - ); - } - render() { - const { - data: { - challengeNode: { - videoUrl - } - } - } = this.props; - return ( {matches => matches - ? this.renderMobileLayout() - : this.renderDesktopLayout() + ? ( + + ) + : ( + + ) } - + ); diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index 46abdd15c1..15a267b04b 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -146,7 +146,7 @@ export const noStoredCodeFound = createAction(types.noStoredCodeFound); export const closeModal = createAction(types.closeModal); export const openModal = createAction(types.openModal); -export const previewMounted = createAction(types.challengeMounted); +export const previewMounted = createAction(types.previewMounted); export const challengeMounted = createAction(types.challengeMounted); export const checkChallenge = createAction(types.checkChallenge); export const executeChallenge = createAction(types.executeChallenge);