Merge pull request #18 from Bouncey/feat/transitionIntros
Feat Transition Intros
This commit is contained in:
parent
fa68757553
commit
4d9f59f318
@ -2,30 +2,8 @@ const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const { dasherize } = require('./utils');
|
||||
const { viewTypes } = require('./utils/challengeTypes');
|
||||
const { blockNameify } = require('./utils/blockNameify');
|
||||
|
||||
const backend = path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/backend/Show.js'
|
||||
);
|
||||
const classic = path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/classic/Show.js'
|
||||
);
|
||||
const intro = path.resolve(__dirname, './src/templates/Introduction/Intro.js');
|
||||
const project = path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/project/Show.js'
|
||||
);
|
||||
|
||||
const views = {
|
||||
backend,
|
||||
classic,
|
||||
modern: classic,
|
||||
project
|
||||
// quiz: Quiz
|
||||
};
|
||||
const { createChallengePages, createIntroPages } = require('./utils/gatsby');
|
||||
|
||||
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
||||
const { createNodeField } = boundActionCreators;
|
||||
@ -60,17 +38,22 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
||||
edges {
|
||||
node {
|
||||
block
|
||||
challengeType
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
id
|
||||
order
|
||||
required {
|
||||
link
|
||||
raw
|
||||
src
|
||||
}
|
||||
suborder
|
||||
superBlock
|
||||
superOrder
|
||||
template
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,46 +80,14 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
}
|
||||
|
||||
// Create challenge pages.
|
||||
result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => {
|
||||
const {
|
||||
fields: { slug },
|
||||
required = [],
|
||||
template,
|
||||
challengeType,
|
||||
id
|
||||
} = edge.node;
|
||||
if (challengeType === 7) {
|
||||
return;
|
||||
}
|
||||
const next = thisArray[index + 1];
|
||||
const nextChallengePath = next ? next.node.fields.slug : '/';
|
||||
createPage({
|
||||
path: slug,
|
||||
component: views[viewTypes[challengeType]],
|
||||
context: {
|
||||
challengeMeta: {
|
||||
challengeType,
|
||||
template,
|
||||
required,
|
||||
nextChallengePath,
|
||||
id
|
||||
},
|
||||
slug
|
||||
}
|
||||
});
|
||||
});
|
||||
result.data.allChallengeNode.edges.forEach(
|
||||
createChallengePages(createPage)
|
||||
);
|
||||
|
||||
// Create intro pages
|
||||
result.data.allMarkdownRemark.edges.forEach(edge => {
|
||||
const { fields: { slug } } = edge.node;
|
||||
createPage({
|
||||
path: slug,
|
||||
component: intro,
|
||||
context: {
|
||||
slug
|
||||
}
|
||||
});
|
||||
});
|
||||
result.data.allMarkdownRemark.edges.forEach(
|
||||
createIntroPages(createPage)
|
||||
);
|
||||
|
||||
return;
|
||||
})
|
||||
|
@ -55,12 +55,14 @@
|
||||
"build:frame-runner": "webpack --config ./webpack-frame-runner.js",
|
||||
"build:loop-protect": "webpack --config ./webpack-loop-protect.js",
|
||||
"develop": "yarn build:frame-runner && gatsby develop",
|
||||
"format": "yarn format:gatsby && yarn format:src && yarn lint",
|
||||
"format": "yarn format:gatsby && yarn format:src && yarn format:utils && yarn lint",
|
||||
"format:gatsby": "prettier --write './gatsby*.js'",
|
||||
"format:src": "prettier --write './src/**/*.js'",
|
||||
"lint": "yarn lint:gatsby && yarn lint:src",
|
||||
"format:utils": "prettier --write './utils/**/*.js'",
|
||||
"lint": "yarn lint:gatsby && yarn lint:src && yarn lint:utils",
|
||||
"lint:gatsby": "eslint ./gatsby*.js --fix",
|
||||
"lint:src": "eslint ./src . --fix",
|
||||
"lint:utils": "eslint ./utils . --fix",
|
||||
"pretty": "yarn format && yarn lint",
|
||||
"test": "yarn format && jest src",
|
||||
"test:watch": "jest --watch src"
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import './button-spacer.css';
|
||||
|
||||
function ButtonSpacer() {
|
||||
return <div className='button-spacer' />;
|
||||
}
|
||||
|
20
packages/learn/src/components/util/FullWidthRow.js
Normal file
20
packages/learn/src/components/util/FullWidthRow.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
|
||||
function FullWidthRow({ children }) {
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={8} smOffset={2} xs={12}>
|
||||
{children}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
FullWidthRow.displayName = 'FullWidthRow';
|
||||
FullWidthRow.propTypes = {
|
||||
children: PropTypes.any
|
||||
};
|
||||
|
||||
export default FullWidthRow;
|
16
packages/learn/src/components/util/FullWidthRow.test.js
Normal file
16
packages/learn/src/components/util/FullWidthRow.test.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* global expect */
|
||||
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
import FullWidthRow from './FullWidthRow';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
test('<FullWidthRow /> snapshot', () => {
|
||||
const component = renderer.create(<FullWidthRow />);
|
||||
let tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<FullWidthRow /> snapshot 1`] = `
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-sm-8 col-sm-offset-2 col-xs-12"
|
||||
/>
|
||||
</div>
|
||||
`;
|
3
packages/learn/src/components/util/button-spacer.css
Normal file
3
packages/learn/src/components/util/button-spacer.css
Normal file
@ -0,0 +1,3 @@
|
||||
.button-spacer {
|
||||
margin: 15px 0;
|
||||
}
|
@ -162,13 +162,18 @@ export default function completionEpic(action$, { getState }) {
|
||||
return action$.pipe(
|
||||
ofType(types.submitChallenge),
|
||||
switchMap(({ type }) => {
|
||||
const { nextChallengePath } = challengeMetaSelector(getState());
|
||||
const { nextChallengePath, introPath } = challengeMetaSelector(
|
||||
getState()
|
||||
);
|
||||
// const state = getState();
|
||||
// const { submitType } = challengeMetaSelector(state);
|
||||
// const submitter = submitters[submitType] || (() => Observable.empty());
|
||||
|
||||
return type === types.submitChallenge
|
||||
? of(closeModal('completion'), push(nextChallengePath))
|
||||
? of(
|
||||
closeModal('completion'),
|
||||
push(introPath ? introPath : nextChallengePath)
|
||||
)
|
||||
: of({ type: 'PONG' });
|
||||
})
|
||||
);
|
||||
|
@ -1,18 +1,62 @@
|
||||
/* global graphql */
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MarkdownRemark } from '../../redux/propTypes';
|
||||
import Link from 'gatsby-link';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Button, ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||
|
||||
import FullWidthRow from '../../components/util/FullWidthRow';
|
||||
import ButtonSpacer from '../../components/util/ButtonSpacer';
|
||||
import { MarkdownRemark, AllChallengeNode } from '../../redux/propTypes';
|
||||
|
||||
import './intro.css';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.shape({
|
||||
markdownRemark: MarkdownRemark
|
||||
markdownRemark: MarkdownRemark,
|
||||
allChallengeNode: AllChallengeNode
|
||||
})
|
||||
};
|
||||
|
||||
function IntroductionPage({ data: { markdownRemark } }) {
|
||||
const { html } = markdownRemark;
|
||||
function renderMenuItems({ edges }) {
|
||||
return edges.map(({ node }) => node).map(({ title, fields: { slug } }) => (
|
||||
<Link to={slug}>
|
||||
<ListGroupItem>{title}</ListGroupItem>
|
||||
</Link>
|
||||
));
|
||||
}
|
||||
|
||||
function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
|
||||
const { html, frontmatter: { block } } = markdownRemark;
|
||||
const firstLesson = allChallengeNode.edges[0].node;
|
||||
const firstLessonPath = firstLesson.fields.slug;
|
||||
return (
|
||||
<div className='intro-layout' dangerouslySetInnerHTML={{ __html: html }} />
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>{block} | freeCodeCamp</title>
|
||||
</Helmet>
|
||||
<FullWidthRow>
|
||||
<div
|
||||
className='intro-layout'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<Link to={firstLessonPath}>
|
||||
<Button block={true} bsSize='lg' bsStyle='primary'>
|
||||
Go to the first lesson
|
||||
</Button>
|
||||
</Link>
|
||||
<ButtonSpacer />
|
||||
<hr />
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<h2 className='intro-toc-title'>Upcoming Lessons</h2>
|
||||
<ListGroup className='intro-toc'>
|
||||
{renderMenuItems(allChallengeNode)}
|
||||
</ListGroup>
|
||||
</FullWidthRow>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@ -22,9 +66,25 @@ IntroductionPage.propTypes = propTypes;
|
||||
export default IntroductionPage;
|
||||
|
||||
export const query = graphql`
|
||||
query IntroPageBySlug($slug: String!) {
|
||||
query IntroPageBySlug($slug: String!, $block: String!) {
|
||||
markdownRemark(fields: { slug: { eq: $slug } }) {
|
||||
frontmatter {
|
||||
block
|
||||
}
|
||||
html
|
||||
}
|
||||
allChallengeNode(
|
||||
filter: { block: { eq: $block } }
|
||||
sort: { fields: [superOrder, order, suborder] }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
9
packages/learn/src/templates/Introduction/intro.css
Normal file
9
packages/learn/src/templates/Introduction/intro.css
Normal file
@ -0,0 +1,9 @@
|
||||
.intro-toc .list-group-item:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.intro-toc a:hover {
|
||||
text-decoration: none;
|
||||
text-decoration-style: none;
|
||||
color: #006400;
|
||||
}
|
@ -13,22 +13,16 @@ const preFormattedBlockNames = {
|
||||
'the-dom': 'The DOM'
|
||||
};
|
||||
|
||||
const noFormatting = [
|
||||
'and',
|
||||
'for',
|
||||
'of',
|
||||
'the',
|
||||
'up',
|
||||
'with'
|
||||
];
|
||||
const noFormatting = ['and', 'for', 'of', 'the', 'up', 'with'];
|
||||
|
||||
exports.blockNameify = function blockNameify(phrase) {
|
||||
const preFormatted = preFormattedBlockNames[phrase] || '';
|
||||
if (preFormatted) {
|
||||
return preFormatted;
|
||||
}
|
||||
return phrase.split('-')
|
||||
.map((word) => {
|
||||
return phrase
|
||||
.split('-')
|
||||
.map(word => {
|
||||
if (noFormatting.indexOf(word) !== -1) {
|
||||
return word;
|
||||
}
|
||||
|
85
packages/learn/utils/gatsby/index.js
Normal file
85
packages/learn/utils/gatsby/index.js
Normal file
@ -0,0 +1,85 @@
|
||||
import { dasherize } from '..';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const { viewTypes } = require('../challengeTypes');
|
||||
|
||||
const backend = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Challenges/backend/Show.js'
|
||||
);
|
||||
const classic = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Challenges/classic/Show.js'
|
||||
);
|
||||
const project = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Challenges/project/Show.js'
|
||||
);
|
||||
const intro = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Introduction/Intro.js'
|
||||
);
|
||||
|
||||
const views = {
|
||||
backend,
|
||||
classic,
|
||||
modern: classic,
|
||||
project
|
||||
// quiz: Quiz
|
||||
};
|
||||
|
||||
const getNextChallengePath = (node, index, nodeArray) => {
|
||||
const next = nodeArray[index + 1];
|
||||
return next ? next.node.fields.slug : '/';
|
||||
};
|
||||
const getTemplateComponent = challengeType => views[viewTypes[challengeType]];
|
||||
|
||||
const getIntroIfRequired = (node, index, nodeArray) => {
|
||||
const next = nodeArray[index + 1];
|
||||
const isEndOfBlock = next && next.node.suborder === 1;
|
||||
let nextSuperBlock = '';
|
||||
let nextBlock = '';
|
||||
if (next) {
|
||||
const { superBlock, block } = next.node;
|
||||
nextSuperBlock = superBlock;
|
||||
nextBlock = block;
|
||||
}
|
||||
return isEndOfBlock
|
||||
? `/${dasherize(nextSuperBlock)}/${dasherize(nextBlock)}`
|
||||
: '';
|
||||
};
|
||||
|
||||
exports.createChallengePages = createPage => ({ node }, index, thisArray) => {
|
||||
const { fields: { slug }, required = [], template, challengeType, id } = node;
|
||||
if (challengeType === 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
createPage({
|
||||
path: slug,
|
||||
component: getTemplateComponent(challengeType),
|
||||
context: {
|
||||
challengeMeta: {
|
||||
introPath: getIntroIfRequired(node, index, thisArray),
|
||||
template,
|
||||
required,
|
||||
nextChallengePath: getNextChallengePath(node, index, thisArray),
|
||||
id
|
||||
},
|
||||
slug
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.createIntroPages = createPage => edge => {
|
||||
const { fields: { slug }, frontmatter: { block } } = edge.node;
|
||||
createPage({
|
||||
path: slug,
|
||||
component: intro,
|
||||
context: {
|
||||
block: dasherize(block),
|
||||
slug
|
||||
}
|
||||
});
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user