fix(learn): created components for mobile/desktop layouts (#17467)
This commit is contained in:
committed by
Stuart Taylor
parent
d4b07f47ab
commit
8a60b19b6c
78
client/src/templates/Challenges/classic/DesktopLayout.js
Normal file
78
client/src/templates/Challenges/classic/DesktopLayout.js
Normal file
@ -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 (
|
||||
<ReflexContainer orientation='vertical'>
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{instructions}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
<ReflexElement
|
||||
flex={1}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...resizeProps}
|
||||
>
|
||||
{editor}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement
|
||||
flex={0.25}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...resizeProps}
|
||||
>
|
||||
{testOutput}
|
||||
</ReflexElement>
|
||||
</ReflexContainer>
|
||||
)}
|
||||
</ReflexElement>
|
||||
{hasPreview && (
|
||||
<Fragment>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={0.7} {...resizeProps}>
|
||||
{preview}
|
||||
</ReflexElement>
|
||||
</Fragment>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DesktopLayout.displayName = 'DesktopLayout';
|
||||
DesktopLayout.propTypes = propTypes;
|
||||
|
||||
export default DesktopLayout;
|
98
client/src/templates/Challenges/classic/MobileLayout.js
Normal file
98
client/src/templates/Challenges/classic/MobileLayout.js
Normal file
@ -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 (
|
||||
<Fragment>
|
||||
<Tabs
|
||||
activeKey={currentTab}
|
||||
defaultActiveKey={1}
|
||||
id='challege-page-tabs'
|
||||
onSelect={(key) => moveToTab(key)}
|
||||
>
|
||||
<TabPane eventKey={1} title='Instructions'>
|
||||
{ instructions }
|
||||
</TabPane>
|
||||
<TabPane eventKey={2} title='Code' {...editorTabPaneProps}>
|
||||
<div className='challege-edittor-wrapper'>
|
||||
{editor}
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane eventKey={3} title='Tests' {...editorTabPaneProps}>
|
||||
<div className='challege-edittor-wrapper'>
|
||||
{testOutput}
|
||||
</div>
|
||||
</TabPane>
|
||||
{hasPreview && (
|
||||
<TabPane eventKey={4} title='Preview'>
|
||||
{preview}
|
||||
</TabPane>
|
||||
)}
|
||||
</Tabs>
|
||||
<ToolPanel
|
||||
guideUrl={guideUrl}
|
||||
isMobile={true}
|
||||
videoUrl={videoUrl}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MobileLayout.displayName = 'MobileLayout';
|
||||
MobileLayout.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MobileLayout);
|
@ -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 (
|
||||
<SidePanel
|
||||
className='full-height'
|
||||
@ -242,7 +213,7 @@ class ShowClassic extends Component {
|
||||
section={dasherize(blockName)}
|
||||
showToolPanel={showToolPanel}
|
||||
title={this.getBlockNameTitle()}
|
||||
videoUrl={videoUrl}
|
||||
videoUrl={this.getVideoUrl()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -278,112 +249,7 @@ class ShowClassic extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderDesktopLayout() {
|
||||
const challengeFile = this.getChallengeFile();
|
||||
return (
|
||||
<ReflexContainer orientation='vertical'>
|
||||
<ReflexElement flex={1} {...this.resizeProps}>
|
||||
{this.renderInstructionsPanel({ showToolPanel: true })}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
<ReflexElement flex={1} {...this.resizeProps}>
|
||||
{challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
<ReflexElement
|
||||
flex={1}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...this.resizeProps}
|
||||
>
|
||||
{this.renderEditor()}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
<ReflexElement
|
||||
flex={0.25}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...this.resizeProps}
|
||||
>
|
||||
{this.renderTestOutput()}
|
||||
</ReflexElement>
|
||||
</ReflexContainer>
|
||||
)}
|
||||
</ReflexElement>
|
||||
{this.hasPreview() && (
|
||||
<Fragment>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
<ReflexElement flex={0.7} {...this.resizeProps}>
|
||||
{this.renderPreview()}
|
||||
</ReflexElement>
|
||||
</Fragment>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
);
|
||||
}
|
||||
|
||||
renderMobileLayout() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
videoUrl
|
||||
}
|
||||
},
|
||||
currentTab,
|
||||
moveToTab
|
||||
} = this.props;
|
||||
|
||||
const editorTabPaneProps = {
|
||||
mountOnEnter: true,
|
||||
unmountOnExit: true
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Tabs
|
||||
activeKey={currentTab}
|
||||
defaultActiveKey={1}
|
||||
id='challege-page-tabs'
|
||||
onSelect={(key) => moveToTab(key)}
|
||||
>
|
||||
<TabPane eventKey={1} title='Instructions'>
|
||||
{ this.renderInstructionsPanel({ showToolPanel: false }) }
|
||||
</TabPane>
|
||||
<TabPane eventKey={2} title='Code' {...editorTabPaneProps}>
|
||||
<div className='challege-edittor-wrapper'>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane eventKey={3} title='Tests' {...editorTabPaneProps}>
|
||||
<div className='challege-edittor-wrapper'>
|
||||
{this.renderTestOutput()}
|
||||
</div>
|
||||
</TabPane>
|
||||
{this.hasPreview() && (
|
||||
<TabPane eventKey={4} title='Preview'>
|
||||
{this.renderPreview()}
|
||||
</TabPane>
|
||||
)}
|
||||
</Tabs>
|
||||
<ToolPanel
|
||||
guideUrl={this.getGuideUrl()}
|
||||
isMobile={true}
|
||||
videoUrl={videoUrl}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
videoUrl
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<LearnLayout>
|
||||
<Helmet
|
||||
@ -392,13 +258,33 @@ class ShowClassic extends Component {
|
||||
<Media query={{ maxWidth: MAX_MOBILE_WIDTH }}>
|
||||
{matches =>
|
||||
matches
|
||||
? this.renderMobileLayout()
|
||||
: this.renderDesktopLayout()
|
||||
? (
|
||||
<MobileLayout
|
||||
instructions={this.renderInstructionsPanel({ showToolPanel: false })}
|
||||
editor={this.renderEditor()}
|
||||
testOutput={this.renderTestOutput()}
|
||||
hasPreview={this.hasPreview()}
|
||||
preview={this.renderPreview()}
|
||||
guideUrl={this.getGuideUrl()}
|
||||
videoUrl={this.getVideoUrl()}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<DesktopLayout
|
||||
instructions={this.renderInstructionsPanel({ showToolPanel: true })}
|
||||
editor={this.renderEditor()}
|
||||
testOutput={this.renderTestOutput()}
|
||||
hasPreview={this.hasPreview()}
|
||||
preview={this.renderPreview()}
|
||||
resizeProps={this.resizeProps}
|
||||
challengeFile={this.getChallengeFile()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Media>
|
||||
<CompletionModal />
|
||||
<HelpModal />
|
||||
<VideoModal videoUrl={videoUrl} />
|
||||
<VideoModal videoUrl={this.getVideoUrl()} />
|
||||
<ResetModal />
|
||||
</LearnLayout>
|
||||
);
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user