fix(learn): implemented curriculum layout for mobile devices (#17467)
remove useless changes
This commit is contained in:
committed by
Stuart Taylor
parent
37d98f1123
commit
d4b07f47ab
@ -624,4 +624,4 @@ pre tt:after {
|
||||
}
|
||||
.default-layout {
|
||||
margin-top: 38px;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,13 @@
|
||||
padding: 5px 10px 5px 10px;
|
||||
}
|
||||
|
||||
@media screen, (max-width: 767px) {
|
||||
#learn-app-wrapper {
|
||||
right: -2em;
|
||||
padding: 2px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.reflex-layout.reflex-container.vertical {
|
||||
width: 100%;
|
||||
margin: 0 18px;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
@ -7,6 +7,8 @@ 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';
|
||||
import Editor from './Editor';
|
||||
@ -17,6 +19,7 @@ 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 { randomCompliment } from '../utils/get-words';
|
||||
import { createGuideUrl } from '../utils';
|
||||
@ -27,11 +30,13 @@ import {
|
||||
createFiles,
|
||||
challengeFilesSelector,
|
||||
challengeTestsSelector,
|
||||
currentTabSelector,
|
||||
initTests,
|
||||
updateChallengeMeta,
|
||||
challengeMounted,
|
||||
updateSuccessMessage,
|
||||
consoleOutputSelector
|
||||
consoleOutputSelector,
|
||||
moveToTab
|
||||
} from '../redux';
|
||||
|
||||
import './classic.css';
|
||||
@ -43,16 +48,19 @@ const mapStateToProps = createSelector(
|
||||
challengeFilesSelector,
|
||||
challengeTestsSelector,
|
||||
consoleOutputSelector,
|
||||
(files, tests, output) => ({
|
||||
currentTabSelector,
|
||||
(files, tests, output, currentTab) => ({
|
||||
files,
|
||||
tests,
|
||||
output
|
||||
output,
|
||||
currentTab
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
moveToTab,
|
||||
createFiles,
|
||||
initTests,
|
||||
updateChallengeMeta,
|
||||
@ -65,6 +73,7 @@ const mapDispatchToProps = dispatch =>
|
||||
const propTypes = {
|
||||
challengeMounted: PropTypes.func.isRequired,
|
||||
createFiles: PropTypes.func.isRequired,
|
||||
currentTab: PropTypes.number,
|
||||
data: PropTypes.shape({
|
||||
challengeNode: ChallengeNode
|
||||
}),
|
||||
@ -72,6 +81,7 @@ const propTypes = {
|
||||
key: PropTypes.string
|
||||
}),
|
||||
initTests: PropTypes.func.isRequired,
|
||||
moveToTab: PropTypes.func.isRequired,
|
||||
output: PropTypes.string,
|
||||
pageContext: PropTypes.shape({
|
||||
challengeMeta: PropTypes.shape({
|
||||
@ -88,6 +98,8 @@ const propTypes = {
|
||||
updateSuccessMessage: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const MAX_MOBILE_WIDTH = 767;
|
||||
|
||||
class ShowClassic extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
@ -101,7 +113,6 @@ class ShowClassic extends Component {
|
||||
resizing: false
|
||||
};
|
||||
}
|
||||
|
||||
onResize() {
|
||||
this.setState({ resizing: true });
|
||||
}
|
||||
@ -169,88 +180,222 @@ class ShowClassic extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
getBlockNameTitle() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challengeType,
|
||||
fields: { blockName, slug },
|
||||
title,
|
||||
fields: { blockName },
|
||||
title
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
return `${blockName}: ${title}`;
|
||||
}
|
||||
|
||||
getGuideUrl() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
fields: { slug }
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
return createGuideUrl(slug);
|
||||
}
|
||||
|
||||
getChallengeFile() {
|
||||
const { files } = this.props;
|
||||
return first(Object.keys(files).map(key => files[key]));
|
||||
}
|
||||
|
||||
hasPreview() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challengeType
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
return (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern
|
||||
);
|
||||
}
|
||||
|
||||
renderInstructionsPanel({ showToolPanel }) {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
fields: { blockName },
|
||||
description,
|
||||
instructions,
|
||||
videoUrl
|
||||
}
|
||||
},
|
||||
files,
|
||||
output
|
||||
}
|
||||
} = this.props;
|
||||
return (
|
||||
<SidePanel
|
||||
className='full-height'
|
||||
description={description}
|
||||
guideUrl={this.getGuideUrl()}
|
||||
instructions={instructions}
|
||||
section={dasherize(blockName)}
|
||||
showToolPanel={showToolPanel}
|
||||
title={this.getBlockNameTitle()}
|
||||
videoUrl={videoUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditor() {
|
||||
const { files } = this.props;
|
||||
const challengeFile = first(Object.keys(files).map(key => files[key]));
|
||||
const editors = challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
<ReflexElement
|
||||
flex={1}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...this.resizeProps}
|
||||
>
|
||||
<Editor {...challengeFile} fileKey={challengeFile.key} />
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
<ReflexElement
|
||||
flex={0.25}
|
||||
propagateDimensions={true}
|
||||
renderOnResize={true}
|
||||
renderOnResizeRate={20}
|
||||
{...this.resizeProps}
|
||||
>
|
||||
<Output
|
||||
return challengeFile && (
|
||||
<Editor {...challengeFile} fileKey={challengeFile.key} />
|
||||
);
|
||||
}
|
||||
|
||||
renderTestOutput() {
|
||||
const { output } = this.props;
|
||||
return (
|
||||
<Output
|
||||
defaultOutput={`
|
||||
/**
|
||||
* Your test output will go here.
|
||||
*/
|
||||
`}
|
||||
output={decodeHTMLEntities(output)}
|
||||
/>
|
||||
output={decodeHTMLEntities(output)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
return (
|
||||
<Preview
|
||||
className='full-height'
|
||||
disableIframe={this.state.resizing}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
const showPreview =
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern;
|
||||
const blockNameTitle = `${blockName}: ${title}`;
|
||||
}
|
||||
|
||||
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 title={`Learn ${blockNameTitle} | freeCodeCamp.org`} />
|
||||
<ReflexContainer orientation='vertical'>
|
||||
<ReflexElement flex={1} {...this.resizeProps}>
|
||||
<SidePanel
|
||||
className='full-height'
|
||||
description={description}
|
||||
guideUrl={createGuideUrl(slug)}
|
||||
instructions={instructions}
|
||||
section={dasherize(blockName)}
|
||||
title={blockNameTitle}
|
||||
videoUrl={videoUrl}
|
||||
/>
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
<ReflexElement flex={1} {...this.resizeProps}>
|
||||
{editors}
|
||||
</ReflexElement>
|
||||
{showPreview && (
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
)}
|
||||
{showPreview ? (
|
||||
<ReflexElement flex={0.7} {...this.resizeProps}>
|
||||
<Preview
|
||||
className='full-height'
|
||||
disableIframe={this.state.resizing}
|
||||
/>
|
||||
</ReflexElement>
|
||||
) : null}
|
||||
</ReflexContainer>
|
||||
|
||||
<Helmet
|
||||
title={`Learn ${this.getBlockNameTitle()} | freeCodeCamp.org`}
|
||||
/>
|
||||
<Media query={{ maxWidth: MAX_MOBILE_WIDTH }}>
|
||||
{matches =>
|
||||
matches
|
||||
? this.renderMobileLayout()
|
||||
: this.renderDesktopLayout()
|
||||
}
|
||||
</Media>
|
||||
<CompletionModal />
|
||||
<HelpModal />
|
||||
<VideoModal videoUrl={videoUrl} />
|
||||
|
@ -13,6 +13,12 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media screen, (max-width: 767px) {
|
||||
.instructions-panel {
|
||||
height: calc(100vh - 112px);
|
||||
}
|
||||
}
|
||||
|
||||
.react-monaco-editor-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -23,4 +29,23 @@
|
||||
.monaco-menu .action-label {
|
||||
color: #a2bd9b;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
#challege-page-tabs .nav-tabs {
|
||||
margin-left: 2px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#challege-page-tabs .nav-tabs > li {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#challege-page-tabs .nav-tabs > li > a {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.challege-edittor-wrapper {
|
||||
height: calc(100vh - 112px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ export class CompletionModal extends Component {
|
||||
bsStyle='primary'
|
||||
onClick={submitChallenge}
|
||||
>
|
||||
Submit and go to next challenge (Ctrl + Enter)
|
||||
Submit and go to next challenge <span className='hidden-xs'>(Ctrl + Enter)</span>
|
||||
</Button>
|
||||
{showDownloadButton ? (
|
||||
<Button
|
||||
|
@ -7,6 +7,8 @@ import { Button, Modal } from '@freecodecamp/react-bootstrap';
|
||||
import ga from '../../../analytics';
|
||||
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
||||
|
||||
import './help-modal.css';
|
||||
|
||||
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
@ -31,7 +33,7 @@ export class HelpModal extends Component {
|
||||
ga.modalview('/help-modal');
|
||||
}
|
||||
return (
|
||||
<Modal onHide={closeHelpModal} show={isOpen}>
|
||||
<Modal dialogClassName='help-modal' onHide={closeHelpModal} show={isOpen}>
|
||||
<Modal.Header
|
||||
className='help-modal-header fcc-modal'
|
||||
closeButton={true}
|
||||
|
@ -1,13 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { previewMounted } from '../redux';
|
||||
|
||||
import './preview.css';
|
||||
|
||||
const mainId = 'fcc-main-frame';
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
previewMounted
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
disableIframe: PropTypes.bool
|
||||
disableIframe: PropTypes.bool,
|
||||
previewMounted: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class Preview extends Component {
|
||||
@ -19,6 +32,10 @@ class Preview extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.previewMounted();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.disableIframe !== prevProps.disableIframe) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
@ -39,4 +56,7 @@ class Preview extends Component {
|
||||
Preview.displayName = 'Preview';
|
||||
Preview.propTypes = propTypes;
|
||||
|
||||
export default Preview;
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(Preview);
|
||||
|
@ -34,6 +34,7 @@ const propTypes = {
|
||||
initConsole: PropTypes.func.isRequired,
|
||||
instructions: PropTypes.string,
|
||||
section: PropTypes.string,
|
||||
showToolPanel: PropTypes.bool,
|
||||
tests: PropTypes.arrayOf(PropTypes.object),
|
||||
title: PropTypes.string,
|
||||
videoUrl: PropTypes.string
|
||||
@ -89,6 +90,7 @@ export class SidePanel extends Component {
|
||||
guideUrl,
|
||||
tests,
|
||||
section,
|
||||
showToolPanel,
|
||||
videoUrl
|
||||
} = this.props;
|
||||
return (
|
||||
@ -99,7 +101,9 @@ export class SidePanel extends Component {
|
||||
<ChallengeTitle>{title}</ChallengeTitle>
|
||||
<ChallengeDescription description={description} instructions={instructions} section={section} />
|
||||
</div>
|
||||
<ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />
|
||||
{ showToolPanel && (
|
||||
<ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />
|
||||
)}
|
||||
<TestSuite tests={tests} />
|
||||
</div>
|
||||
);
|
||||
|
@ -23,6 +23,7 @@ const mapDispatchToProps = dispatch =>
|
||||
const propTypes = {
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
guideUrl: PropTypes.string,
|
||||
isMobile: PropTypes.bool,
|
||||
openHelpModal: PropTypes.func.isRequired,
|
||||
openResetModal: PropTypes.func.isRequired,
|
||||
openVideoModal: PropTypes.func.isRequired,
|
||||
@ -31,6 +32,7 @@ const propTypes = {
|
||||
|
||||
function ToolPanel({
|
||||
executeChallenge,
|
||||
isMobile,
|
||||
openHelpModal,
|
||||
openVideoModal,
|
||||
openResetModal,
|
||||
@ -39,9 +41,11 @@ function ToolPanel({
|
||||
}) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='tool-panel-group'>
|
||||
<div className={`tool-panel-group ${
|
||||
isMobile ? 'tool-panel-group-mobile' : ''
|
||||
}`}>
|
||||
<Button block={true} bsStyle='primary' onClick={executeChallenge}>
|
||||
Run the Tests
|
||||
{isMobile ? 'Run' : 'Run the Tests'}
|
||||
</Button>
|
||||
<Button
|
||||
block={true}
|
||||
@ -49,7 +53,7 @@ function ToolPanel({
|
||||
className='btn-primary-invert'
|
||||
onClick={openResetModal}
|
||||
>
|
||||
Reset All Code
|
||||
{isMobile ? 'Reset' : 'Reset All Code'}
|
||||
</Button>
|
||||
{guideUrl ? (
|
||||
<Button
|
||||
@ -59,7 +63,7 @@ function ToolPanel({
|
||||
href={guideUrl}
|
||||
target='_blank'
|
||||
>
|
||||
Get a hint
|
||||
{isMobile ? 'Hint' : 'Get a hint'}
|
||||
</Button>
|
||||
) : null}
|
||||
{videoUrl ? (
|
||||
@ -69,7 +73,7 @@ function ToolPanel({
|
||||
className='btn-primary-invert'
|
||||
onClick={openVideoModal}
|
||||
>
|
||||
Watch a video
|
||||
{isMobile ? 'Video' : 'Watch a video'}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
@ -78,7 +82,7 @@ function ToolPanel({
|
||||
className='btn-primary-invert'
|
||||
onClick={openHelpModal}
|
||||
>
|
||||
Ask for help
|
||||
{isMobile ? 'Help' : 'Ask for help'}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
@ -9,3 +9,9 @@
|
||||
height: 30vh;
|
||||
width: 30vh;
|
||||
}
|
||||
|
||||
@media screen, (max-width: 767px) {
|
||||
.challenge-success-modal .btn-lg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
@media screen, (max-width: 767px) {
|
||||
.help-modal .btn-lg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
@ -3,6 +3,13 @@
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen, (max-width: 767px) {
|
||||
.challenge-preview, .challenge-preview-frame {
|
||||
height: calc(100vh - 112px);
|
||||
}
|
||||
}
|
||||
|
||||
.enable-iframe {
|
||||
|
@ -8,4 +8,10 @@
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen, (max-width: 767px) {
|
||||
.reset-modal .btn-lg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,17 @@
|
||||
.tool-panel-group button, .tool-panel-group a {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.tool-panel-group-mobile button, .tool-panel-group-mobile a {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tool-panel-group-mobile {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
padding: 0 2px;
|
||||
background: #fff;
|
||||
}
|
||||
.tool-panel-group-mobile .btn-block + .btn-block {
|
||||
margin: 0 2px 0 0;
|
||||
}
|
||||
|
@ -43,7 +43,12 @@ const executeDebounceTimeout = 750;
|
||||
|
||||
function updateMainEpic(action$, state$, { document }) {
|
||||
return action$.pipe(
|
||||
ofType(types.updateFile, types.challengeMounted),
|
||||
ofType(
|
||||
types.updateFile,
|
||||
types.previewMounted,
|
||||
types.challengeMounted,
|
||||
types.resetChallenge
|
||||
),
|
||||
filter(() => {
|
||||
const { challengeType } = challengeMetaSelector(state$.value);
|
||||
return (
|
||||
|
@ -67,6 +67,7 @@ export const types = createTypes(
|
||||
'closeModal',
|
||||
'openModal',
|
||||
|
||||
'previewMounted',
|
||||
'challengeMounted',
|
||||
'checkChallenge',
|
||||
'executeChallenge',
|
||||
@ -74,6 +75,8 @@ export const types = createTypes(
|
||||
'submitChallenge',
|
||||
'submitComplete',
|
||||
|
||||
'moveToTab',
|
||||
|
||||
...createAsyncTypes('fetchIdToNameMap')
|
||||
],
|
||||
ns
|
||||
@ -143,6 +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 challengeMounted = createAction(types.challengeMounted);
|
||||
export const checkChallenge = createAction(types.checkChallenge);
|
||||
export const executeChallenge = createAction(types.executeChallenge);
|
||||
@ -150,6 +154,9 @@ export const resetChallenge = createAction(types.resetChallenge);
|
||||
export const submitChallenge = createAction(types.submitChallenge);
|
||||
export const submitComplete = createAction(types.submitComplete);
|
||||
|
||||
export const moveToTab = createAction(types.moveToTab);
|
||||
|
||||
export const currentTabSelector = state => state[ns].currentTab;
|
||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||
export const challengeIdToNameMapSelector = state =>
|
||||
state[ns].challengeIdToNameMap;
|
||||
@ -234,6 +241,7 @@ export const reducer = handleActions(
|
||||
|
||||
[types.resetChallenge]: state => ({
|
||||
...state,
|
||||
currentTab: 2,
|
||||
challengeFiles: {
|
||||
...Object.keys(state.challengeFiles)
|
||||
.map(key => state.challengeFiles[key])
|
||||
@ -291,6 +299,14 @@ export const reducer = handleActions(
|
||||
...state.modal,
|
||||
[payload]: true
|
||||
}
|
||||
}),
|
||||
[types.moveToTab]: (state, { payload }) => ({
|
||||
...state,
|
||||
currentTab: payload
|
||||
}),
|
||||
[types.executeChallenge]: (state, { payload }) => ({
|
||||
...state,
|
||||
currentTab: 3
|
||||
})
|
||||
},
|
||||
initialState
|
||||
|
@ -44,31 +44,33 @@ function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
|
||||
<Helmet>
|
||||
<title>{block} | freeCodeCamp</title>
|
||||
</Helmet>
|
||||
<FullWidthRow>
|
||||
<div
|
||||
className='intro-layout'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<Link className='btn btn-lg btn-primary btn-block' to={firstLessonPath}>
|
||||
Go to the first lesson
|
||||
</Link>
|
||||
<ButtonSpacer />
|
||||
<Link to='/learn'>
|
||||
<Button block={true} bsSize='lg' className='btn-primary-invert'>
|
||||
View the curriculum
|
||||
</Button>
|
||||
</Link>
|
||||
<ButtonSpacer />
|
||||
<hr />
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<h2 className='intro-toc-title'>Upcoming Lessons</h2>
|
||||
<ListGroup className='intro-toc'>
|
||||
{allChallengeNode ? renderMenuItems(allChallengeNode) : null}
|
||||
</ListGroup>
|
||||
</FullWidthRow>
|
||||
<div className='intro-layout-container'>
|
||||
<FullWidthRow>
|
||||
<div
|
||||
className='intro-layout'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<Link className='btn btn-lg btn-primary btn-block' to={firstLessonPath}>
|
||||
Go to the first lesson
|
||||
</Link>
|
||||
<ButtonSpacer />
|
||||
<Link to='/learn'>
|
||||
<Button block={true} bsSize='lg' className='btn-primary-invert'>
|
||||
View the curriculum
|
||||
</Button>
|
||||
</Link>
|
||||
<ButtonSpacer />
|
||||
<hr />
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<h2 className='intro-toc-title'>Upcoming Lessons</h2>
|
||||
<ListGroup className='intro-toc'>
|
||||
{allChallengeNode ? renderMenuItems(allChallengeNode) : null}
|
||||
</ListGroup>
|
||||
</FullWidthRow>
|
||||
</div>
|
||||
</LearnLayout>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
@media screen, (max-width: 767px) {
|
||||
.intro-layout-container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-layout {
|
||||
margin-top: 1.45rem;
|
||||
}
|
||||
|
Reference in New Issue
Block a user