fix(learn): created components for mobile/desktop layouts (#17467)

This commit is contained in:
Ivan Nikolaievskyi
2018-12-11 23:05:15 +02:00
committed by Stuart Taylor
parent d4b07f47ab
commit 8a60b19b6c
4 changed files with 225 additions and 163 deletions

View 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;

View 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);

View File

@ -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>
);

View File

@ -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);