Merge pull request #18 from Bouncey/feat/transitionIntros
Feat Transition Intros
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
fa68757553
commit
4d9f59f318
@ -2,30 +2,8 @@ const path = require('path');
|
|||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
const { dasherize } = require('./utils');
|
const { dasherize } = require('./utils');
|
||||||
const { viewTypes } = require('./utils/challengeTypes');
|
|
||||||
const { blockNameify } = require('./utils/blockNameify');
|
const { blockNameify } = require('./utils/blockNameify');
|
||||||
|
const { createChallengePages, createIntroPages } = require('./utils/gatsby');
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
||||||
const { createNodeField } = boundActionCreators;
|
const { createNodeField } = boundActionCreators;
|
||||||
@ -60,17 +38,22 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
|||||||
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
block
|
||||||
challengeType
|
challengeType
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
id
|
id
|
||||||
|
order
|
||||||
required {
|
required {
|
||||||
link
|
link
|
||||||
raw
|
raw
|
||||||
src
|
src
|
||||||
}
|
}
|
||||||
|
suborder
|
||||||
|
superBlock
|
||||||
|
superOrder
|
||||||
template
|
template
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,46 +80,14 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create challenge pages.
|
// Create challenge pages.
|
||||||
result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => {
|
result.data.allChallengeNode.edges.forEach(
|
||||||
const {
|
createChallengePages(createPage)
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create intro pages
|
// Create intro pages
|
||||||
result.data.allMarkdownRemark.edges.forEach(edge => {
|
result.data.allMarkdownRemark.edges.forEach(
|
||||||
const { fields: { slug } } = edge.node;
|
createIntroPages(createPage)
|
||||||
createPage({
|
);
|
||||||
path: slug,
|
|
||||||
component: intro,
|
|
||||||
context: {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
|
@ -55,12 +55,14 @@
|
|||||||
"build:frame-runner": "webpack --config ./webpack-frame-runner.js",
|
"build:frame-runner": "webpack --config ./webpack-frame-runner.js",
|
||||||
"build:loop-protect": "webpack --config ./webpack-loop-protect.js",
|
"build:loop-protect": "webpack --config ./webpack-loop-protect.js",
|
||||||
"develop": "yarn build:frame-runner && gatsby develop",
|
"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:gatsby": "prettier --write './gatsby*.js'",
|
||||||
"format:src": "prettier --write './src/**/*.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:gatsby": "eslint ./gatsby*.js --fix",
|
||||||
"lint:src": "eslint ./src . --fix",
|
"lint:src": "eslint ./src . --fix",
|
||||||
|
"lint:utils": "eslint ./utils . --fix",
|
||||||
"pretty": "yarn format && yarn lint",
|
"pretty": "yarn format && yarn lint",
|
||||||
"test": "yarn format && jest src",
|
"test": "yarn format && jest src",
|
||||||
"test:watch": "jest --watch src"
|
"test:watch": "jest --watch src"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import './button-spacer.css';
|
||||||
|
|
||||||
function ButtonSpacer() {
|
function ButtonSpacer() {
|
||||||
return <div className='button-spacer' />;
|
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(
|
return action$.pipe(
|
||||||
ofType(types.submitChallenge),
|
ofType(types.submitChallenge),
|
||||||
switchMap(({ type }) => {
|
switchMap(({ type }) => {
|
||||||
const { nextChallengePath } = challengeMetaSelector(getState());
|
const { nextChallengePath, introPath } = challengeMetaSelector(
|
||||||
|
getState()
|
||||||
|
);
|
||||||
// const state = getState();
|
// const state = getState();
|
||||||
// const { submitType } = challengeMetaSelector(state);
|
// const { submitType } = challengeMetaSelector(state);
|
||||||
// const submitter = submitters[submitType] || (() => Observable.empty());
|
// const submitter = submitters[submitType] || (() => Observable.empty());
|
||||||
|
|
||||||
return type === types.submitChallenge
|
return type === types.submitChallenge
|
||||||
? of(closeModal('completion'), push(nextChallengePath))
|
? of(
|
||||||
|
closeModal('completion'),
|
||||||
|
push(introPath ? introPath : nextChallengePath)
|
||||||
|
)
|
||||||
: of({ type: 'PONG' });
|
: of({ type: 'PONG' });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,62 @@
|
|||||||
/* global graphql */
|
/* global graphql */
|
||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 = {
|
const propTypes = {
|
||||||
data: PropTypes.shape({
|
data: PropTypes.shape({
|
||||||
markdownRemark: MarkdownRemark
|
markdownRemark: MarkdownRemark,
|
||||||
|
allChallengeNode: AllChallengeNode
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
function IntroductionPage({ data: { markdownRemark } }) {
|
function renderMenuItems({ edges }) {
|
||||||
const { html } = markdownRemark;
|
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 (
|
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 default IntroductionPage;
|
||||||
|
|
||||||
export const query = graphql`
|
export const query = graphql`
|
||||||
query IntroPageBySlug($slug: String!) {
|
query IntroPageBySlug($slug: String!, $block: String!) {
|
||||||
markdownRemark(fields: { slug: { eq: $slug } }) {
|
markdownRemark(fields: { slug: { eq: $slug } }) {
|
||||||
|
frontmatter {
|
||||||
|
block
|
||||||
|
}
|
||||||
html
|
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'
|
'the-dom': 'The DOM'
|
||||||
};
|
};
|
||||||
|
|
||||||
const noFormatting = [
|
const noFormatting = ['and', 'for', 'of', 'the', 'up', 'with'];
|
||||||
'and',
|
|
||||||
'for',
|
|
||||||
'of',
|
|
||||||
'the',
|
|
||||||
'up',
|
|
||||||
'with'
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.blockNameify = function blockNameify(phrase) {
|
exports.blockNameify = function blockNameify(phrase) {
|
||||||
const preFormatted = preFormattedBlockNames[phrase] || '';
|
const preFormatted = preFormattedBlockNames[phrase] || '';
|
||||||
if (preFormatted) {
|
if (preFormatted) {
|
||||||
return preFormatted;
|
return preFormatted;
|
||||||
}
|
}
|
||||||
return phrase.split('-')
|
return phrase
|
||||||
.map((word) => {
|
.split('-')
|
||||||
|
.map(word => {
|
||||||
if (noFormatting.indexOf(word) !== -1) {
|
if (noFormatting.indexOf(word) !== -1) {
|
||||||
return word;
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
Reference in New Issue
Block a user