From 815f0291b609c4f4f318002a8ac9b4fd83f14083 Mon Sep 17 00:00:00 2001 From: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:17:21 -0700 Subject: [PATCH] feat(client, curriculum): add support for blockLayout property (#56101) Co-authored-by: Tom <20648924+moT01@users.noreply.github.com> --- client/gatsby-node.js | 2 + client/src/redux/prop-types.ts | 3 +- .../Introduction/components/block.tsx | 48 +++++++++++++++---- .../Introduction/super-block-intro.tsx | 1 + curriculum/get-challenges.js | 1 + .../challenge-schema.test.js.snap | 9 ++++ curriculum/schema/challenge-schema.js | 9 ++++ curriculum/schema/meta-schema.js | 6 +++ shared/config/blocks.ts | 30 ++++++++++++ 9 files changed, 99 insertions(+), 10 deletions(-) diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 30a2849bce3..80b49779655 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -74,6 +74,7 @@ exports.createPages = async function createPages({ challenge { block blockType + blockLayout certification challengeType dashedName @@ -260,6 +261,7 @@ exports.createSchemaCustomization = ({ actions }) => { } type Challenge { blockType: String + blockLayout: String challengeFiles: [FileContents] explanation: String notes: String diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index ec19f5e5199..77b8582634f 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -1,6 +1,6 @@ import { HandlerProps } from 'react-reflex'; import { SuperBlocks } from '../../../shared/config/curriculum'; -import { BlockTypes } from '../../../shared/config/blocks'; +import { BlockLayouts, BlockTypes } from '../../../shared/config/blocks'; import { Themes } from '../components/settings/theme'; import { type CertTitle } from '../../config/cert-and-project-map'; @@ -167,6 +167,7 @@ export type ChallengeNode = { challenge: { block: string; blockType: BlockTypes; + blockLayout: BlockLayouts; certification: string; challengeOrder: number; challengeType: number; diff --git a/client/src/templates/Introduction/components/block.tsx b/client/src/templates/Introduction/components/block.tsx index 184a16674cc..11af9cbe260 100644 --- a/client/src/templates/Introduction/components/block.tsx +++ b/client/src/templates/Introduction/components/block.tsx @@ -20,7 +20,7 @@ import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types'; import { playTone } from '../../../utils/tone'; import { makeExpandedBlockSelector, toggleBlock } from '../redux'; import { isGridBased, isProjectBased } from '../../../utils/curriculum-layout'; -import { BlockTypes } from '../../../../../shared/config/blocks'; +import { BlockLayouts, BlockTypes } from '../../../../../shared/config/blocks'; import Challenges from './challenges'; import BlockLabel from './block-label'; @@ -151,7 +151,12 @@ class Block extends Component { ); - const Block = ( + /** + * ChallengeListBlock displays challenges in a list. + * This layout is used in backend blocks, The Odin Project blocks, and blocks in legacy certification. + * Example: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/#basic-javascript + */ + const ChallengeListBlock = ( <> {' '} @@ -209,7 +214,12 @@ class Block extends Component { ); - const ProjectBlock = ( + /** + * ProjectListBlock displays a list of certification projects. + * This layout is used in legacy certifications. + * Example: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/#javascript-algorithms-and-data-structures-projects + */ + const ProjectListBlock = ( <>
@@ -247,7 +257,12 @@ class Block extends Component { return `${percentageCompleted}% ${t('learn.completed')}`; }; - const GridBlock = ( + /** + * ChallengeGridBlock displays challenges in a grid. + * This layout is used for step-based blocks. + * Example: https://www.freecodecamp.org/learn/2022/responsive-web-design/#learn-html-by-building-a-cat-photo-app + */ + const ChallengeGridBlock = ( <> {' '} @@ -304,7 +319,12 @@ class Block extends Component { ); - const GridProjectBlock = ( + /** + * LinkBlock displays the block as a single link. + * This layout is used if the block has a single challenge. + * Example: https://www.freecodecamp.org/learn/2022/responsive-web-design/#build-a-survey-form-project + */ + const LinkBlock = (
@@ -346,14 +366,24 @@ class Block extends Component { ); - const blockrenderer = () => { - if (isProjectBlock) return isGridBlock ? GridProjectBlock : ProjectBlock; - return isGridBlock ? GridBlock : Block; + const blockRenderer = () => { + const blockLayout = challenges[0].blockLayout; + + // `blockLayout` property isn't available in all challenges + if (!blockLayout) { + if (isProjectBlock) return isGridBlock ? LinkBlock : ProjectListBlock; + return isGridBlock ? ChallengeGridBlock : ChallengeListBlock; + } + + if (blockLayout === BlockLayouts.ChallengeGrid) return ChallengeGridBlock; + if (blockLayout === BlockLayouts.ChallengeList) return ChallengeListBlock; + if (blockLayout === BlockLayouts.Link) return LinkBlock; + if (blockLayout === BlockLayouts.ProjectList) return ProjectListBlock; }; return ( <> - {blockrenderer()} + {blockRenderer()} {isGridBlock && !isProjectBlock ? null : } ); diff --git a/client/src/templates/Introduction/super-block-intro.tsx b/client/src/templates/Introduction/super-block-intro.tsx index 22ec3f286e8..45562fc53ec 100644 --- a/client/src/templates/Introduction/super-block-intro.tsx +++ b/client/src/templates/Introduction/super-block-intro.tsx @@ -305,6 +305,7 @@ export const query = graphql` order superBlock dashedName + blockLayout } } } diff --git a/curriculum/get-challenges.js b/curriculum/get-challenges.js index 3f7cc8bcd17..6f4c103d17c 100644 --- a/curriculum/get-challenges.js +++ b/curriculum/get-challenges.js @@ -287,6 +287,7 @@ function generateChallengeCreator(lang, englishPath, i18nPath) { challenge.block = meta.dashedName; challenge.blockType = meta.blockType; + challenge.blockLayout = meta.blockLayout; challenge.hasEditableBoundaries = !!meta.hasEditableBoundaries; challenge.order = meta.order; // const superOrder = getSuperOrder(meta.superBlock); diff --git a/curriculum/schema/__snapshots__/challenge-schema.test.js.snap b/curriculum/schema/__snapshots__/challenge-schema.test.js.snap index 9016c5d333f..b5992f48be9 100644 --- a/curriculum/schema/__snapshots__/challenge-schema.test.js.snap +++ b/curriculum/schema/__snapshots__/challenge-schema.test.js.snap @@ -132,6 +132,15 @@ const schema = Joi.object() ).required(), otherwise: Joi.valid(null) }), + blockLayout: Joi.when('superBlock', { + is: [SuperBlocks.FrontEndDevelopment], + then: Joi.valid( + 'challenge-list', + 'challenge-grid', + 'link', + 'project-list' + ) + }), challengeOrder: Joi.number(), certification: Joi.string().regex(slugWithSlashRE), challengeType: Joi.number().min(0).max(23).required(), diff --git a/curriculum/schema/challenge-schema.js b/curriculum/schema/challenge-schema.js index 43ca8dc7774..b7ccd595d06 100644 --- a/curriculum/schema/challenge-schema.js +++ b/curriculum/schema/challenge-schema.js @@ -129,6 +129,15 @@ const schema = Joi.object() ).required(), otherwise: Joi.valid(null) }), + blockLayout: Joi.when('superBlock', { + is: [SuperBlocks.FrontEndDevelopment], + then: Joi.valid( + 'challenge-list', + 'challenge-grid', + 'link', + 'project-list' + ) + }), challengeOrder: Joi.number(), certification: Joi.string().regex(slugWithSlashRE), challengeType: Joi.number().min(0).max(23).required(), diff --git a/curriculum/schema/meta-schema.js b/curriculum/schema/meta-schema.js index 0be69d53776..1ec0d4a5b59 100644 --- a/curriculum/schema/meta-schema.js +++ b/curriculum/schema/meta-schema.js @@ -7,6 +7,12 @@ const schema = Joi.object() .keys({ name: Joi.string().required(), blockType: Joi.valid('workshop', 'lab', 'lecture', 'quiz', 'exam'), + blockLayout: Joi.valid( + 'challenge-list', + 'challenge-grid', + 'link', + 'project-list' + ), isUpcomingChange: Joi.boolean().required(), dashedName: Joi.string().regex(slugRE).required(), superBlock: Joi.string().regex(slugWithSlashRE).required(), diff --git a/shared/config/blocks.ts b/shared/config/blocks.ts index 15b61094b6e..da2f742db0d 100644 --- a/shared/config/blocks.ts +++ b/shared/config/blocks.ts @@ -6,3 +6,33 @@ export enum BlockTypes { quiz = 'quiz', exam = 'exam' } + +export enum BlockLayouts { + /** + * ChallengeList displays challenges in a list. + * This layout is used in backend blocks, The Odin Project blocks, and blocks in legacy certification. + * Example: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/#basic-javascript + */ + ChallengeList = 'challenge-list', + + /** + * ChallengeGrid displays challenges in a grid. + * This layout is used for step-based blocks. + * Example: https://www.freecodecamp.org/learn/2022/responsive-web-design/#learn-html-by-building-a-cat-photo-app + */ + ChallengeGrid = 'challenge-grid', + + /** + * Link displays the block as a single link. + * This layout is used if the block has a single challenge. + * Example: https://www.freecodecamp.org/learn/2022/responsive-web-design/#build-a-survey-form-project + */ + Link = 'link', + + /** + * ProjectList displays a list of certification projects. + * This layout is used in legacy certifications. + * Example: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/#javascript-algorithms-and-data-structures-projects + */ + ProjectList = 'project-list' +}