mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(client): source super block structure in graphql and store in redux (#62613)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -13,7 +13,15 @@ import {
|
|||||||
} from '../../templates/Challenges/redux/selectors';
|
} from '../../templates/Challenges/redux/selectors';
|
||||||
import { liveCerts } from '../../../config/cert-and-project-map';
|
import { liveCerts } from '../../../config/cert-and-project-map';
|
||||||
import { updateAllChallengesInfo } from '../../redux/actions';
|
import { updateAllChallengesInfo } from '../../redux/actions';
|
||||||
import { CertificateNode, ChallengeNode } from '../../redux/prop-types';
|
import type {
|
||||||
|
CertificateNode,
|
||||||
|
ChallengeNode,
|
||||||
|
SuperBlockStructure
|
||||||
|
} from '../../redux/prop-types';
|
||||||
|
import {
|
||||||
|
updateSuperBlockStructures,
|
||||||
|
superBlockStructuresSelector
|
||||||
|
} from '../../templates/Introduction/redux';
|
||||||
import { getIsDailyCodingChallenge } from '../../../../shared-dist/config/challenge-types';
|
import { getIsDailyCodingChallenge } from '../../../../shared-dist/config/challenge-types';
|
||||||
import {
|
import {
|
||||||
isValidDateString,
|
isValidDateString,
|
||||||
@@ -26,6 +34,7 @@ const mapStateToProps = createSelector(
|
|||||||
challengeMetaSelector,
|
challengeMetaSelector,
|
||||||
completedChallengesInBlockSelector,
|
completedChallengesInBlockSelector,
|
||||||
completedPercentageSelector,
|
completedPercentageSelector,
|
||||||
|
superBlockStructuresSelector,
|
||||||
(
|
(
|
||||||
currentBlockIds: string[],
|
currentBlockIds: string[],
|
||||||
{
|
{
|
||||||
@@ -40,7 +49,8 @@ const mapStateToProps = createSelector(
|
|||||||
superBlock: string;
|
superBlock: string;
|
||||||
},
|
},
|
||||||
completedChallengesInBlock: number,
|
completedChallengesInBlock: number,
|
||||||
completedPercent: number
|
completedPercent: number,
|
||||||
|
superBlockStructures: Record<string, SuperBlockStructure>
|
||||||
) => ({
|
) => ({
|
||||||
currentBlockIds,
|
currentBlockIds,
|
||||||
challengeType,
|
challengeType,
|
||||||
@@ -48,11 +58,15 @@ const mapStateToProps = createSelector(
|
|||||||
block,
|
block,
|
||||||
superBlock,
|
superBlock,
|
||||||
completedChallengesInBlock,
|
completedChallengesInBlock,
|
||||||
completedPercent
|
completedPercent,
|
||||||
|
superBlockStructures
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapDispatchToProps = { updateAllChallengesInfo };
|
const mapDispatchToProps = {
|
||||||
|
updateAllChallengesInfo,
|
||||||
|
updateSuperBlockStructures
|
||||||
|
};
|
||||||
|
|
||||||
type PropsFromRedux = ConnectedProps<typeof connector>;
|
type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
@@ -68,7 +82,9 @@ function Progress({
|
|||||||
completedChallengesInBlock,
|
completedChallengesInBlock,
|
||||||
completedPercent,
|
completedPercent,
|
||||||
t,
|
t,
|
||||||
updateAllChallengesInfo
|
updateAllChallengesInfo,
|
||||||
|
updateSuperBlockStructures,
|
||||||
|
superBlockStructures: superBlockStructuresFromStore
|
||||||
}: ProgressProps): JSX.Element {
|
}: ProgressProps): JSX.Element {
|
||||||
let blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
|
let blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
|
||||||
// Always false for legacy full stack, since it has no projects.
|
// Always false for legacy full stack, since it has no projects.
|
||||||
@@ -86,10 +102,31 @@ function Progress({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { challengeNodes, certificateNodes } = useGetAllBlockIds();
|
const { challengeNodes, certificateNodes, superBlockStructureNodes } =
|
||||||
|
useGetAllChallengeData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateAllChallengesInfo({ challengeNodes, certificateNodes });
|
updateAllChallengesInfo({ challengeNodes, certificateNodes });
|
||||||
}, [challengeNodes, certificateNodes, updateAllChallengesInfo]);
|
|
||||||
|
const structuresMap: Record<string, SuperBlockStructure> = {};
|
||||||
|
|
||||||
|
// The super block structures are pretty static, so we only want to
|
||||||
|
// update them if we don't already have them in the store.
|
||||||
|
if (Object.keys(superBlockStructuresFromStore).length === 0) {
|
||||||
|
superBlockStructureNodes.forEach((node: SuperBlockStructure) => {
|
||||||
|
structuresMap[node.superBlock] = node;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateSuperBlockStructures(structuresMap);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
challengeNodes,
|
||||||
|
certificateNodes,
|
||||||
|
superBlockStructureNodes,
|
||||||
|
updateAllChallengesInfo,
|
||||||
|
updateSuperBlockStructures,
|
||||||
|
superBlockStructuresFromStore
|
||||||
|
]);
|
||||||
|
|
||||||
const totalChallengesInBlock = currentBlockIds?.length ?? 0;
|
const totalChallengesInBlock = currentBlockIds?.length ?? 0;
|
||||||
const meta =
|
const meta =
|
||||||
@@ -119,13 +156,15 @@ function Progress({
|
|||||||
// and in completion-modal). Then we don't have to pass the data into redux.
|
// and in completion-modal). Then we don't have to pass the data into redux.
|
||||||
// This would mean that we have to memoize any complex calculations in the hook.
|
// This would mean that we have to memoize any complex calculations in the hook.
|
||||||
// Otherwise, this will undo all the recent performance improvements.
|
// Otherwise, this will undo all the recent performance improvements.
|
||||||
const useGetAllBlockIds = () => {
|
const useGetAllChallengeData = () => {
|
||||||
const {
|
const {
|
||||||
allChallengeNode: { nodes: challengeNodes },
|
allChallengeNode: { nodes: challengeNodes },
|
||||||
allCertificateNode: { nodes: certificateNodes }
|
allCertificateNode: { nodes: certificateNodes },
|
||||||
|
allSuperBlockStructure: { nodes: superBlockStructureNodes }
|
||||||
}: {
|
}: {
|
||||||
allChallengeNode: { nodes: ChallengeNode[] };
|
allChallengeNode: { nodes: ChallengeNode[] };
|
||||||
allCertificateNode: { nodes: CertificateNode[] };
|
allCertificateNode: { nodes: CertificateNode[] };
|
||||||
|
allSuperBlockStructure: { nodes: SuperBlockStructure[] };
|
||||||
} = useStaticQuery(graphql`
|
} = useStaticQuery(graphql`
|
||||||
query getBlockNode {
|
query getBlockNode {
|
||||||
allChallengeNode(
|
allChallengeNode(
|
||||||
@@ -154,10 +193,25 @@ const useGetAllBlockIds = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
allSuperBlockStructure {
|
||||||
|
nodes {
|
||||||
|
superBlock
|
||||||
|
chapters {
|
||||||
|
dashedName
|
||||||
|
comingSoon
|
||||||
|
modules {
|
||||||
|
dashedName
|
||||||
|
comingSoon
|
||||||
|
moduleType
|
||||||
|
blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
return { challengeNodes, certificateNodes };
|
return { challengeNodes, certificateNodes, superBlockStructureNodes };
|
||||||
};
|
};
|
||||||
|
|
||||||
Progress.displayName = 'Progress';
|
Progress.displayName = 'Progress';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { HandlerProps } from 'react-reflex';
|
import { HandlerProps } from 'react-reflex';
|
||||||
import { SuperBlocks } from '../../../shared-dist/config/curriculum';
|
import { SuperBlocks } from '../../../shared-dist/config/curriculum';
|
||||||
|
import type { Chapter } from '../../../shared-dist/config/chapters';
|
||||||
import { BlockLayouts, BlockTypes } from '../../../shared-dist/config/blocks';
|
import { BlockLayouts, BlockTypes } from '../../../shared-dist/config/blocks';
|
||||||
import type { ChallengeFile, Ext } from '../../../shared-dist/utils/polyvinyl';
|
import type { ChallengeFile, Ext } from '../../../shared-dist/utils/polyvinyl';
|
||||||
import { type CertTitle } from '../../config/cert-and-project-map';
|
import { type CertTitle } from '../../config/cert-and-project-map';
|
||||||
@@ -347,6 +348,20 @@ export type AllChallengesInfo = {
|
|||||||
certificateNodes: CertificateNode[];
|
certificateNodes: CertificateNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ChapterBasedSuperBlockStructure = {
|
||||||
|
superBlock: SuperBlocks;
|
||||||
|
chapters: Chapter[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BlockBasedSuperBlockStructure = {
|
||||||
|
superBlock: SuperBlocks;
|
||||||
|
blocks: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SuperBlockStructure =
|
||||||
|
| ChapterBasedSuperBlockStructure
|
||||||
|
| BlockBasedSuperBlockStructure;
|
||||||
|
|
||||||
export type AllChallengeNode = {
|
export type AllChallengeNode = {
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
// TODO: source the superblock structure via a GQL query, rather than directly
|
|
||||||
// from the curriculum
|
|
||||||
import superBlockStructure from '../../../curriculum/structure/superblocks/full-stack-developer.json';
|
|
||||||
import { randomBetween } from '../utils/random-between';
|
import { randomBetween } from '../utils/random-between';
|
||||||
import { getSessionChallengeData } from '../utils/session-storage';
|
import { getSessionChallengeData } from '../utils/session-storage';
|
||||||
|
import { superBlockStructuresSelector } from '../templates/Introduction/redux';
|
||||||
import { ns as MainApp } from './action-types';
|
import { ns as MainApp } from './action-types';
|
||||||
|
|
||||||
export const savedChallengesSelector = state =>
|
export const savedChallengesSelector = state =>
|
||||||
@@ -121,6 +119,8 @@ export const createUserByNameSelector = username => state => {
|
|||||||
export const userFetchStateSelector = state => state[MainApp].userFetchState;
|
export const userFetchStateSelector = state => state[MainApp].userFetchState;
|
||||||
export const allChallengesInfoSelector = state =>
|
export const allChallengesInfoSelector = state =>
|
||||||
state[MainApp].allChallengesInfo;
|
state[MainApp].allChallengesInfo;
|
||||||
|
export const getSuperBlockStructure = (state, superBlock) =>
|
||||||
|
superBlockStructuresSelector(state)[superBlock];
|
||||||
|
|
||||||
export const completedChallengesIdsSelector = createSelector(
|
export const completedChallengesIdsSelector = createSelector(
|
||||||
completedChallengesSelector,
|
completedChallengesSelector,
|
||||||
@@ -133,11 +133,24 @@ export const completedDailyCodingChallengesIdsSelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const completionStateSelector = createSelector(
|
export const completionStateSelector = createSelector(
|
||||||
[allChallengesInfoSelector, completedChallengesIdsSelector],
|
[
|
||||||
(allChallengesInfo, completedChallengesIds) => {
|
allChallengesInfoSelector,
|
||||||
const chapters = superBlockStructure.chapters;
|
completedChallengesIdsSelector,
|
||||||
|
superBlockStructuresSelector,
|
||||||
|
state => state.challenge.challengeMeta
|
||||||
|
],
|
||||||
|
(
|
||||||
|
allChallengesInfo,
|
||||||
|
completedChallengesIds,
|
||||||
|
superBlockStructures,
|
||||||
|
challengeMeta
|
||||||
|
) => {
|
||||||
const { challengeNodes } = allChallengesInfo;
|
const { challengeNodes } = allChallengesInfo;
|
||||||
|
|
||||||
|
const structure = superBlockStructures[challengeMeta.superBlock];
|
||||||
|
|
||||||
|
const chapters = structure.chapters ?? [];
|
||||||
|
|
||||||
const getCompletionState = ({
|
const getCompletionState = ({
|
||||||
chapters,
|
chapters,
|
||||||
challenges,
|
challenges,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { isSignedInSelector, userSelector } from '../../../redux/selectors';
|
|||||||
import { mapFilesToChallengeFiles } from '../../../utils/ajax';
|
import { mapFilesToChallengeFiles } from '../../../utils/ajax';
|
||||||
import { standardizeRequestBody } from '../../../utils/challenge-request-helpers';
|
import { standardizeRequestBody } from '../../../utils/challenge-request-helpers';
|
||||||
import postUpdate$ from '../utils/post-update';
|
import postUpdate$ from '../utils/post-update';
|
||||||
import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
|
import { chapterBasedSuperBlocks } from '../../../../../shared-dist/config/curriculum';
|
||||||
import { actionTypes } from './action-types';
|
import { actionTypes } from './action-types';
|
||||||
import {
|
import {
|
||||||
closeModal,
|
closeModal,
|
||||||
@@ -295,11 +295,11 @@ export default function completionEpic(action$, state$) {
|
|||||||
if (action.type !== submitActionTypes.submitComplete) return null;
|
if (action.type !== submitActionTypes.submitComplete) return null;
|
||||||
|
|
||||||
const donationData =
|
const donationData =
|
||||||
superBlock === SuperBlocks.FullStackDeveloper &&
|
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||||
blockType !== 'review' &&
|
blockType !== 'review' &&
|
||||||
isModuleNewlyCompletedSelector(state)
|
isModuleNewlyCompletedSelector(state)
|
||||||
? { module, superBlock }
|
? { module, superBlock }
|
||||||
: superBlock !== SuperBlocks.FullStackDeveloper &&
|
: !chapterBasedSuperBlocks.includes(superBlock) &&
|
||||||
isBlockNewlyCompletedSelector(state)
|
isBlockNewlyCompletedSelector(state)
|
||||||
? { block, superBlock }
|
? { block, superBlock }
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
@@ -5,18 +5,7 @@ import { Disclosure } from '@headlessui/react';
|
|||||||
|
|
||||||
import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
|
import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
|
||||||
import DropDown from '../../../assets/icons/dropdown';
|
import DropDown from '../../../assets/icons/dropdown';
|
||||||
// TODO: source the superblock structure via a GQL query, rather than directly
|
import type { ChapterBasedSuperBlockStructure } from '../../../redux/prop-types';
|
||||||
// from the curriculum
|
|
||||||
import fullStackCert from '../../../../../curriculum/structure/superblocks/full-stack-developer.json';
|
|
||||||
import fullStackOpen from '../../../../../curriculum/structure/superblocks/full-stack-open.json';
|
|
||||||
import a1Spanish from '../../../../../curriculum/structure/superblocks/a1-professional-spanish.json';
|
|
||||||
import respWebDesignV9 from '../../../../../curriculum/structure/superblocks/responsive-web-design-v9.json';
|
|
||||||
import javascriptV9 from '../../../../../curriculum/structure/superblocks/javascript-v9.json';
|
|
||||||
import frontEndDevLibsV9 from '../../../../../curriculum/structure/superblocks/front-end-development-libraries-v9.json';
|
|
||||||
import pythonV9 from '../../../../../curriculum/structure/superblocks/python-v9.json';
|
|
||||||
import relationalDbV9 from '../../../../../curriculum/structure/superblocks/relational-databases-v9.json';
|
|
||||||
import backEndDevApisV9 from '../../../../../curriculum/structure/superblocks/back-end-development-and-apis-v9.json';
|
|
||||||
|
|
||||||
import { ChapterIcon } from '../../../assets/chapter-icon';
|
import { ChapterIcon } from '../../../assets/chapter-icon';
|
||||||
import { type Chapter } from '../../../../../shared-dist/config/chapters';
|
import { type Chapter } from '../../../../../shared-dist/config/chapters';
|
||||||
import {
|
import {
|
||||||
@@ -67,6 +56,7 @@ interface Challenge {
|
|||||||
interface SuperBlockAccordionProps {
|
interface SuperBlockAccordionProps {
|
||||||
challenges: Challenge[];
|
challenges: Challenge[];
|
||||||
superBlock: SuperBlocks;
|
superBlock: SuperBlocks;
|
||||||
|
structure: ChapterBasedSuperBlockStructure;
|
||||||
chosenBlock: string;
|
chosenBlock: string;
|
||||||
completedChallengeIds: string[];
|
completedChallengeIds: string[];
|
||||||
}
|
}
|
||||||
@@ -182,37 +172,11 @@ const LinkBlock = ({
|
|||||||
export const SuperBlockAccordion = ({
|
export const SuperBlockAccordion = ({
|
||||||
challenges,
|
challenges,
|
||||||
superBlock,
|
superBlock,
|
||||||
|
structure,
|
||||||
chosenBlock,
|
chosenBlock,
|
||||||
completedChallengeIds
|
completedChallengeIds
|
||||||
}: SuperBlockAccordionProps) => {
|
}: SuperBlockAccordionProps) => {
|
||||||
function getSuperblockStructure(superBlock: SuperBlocks): {
|
const superBlockStructure = structure;
|
||||||
chapters: Chapter[];
|
|
||||||
} {
|
|
||||||
switch (superBlock) {
|
|
||||||
case SuperBlocks.FullStackOpen:
|
|
||||||
return fullStackOpen;
|
|
||||||
case SuperBlocks.FullStackDeveloper:
|
|
||||||
return fullStackCert;
|
|
||||||
case SuperBlocks.A1Spanish:
|
|
||||||
return a1Spanish;
|
|
||||||
case SuperBlocks.RespWebDesignV9:
|
|
||||||
return respWebDesignV9;
|
|
||||||
case SuperBlocks.JsV9:
|
|
||||||
return javascriptV9;
|
|
||||||
case SuperBlocks.FrontEndDevLibsV9:
|
|
||||||
return frontEndDevLibsV9;
|
|
||||||
case SuperBlocks.PythonV9:
|
|
||||||
return pythonV9;
|
|
||||||
case SuperBlocks.RelationalDbV9:
|
|
||||||
return relationalDbV9;
|
|
||||||
case SuperBlocks.BackEndDevApisV9:
|
|
||||||
return backEndDevApisV9;
|
|
||||||
default:
|
|
||||||
throw new Error("The SuperBlock structure hasn't been imported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const superBlockStructure = getSuperblockStructure(superBlock);
|
|
||||||
|
|
||||||
const modules = superBlockStructure.chapters.flatMap<Module>(
|
const modules = superBlockStructure.chapters.flatMap<Module>(
|
||||||
({ modules }) => modules
|
({ modules }) => modules
|
||||||
|
|||||||
@@ -7,16 +7,25 @@ export const ns = 'curriculumMap';
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
expandedState: {
|
expandedState: {
|
||||||
block: {}
|
block: {}
|
||||||
}
|
},
|
||||||
|
superBlockStructures: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const types = createTypes(['resetExpansion', 'toggleBlock'], ns);
|
const types = createTypes(
|
||||||
|
['resetExpansion', 'toggleBlock', 'updateSuperBlockStructures'],
|
||||||
|
ns
|
||||||
|
);
|
||||||
|
|
||||||
export const resetExpansion = createAction(types.resetExpansion);
|
export const resetExpansion = createAction(types.resetExpansion);
|
||||||
export const toggleBlock = createAction(types.toggleBlock);
|
export const toggleBlock = createAction(types.toggleBlock);
|
||||||
|
export const updateSuperBlockStructures = createAction(
|
||||||
|
types.updateSuperBlockStructures
|
||||||
|
);
|
||||||
|
|
||||||
export const makeExpandedBlockSelector = block => state =>
|
export const makeExpandedBlockSelector = block => state =>
|
||||||
!!state[ns].expandedState.block[block];
|
!!state[ns].expandedState.block[block];
|
||||||
|
export const superBlockStructuresSelector = state =>
|
||||||
|
state[ns].superBlockStructures || {};
|
||||||
|
|
||||||
export const reducer = handleActions(
|
export const reducer = handleActions(
|
||||||
{
|
{
|
||||||
@@ -35,6 +44,10 @@ export const reducer = handleActions(
|
|||||||
[payload]: !state.expandedState.block[payload]
|
[payload]: !state.expandedState.block[payload]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
[types.updateSuperBlockStructures]: (state, { payload }) => ({
|
||||||
|
...state,
|
||||||
|
superBlockStructures: { ...payload }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initialState
|
initialState
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ import {
|
|||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
signInLoadingSelector
|
signInLoadingSelector
|
||||||
} from '../../redux/selectors';
|
} from '../../redux/selectors';
|
||||||
import type { User } from '../../redux/prop-types';
|
import type {
|
||||||
|
SuperBlockStructure,
|
||||||
|
User,
|
||||||
|
ChapterBasedSuperBlockStructure
|
||||||
|
} from '../../redux/prop-types';
|
||||||
import { CertTitle, liveCerts } from '../../../config/cert-and-project-map';
|
import { CertTitle, liveCerts } from '../../../config/cert-and-project-map';
|
||||||
import { superBlockToCertMap } from '../../../../shared-dist/config/certification-settings';
|
import { superBlockToCertMap } from '../../../../shared-dist/config/certification-settings';
|
||||||
import {
|
import {
|
||||||
@@ -71,6 +75,7 @@ type SuperBlockProps = {
|
|||||||
currentChallengeId: string;
|
currentChallengeId: string;
|
||||||
data: {
|
data: {
|
||||||
allChallengeNode: { nodes: ChallengeNode[] };
|
allChallengeNode: { nodes: ChallengeNode[] };
|
||||||
|
allSuperBlockStructure: { nodes: SuperBlockStructure[] };
|
||||||
};
|
};
|
||||||
expandedState: {
|
expandedState: {
|
||||||
[key: string]: boolean;
|
[key: string]: boolean;
|
||||||
@@ -143,7 +148,8 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
allChallengeNode: { nodes }
|
allChallengeNode: { nodes },
|
||||||
|
allSuperBlockStructure
|
||||||
},
|
},
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
currentChallengeId,
|
currentChallengeId,
|
||||||
@@ -173,6 +179,10 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
|
|
||||||
const i18nTitle = i18next.t(`intro:${superBlock}.title`);
|
const i18nTitle = i18next.t(`intro:${superBlock}.title`);
|
||||||
|
|
||||||
|
const currentSuperBlockStructure = allSuperBlockStructure.nodes.find(
|
||||||
|
node => node.superBlock === superBlock
|
||||||
|
);
|
||||||
|
|
||||||
const showCertification = liveCerts.some(
|
const showCertification = liveCerts.some(
|
||||||
cert => superBlockToCertMap[superBlock] === cert.certSlug
|
cert => superBlockToCertMap[superBlock] === cert.certSlug
|
||||||
);
|
);
|
||||||
@@ -265,6 +275,9 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
<SuperBlockAccordion
|
<SuperBlockAccordion
|
||||||
challenges={superBlockChallenges}
|
challenges={superBlockChallenges}
|
||||||
superBlock={superBlock}
|
superBlock={superBlock}
|
||||||
|
structure={
|
||||||
|
currentSuperBlockStructure as ChapterBasedSuperBlockStructure
|
||||||
|
}
|
||||||
chosenBlock={initialExpandedBlock}
|
chosenBlock={initialExpandedBlock}
|
||||||
completedChallengeIds={completedChallenges.map(c => c.id)}
|
completedChallengeIds={completedChallenges.map(c => c.id)}
|
||||||
/>
|
/>
|
||||||
@@ -359,5 +372,20 @@ export const query = graphql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
allSuperBlockStructure {
|
||||||
|
nodes {
|
||||||
|
superBlock
|
||||||
|
chapters {
|
||||||
|
dashedName
|
||||||
|
comingSoon
|
||||||
|
modules {
|
||||||
|
dashedName
|
||||||
|
comingSoon
|
||||||
|
moduleType
|
||||||
|
blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
const path = require('path');
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
|
const { getSuperblockStructure } = require('../../../curriculum/file-handler');
|
||||||
|
|
||||||
const { createChallengeNode } = require('./create-challenge-nodes');
|
const { createChallengeNode } = require('./create-challenge-nodes');
|
||||||
|
|
||||||
exports.sourceNodes = function sourceChallengesSourceNodes(
|
exports.sourceNodes = function sourceChallengesSourceNodes(
|
||||||
{ actions, reporter },
|
{ actions, reporter, createNodeId, createContentDigest },
|
||||||
pluginOptions
|
pluginOptions
|
||||||
) {
|
) {
|
||||||
const { source, onSourceChange, curriculumPath } = pluginOptions;
|
const { source, onSourceChange, curriculumPath } = pluginOptions;
|
||||||
@@ -67,9 +69,13 @@ exports.sourceNodes = function sourceChallengesSourceNodes(
|
|||||||
function sourceAndCreateNodes() {
|
function sourceAndCreateNodes() {
|
||||||
return source()
|
return source()
|
||||||
.then(challenges => Promise.all(challenges))
|
.then(challenges => Promise.all(challenges))
|
||||||
.then(challenges =>
|
.then(challenges => {
|
||||||
challenges.map(challenge => createVisibleChallenge(challenge))
|
// create challenge nodes
|
||||||
)
|
challenges.forEach(challenge => createVisibleChallenge(challenge));
|
||||||
|
// create superblock structure nodes
|
||||||
|
createSuperBlockStructureNodes();
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
reporter.panic(`fcc-source-challenges
|
reporter.panic(`fcc-source-challenges
|
||||||
@@ -84,6 +90,50 @@ exports.sourceNodes = function sourceChallengesSourceNodes(
|
|||||||
createNode(createChallengeNode(challenge, reporter, options));
|
createNode(createChallengeNode(challenge, reporter, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSuperBlockStructureNodes() {
|
||||||
|
const buildCurriculumPath = path.resolve(
|
||||||
|
curriculumPath,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'build-curriculum'
|
||||||
|
);
|
||||||
|
const buildCurriculum = require(buildCurriculumPath);
|
||||||
|
const superBlockToFilename = buildCurriculum.superBlockToFilename;
|
||||||
|
|
||||||
|
if (!superBlockToFilename) {
|
||||||
|
reporter.panic(
|
||||||
|
'superBlockToFilename is missing from build-curriculum. This map is required.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(superBlockToFilename).forEach(superBlock => {
|
||||||
|
const filename = superBlockToFilename[superBlock] || superBlock;
|
||||||
|
try {
|
||||||
|
const structure = getSuperblockStructure(filename);
|
||||||
|
|
||||||
|
const nodeId = createNodeId(`SuperBlockStructure-${superBlock}`);
|
||||||
|
const nodeContent = JSON.stringify(structure);
|
||||||
|
|
||||||
|
createNode({
|
||||||
|
...structure,
|
||||||
|
superBlock,
|
||||||
|
id: nodeId,
|
||||||
|
parent: null,
|
||||||
|
children: [],
|
||||||
|
internal: {
|
||||||
|
type: 'SuperBlockStructure',
|
||||||
|
content: nodeContent,
|
||||||
|
contentDigest: createContentDigest(structure)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
reporter.warn(
|
||||||
|
`Could not load structure for ${superBlock} (${filename}): ${err.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
watcher.on('ready', () => sourceAndCreateNodes().then(resolve, reject));
|
watcher.on('ready', () => sourceAndCreateNodes().then(resolve, reject));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user