From f96033c47b6278b12e21855c6660b8a198e7eca2 Mon Sep 17 00:00:00 2001
From: Jerel Miller
Date: Wed, 14 Oct 2020 12:10:10 -0700
Subject: [PATCH] feat: gather steps inside tutorial sections
---
src/components/Tutorial.js | 106 +++++++++++++++++++-----------
src/components/TutorialEditor.js | 8 +--
src/components/TutorialSection.js | 21 +++++-
src/components/TutorialStep.js | 14 ++--
4 files changed, 95 insertions(+), 54 deletions(-)
diff --git a/src/components/Tutorial.js b/src/components/Tutorial.js
index 61c2d8a09..cc739b360 100644
--- a/src/components/Tutorial.js
+++ b/src/components/Tutorial.js
@@ -7,59 +7,85 @@ import { diffLines } from 'diff';
const Tutorial = ({ children }) => {
children = Children.toArray(children);
- const initialState =
+ const initialProjectState =
children[0].props.mdxType === 'Project'
- ? parseFileInfoFromConfig(children[0])
- : parseFileInfoFromChildren(children);
+ ? parseProjectStateFromConfig(children[0])
+ : parseProjectStateFromChildren(children);
- return children
- .filter((child) => child.props.mdxType === 'TutorialStep')
- .reduce((steps, stepElement, idx, arr) => {
+ const { elements } = children.reduce(
+ (memo, child) => {
+ const { elements, currentProjectState } = memo;
+
+ if (child.props.mdxType === 'Project') {
+ return memo;
+ } else if (child.props.mdxType === 'TutorialSection') {
+ const { steps, currentProjectState: projectState } = gatherSteps(
+ child,
+ currentProjectState
+ );
+
+ return {
+ currentProjectState: projectState,
+ elements: [...elements, cloneElement(child, { children: steps })],
+ };
+ }
+
+ return { elements: [...elements, child], currentProjectState };
+ },
+ { elements: [], currentProjectState: initialProjectState }
+ );
+
+ return elements;
+};
+
+Tutorial.propTypes = {
+ children: PropTypes.node,
+};
+
+const gatherSteps = (parentElement, initialProjectState) => {
+ return Children.toArray(parentElement.props.children).reduce(
+ ({ steps, currentProjectState }, stepElement, idx, children) => {
+ const sharedProps = { stepNumber: idx + 1, totalSteps: children.length };
const codeBlock = Children.toArray(stepElement.props.children).find(
(child) => isCodeBlock(child) && !isShellCommand(child)
);
if (!codeBlock) {
- return [
- ...steps,
- cloneElement(stepElement, { index: idx, totalSteps: arr.length }),
- ];
+ return {
+ currentProjectState,
+ steps: [...steps, cloneElement(stepElement, sharedProps)],
+ };
}
- const previousStep = new Map(
- steps
- .slice(0, idx)
- .map((element) => element.props.step)
- .reverse()
- .find(Boolean) || initialState
- );
-
const props = parseCodeBlockProps(codeBlock);
- const { code: prevCode } = previousStep.get(props.fileName);
-
- return [
- ...steps,
- cloneElement(stepElement, {
- codeBlock: { ...props, diff: diffLines(prevCode, props.code) },
- step: previousStep.set(props.fileName, {
- code: props.code,
- language: props.language,
+ const { code: prevCode } = currentProjectState.get(props.fileName);
+ const projectState = clone(currentProjectState).set(props.fileName, {
+ code: props.code,
+ language: props.language,
+ });
+
+ return {
+ currentProjectState: projectState,
+ steps: [
+ ...steps,
+ cloneElement(stepElement, {
+ ...sharedProps,
+ codeBlock: { ...props, diff: diffLines(prevCode, props.code) },
+ project: projectState,
+ children: Children.toArray(stepElement.props.children).filter(
+ (child) => isShellCommand(child) || !isCodeBlock(child)
+ ),
}),
- index: idx,
- totalSteps: arr.length,
- children: Children.toArray(stepElement.props.children).filter(
- (child) => isShellCommand(child) || !isCodeBlock(child)
- ),
- }),
- ];
- }, []);
+ ],
+ };
+ },
+ { steps: [], currentProjectState: initialProjectState }
+ );
};
-Tutorial.propTypes = {
- children: PropTypes.node,
-};
+const clone = (map) => new Map(map);
-const parseFileInfoFromConfig = (configElement) => {
+const parseProjectStateFromConfig = (configElement) => {
return Children.toArray(configElement.props.children)
.filter((child) => isCodeBlock(child) && !isShellCommand(child))
.reduce((map, child) => {
@@ -69,7 +95,7 @@ const parseFileInfoFromConfig = (configElement) => {
}, new Map());
};
-const parseFileInfoFromChildren = (children) => {
+const parseProjectStateFromChildren = (children) => {
return children
.flatMap((child) => {
switch (child.props.mdxType) {
diff --git a/src/components/TutorialEditor.js b/src/components/TutorialEditor.js
index 4fe809ecb..46121311a 100644
--- a/src/components/TutorialEditor.js
+++ b/src/components/TutorialEditor.js
@@ -43,7 +43,7 @@ const diffHighlightedLines = (diff) => {
return highlightedLines.join(',');
};
-const TutorialEditor = ({ codeBlock, files }) => {
+const TutorialEditor = ({ codeBlock, project }) => {
const [selectedFile, setSelectedFile] = useState(codeBlock.fileName);
return (
@@ -56,7 +56,7 @@ const TutorialEditor = ({ codeBlock, files }) => {
border-top-right-radius: 0.25rem;
`}
>
- {Array.from(files.keys()).map((fileName) => (
+ {Array.from(project.keys()).map((fileName) => (
- {step ? (
+ {project ? (
) : (
children
@@ -59,8 +59,8 @@ const TutorialStep = ({
TutorialStep.propTypes = {
children: PropTypes.node,
codeBlock: PropTypes.object.isRequired,
- index: PropTypes.number.isRequired,
- step: PropTypes.instanceOf(Map).isRequired,
+ stepNumber: PropTypes.number.isRequired,
+ project: PropTypes.instanceOf(Map).isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
totalSteps: PropTypes.number.isRequired,
};