mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
chore(tools): display chapters in challenge editor (#62050)
This commit is contained in:
@@ -30,3 +30,14 @@ export const CHALLENGE_DIR = join(
|
|||||||
'english',
|
'english',
|
||||||
'blocks'
|
'blocks'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ENGLISH_LANG_DIR = join(
|
||||||
|
process.cwd(),
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'client',
|
||||||
|
'i18n',
|
||||||
|
'locales',
|
||||||
|
'english'
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
interface SuperBlock {
|
||||||
|
title: string;
|
||||||
|
intro: string[];
|
||||||
|
blocks: string[];
|
||||||
|
modules?: string[];
|
||||||
|
chapters?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Intro {
|
||||||
|
[key: string]: SuperBlock;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { getBlocks } from '../utils/get-full-stack-blocks';
|
||||||
|
|
||||||
|
export const moduleBlockRoute = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> => {
|
||||||
|
const { superblock, chapter, module } = req.params;
|
||||||
|
|
||||||
|
const steps = await getBlocks(superblock, chapter, module);
|
||||||
|
|
||||||
|
res.json(steps);
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { getModules } from '../utils/get-full-stack-blocks';
|
||||||
|
|
||||||
|
export const moduleRoute = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> => {
|
||||||
|
const { superblock, chapter } = req.params;
|
||||||
|
|
||||||
|
const steps = await getModules(superblock, chapter);
|
||||||
|
|
||||||
|
res.json(steps);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { getStepContent } from '../utils/get-step-contents';
|
||||||
|
|
||||||
|
export const stepRoute = async (req: Request, res: Response): Promise<void> => {
|
||||||
|
const { superblock, block, step } = req.params;
|
||||||
|
|
||||||
|
const stepContents = await getStepContent(superblock, block, step);
|
||||||
|
res.json(stepContents);
|
||||||
|
};
|
||||||
@@ -9,6 +9,8 @@ import { saveRoute } from './routes/save-route';
|
|||||||
import { stepRoute } from './routes/step-route';
|
import { stepRoute } from './routes/step-route';
|
||||||
import { superblockRoute } from './routes/super-block-route';
|
import { superblockRoute } from './routes/super-block-route';
|
||||||
import { toolsRoute } from './routes/tools-route';
|
import { toolsRoute } from './routes/tools-route';
|
||||||
|
import { moduleRoute } from './routes/module-route';
|
||||||
|
import { moduleBlockRoute } from './routes/module-block-route';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -29,6 +31,14 @@ app.post('/:superblock/:block/:step', (req, res, next) => {
|
|||||||
saveRoute(req, res).catch(next);
|
saveRoute(req, res).catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get(`/:superblock/chapters/:chapter`, (req, res, next) => {
|
||||||
|
moduleRoute(req, res).catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get(`/:superblock/chapters/:chapter/modules/:module`, (req, res, next) => {
|
||||||
|
moduleBlockRoute(req, res).catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/:superblock/:block/:step', (req, res, next) => {
|
app.get('/:superblock/:block/:step', (req, res, next) => {
|
||||||
stepRoute(req, res).catch(next);
|
stepRoute(req, res).catch(next);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,50 +1,69 @@
|
|||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { SUPERBLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
|
import {
|
||||||
|
SUPERBLOCK_META_DIR,
|
||||||
|
BLOCK_META_DIR,
|
||||||
|
ENGLISH_LANG_DIR
|
||||||
|
} from '../configs/paths';
|
||||||
|
|
||||||
import { SuperBlockMeta } from '../interfaces/superblock-meta';
|
import { SuperBlockMeta } from '../interfaces/superblock-meta';
|
||||||
|
import { PartialMeta } from '../interfaces/partial-meta';
|
||||||
|
|
||||||
|
import { Intro } from '../interfaces/intro';
|
||||||
|
|
||||||
type Block = {
|
type Block = {
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBlocks = async (sup: string): Promise<Block[]> => {
|
type BlockLocation = {
|
||||||
|
blocks: Block[];
|
||||||
|
currentSuperBlock: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chapterBasedSuperBlocks = ['full-stack-developer'];
|
||||||
|
|
||||||
|
export const getBlocks = async (sup: string): Promise<BlockLocation> => {
|
||||||
const superBlockDataPath = join(SUPERBLOCK_META_DIR, sup + '.json');
|
const superBlockDataPath = join(SUPERBLOCK_META_DIR, sup + '.json');
|
||||||
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
||||||
encoding: 'utf8'
|
encoding: 'utf8'
|
||||||
});
|
});
|
||||||
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
||||||
|
|
||||||
|
const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json');
|
||||||
|
const introFile = await readFile(introDataPath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
const introData = JSON.parse(introFile) as Intro;
|
||||||
|
|
||||||
let blocks: { name: string; path: string }[] = [];
|
let blocks: { name: string; path: string }[] = [];
|
||||||
|
|
||||||
if (sup === 'full-stack-developer') {
|
if (chapterBasedSuperBlocks.includes(sup)) {
|
||||||
const moduleBlockData = await Promise.all(
|
blocks = superBlockMeta.chapters!.map(chapter => {
|
||||||
superBlockMeta.chapters!.flatMap(async chapter => {
|
const chapters = Object.entries(introData[sup]['chapters']!);
|
||||||
return await Promise.all(
|
const chapterTrueName = chapters.filter(
|
||||||
chapter.modules.flatMap(async module => {
|
x => x[0] === chapter.dashedName
|
||||||
return module.blocks!.flatMap(block => {
|
)[0][1];
|
||||||
const filePath = join(CHALLENGE_DIR, block);
|
return {
|
||||||
return {
|
name: chapterTrueName,
|
||||||
name: block,
|
path: 'chapters/' + chapter.dashedName
|
||||||
path: filePath
|
};
|
||||||
};
|
});
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
blocks = moduleBlockData.flat().flat();
|
|
||||||
} else {
|
} else {
|
||||||
blocks = await Promise.all(
|
blocks = await Promise.all(
|
||||||
superBlockMeta.blocks!.map(async block => {
|
superBlockMeta.blocks!.map(async block => {
|
||||||
const filePath = join(CHALLENGE_DIR, block);
|
const blockStructurePath = join(BLOCK_META_DIR, block + '.json');
|
||||||
|
const blockMetaFile = await readFile(blockStructurePath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
const blockMeta = JSON.parse(blockMetaFile) as PartialMeta;
|
||||||
return {
|
return {
|
||||||
name: block,
|
name: blockMeta.name,
|
||||||
path: filePath
|
path: block
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return { blocks: blocks, currentSuperBlock: introData[sup].title };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,58 +1,133 @@
|
|||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { SUPERBLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
|
import {
|
||||||
|
SUPERBLOCK_META_DIR,
|
||||||
|
BLOCK_META_DIR,
|
||||||
|
ENGLISH_LANG_DIR
|
||||||
|
} from '../configs/paths';
|
||||||
import { SuperBlockMeta } from '../interfaces/superblock-meta';
|
import { SuperBlockMeta } from '../interfaces/superblock-meta';
|
||||||
|
import { PartialMeta } from '../interfaces/partial-meta';
|
||||||
|
import { Intro } from '../interfaces/intro';
|
||||||
|
|
||||||
type Block = {
|
type Block = {
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getModules = async (chap: string): Promise<string[]> => {
|
type Module = {
|
||||||
const superBlockDataPath = join(
|
name: string;
|
||||||
SUPERBLOCK_META_DIR,
|
path: string;
|
||||||
'full-stack-developer' + '.json'
|
};
|
||||||
);
|
|
||||||
|
type BlockLocation = {
|
||||||
|
blocks: Block[];
|
||||||
|
currentModule: string;
|
||||||
|
currentChapter: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModuleLocation = {
|
||||||
|
modules: Module[];
|
||||||
|
currentChapter: string;
|
||||||
|
currentSuperBlock: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getModules = async (
|
||||||
|
superBlock: string,
|
||||||
|
chap: string
|
||||||
|
): Promise<ModuleLocation> => {
|
||||||
|
const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json');
|
||||||
|
|
||||||
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
||||||
encoding: 'utf8'
|
encoding: 'utf8'
|
||||||
});
|
});
|
||||||
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
||||||
|
|
||||||
|
const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json');
|
||||||
|
const introFile = await readFile(introDataPath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
const introData = JSON.parse(introFile) as Intro;
|
||||||
|
|
||||||
|
const chapters = Object.entries(introData[superBlock]['chapters']!);
|
||||||
|
|
||||||
const chapter = superBlockMeta.chapters!.filter(
|
const chapter = superBlockMeta.chapters!.filter(
|
||||||
x => x.dashedName === chap
|
x => x.dashedName === chap
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
return await Promise.all(
|
const chapterTrueName = chapters.filter(x => x[0] === chap)[0][1];
|
||||||
chapter.modules!.map(async module => module.dashedName)
|
|
||||||
|
let modules: Module[] = [];
|
||||||
|
|
||||||
|
modules = await Promise.all(
|
||||||
|
chapter.modules!.map(module => {
|
||||||
|
const modules = Object.entries(introData[superBlock]['modules']!);
|
||||||
|
const moduleTrueName = modules.filter(
|
||||||
|
x => x[0] === module.dashedName
|
||||||
|
)[0][1];
|
||||||
|
return { name: moduleTrueName, path: 'modules/' + module.dashedName };
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
modules: modules,
|
||||||
|
currentChapter: chapterTrueName,
|
||||||
|
currentSuperBlock: introData[superBlock].title
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBlocks = async (module: string): Promise<Block[]> => {
|
export const getBlocks = async (
|
||||||
const superBlockDataPath = join(
|
superBlock: string,
|
||||||
SUPERBLOCK_META_DIR,
|
chapterName: string,
|
||||||
'full-stack-developer' + '.json'
|
moduleName: string
|
||||||
);
|
): Promise<BlockLocation> => {
|
||||||
|
const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json');
|
||||||
|
|
||||||
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
const superBlockMetaFile = await readFile(superBlockDataPath, {
|
||||||
encoding: 'utf8'
|
encoding: 'utf8'
|
||||||
});
|
});
|
||||||
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
|
||||||
|
|
||||||
const foundModule = superBlockMeta
|
const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json');
|
||||||
.chapters!.flatMap(x => x.modules)
|
const introFile = await readFile(introDataPath, {
|
||||||
.filter(x => x.dashedName === module)[0];
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
const introData = JSON.parse(introFile) as Intro;
|
||||||
|
|
||||||
|
const modules = Object.entries(introData[superBlock]['modules']!);
|
||||||
|
|
||||||
|
const moduleTrueName = modules.filter(x => x[0] === moduleName)[0][1];
|
||||||
|
|
||||||
|
const chapters = Object.entries(introData[superBlock]['chapters']!);
|
||||||
|
|
||||||
|
const chapterTrueName = chapters.filter(x => x[0] === chapterName)[0][1];
|
||||||
|
|
||||||
|
const foundChapter = superBlockMeta.chapters?.filter(
|
||||||
|
chapter => chapter.dashedName === chapterName
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const foundModule = foundChapter?.modules.filter(
|
||||||
|
module => module.dashedName === moduleName
|
||||||
|
)[0];
|
||||||
|
|
||||||
let blocks: { name: string; path: string }[] = [];
|
let blocks: { name: string; path: string }[] = [];
|
||||||
|
|
||||||
blocks = await Promise.all(
|
blocks = await Promise.all(
|
||||||
foundModule.blocks!.map(async block => {
|
foundModule!.blocks!.map(async block => {
|
||||||
const filePath = join(CHALLENGE_DIR, block);
|
const blockStructurePath = join(BLOCK_META_DIR, block + '.json');
|
||||||
|
const blockMetaFile = await readFile(blockStructurePath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
const blockMeta = JSON.parse(blockMetaFile) as PartialMeta;
|
||||||
return {
|
return {
|
||||||
name: block,
|
name: blockMeta.name,
|
||||||
path: filePath
|
path: block
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return blocks;
|
|
||||||
|
return {
|
||||||
|
blocks: blocks,
|
||||||
|
currentModule: moduleTrueName,
|
||||||
|
currentChapter: chapterTrueName
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { join } from 'path';
|
|||||||
import matter from 'gray-matter';
|
import matter from 'gray-matter';
|
||||||
|
|
||||||
import { PartialMeta } from '../interfaces/partial-meta';
|
import { PartialMeta } from '../interfaces/partial-meta';
|
||||||
import { BLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
|
import {
|
||||||
|
BLOCK_META_DIR,
|
||||||
|
CHALLENGE_DIR,
|
||||||
|
ENGLISH_LANG_DIR
|
||||||
|
} from '../configs/paths';
|
||||||
|
import { Intro } from '../interfaces/intro';
|
||||||
|
|
||||||
const getFileOrder = (id: string, meta: PartialMeta) => {
|
const getFileOrder = (id: string, meta: PartialMeta) => {
|
||||||
return meta.challengeOrder.findIndex(({ id: f }) => f === id);
|
return meta.challengeOrder.findIndex(({ id: f }) => f === id);
|
||||||
@@ -16,7 +21,16 @@ type Step = {
|
|||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
|
type StepLocation = {
|
||||||
|
steps: Step[];
|
||||||
|
currentBlock: string;
|
||||||
|
currentSuperBlock: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSteps = async (
|
||||||
|
sup: string,
|
||||||
|
block: string
|
||||||
|
): Promise<StepLocation> => {
|
||||||
//const superMetaPath = join(SUPERBLOCK_META_DIR, sup + ".json");
|
//const superMetaPath = join(SUPERBLOCK_META_DIR, sup + ".json");
|
||||||
|
|
||||||
//const superMetaData = JSON.parse(
|
//const superMetaData = JSON.parse(
|
||||||
@@ -27,6 +41,13 @@ export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
|
|||||||
|
|
||||||
const blockFolderPath = join(BLOCK_META_DIR, block + '.json');
|
const blockFolderPath = join(BLOCK_META_DIR, block + '.json');
|
||||||
|
|
||||||
|
const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json');
|
||||||
|
const introFile = await readFile(introDataPath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
|
||||||
|
const introData = JSON.parse(introFile) as Intro;
|
||||||
|
|
||||||
const blockMetaData = JSON.parse(
|
const blockMetaData = JSON.parse(
|
||||||
await readFile(blockFolderPath, { encoding: 'utf8' })
|
await readFile(blockFolderPath, { encoding: 'utf8' })
|
||||||
) as PartialMeta;
|
) as PartialMeta;
|
||||||
@@ -47,8 +68,14 @@ export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return stepData.sort(
|
const steps = stepData.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
getFileOrder(a.id, blockMetaData) - getFileOrder(b.id, blockMetaData)
|
getFileOrder(a.id, blockMetaData) - getFileOrder(b.id, blockMetaData)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
steps: steps,
|
||||||
|
currentBlock: blockMetaData.name,
|
||||||
|
currentSuperBlock: introData[sup].title
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,3 +2,14 @@ export interface Block {
|
|||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlocksWithSuperBlock {
|
||||||
|
blocks: Block[];
|
||||||
|
currentSuperBlock: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlocksWithModule {
|
||||||
|
blocks: Block[];
|
||||||
|
currentModule: string;
|
||||||
|
currentChapter: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ export interface ChallengeData {
|
|||||||
id: string;
|
id: string;
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChallengeDataWithBlock {
|
||||||
|
steps: ChallengeData[];
|
||||||
|
currentBlock: string;
|
||||||
|
currentSuperBlock: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Module {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChaptersWithLocation {
|
||||||
|
modules: Module[];
|
||||||
|
currentSuperBlock: string;
|
||||||
|
currentChapter: string;
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import SuperBlock from './components/superblock/super-block';
|
|||||||
import Block from './components/block/block';
|
import Block from './components/block/block';
|
||||||
import Editor from './components/editor/editor';
|
import Editor from './components/editor/editor';
|
||||||
import Tools from './components/tools/tools';
|
import Tools from './components/tools/tools';
|
||||||
|
import ChapterLanding from './components/chapter/chapter';
|
||||||
|
import ModuleLanding from './components/module/module';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
@@ -16,6 +18,14 @@ const App = () => {
|
|||||||
<Route index element={<Landing />} />
|
<Route index element={<Landing />} />
|
||||||
<Route path=':superblock' element={<SuperBlock />} />
|
<Route path=':superblock' element={<SuperBlock />} />
|
||||||
<Route path=':superblock/:block' element={<Block />} />
|
<Route path=':superblock/:block' element={<Block />} />
|
||||||
|
<Route
|
||||||
|
path=':superblock/chapters/:chapter'
|
||||||
|
element={<ChapterLanding />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path=':superblock/chapters/:chapter/modules/:module'
|
||||||
|
element={<ModuleLanding />}
|
||||||
|
/>
|
||||||
<Route path=':superblock/:block/_tools' element={<Tools />} />
|
<Route path=':superblock/:block/_tools' element={<Tools />} />
|
||||||
<Route path=':superblock/:block/:challenge' element={<Editor />} />
|
<Route path=':superblock/:block/:challenge' element={<Editor />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { ChallengeData } from '../../../interfaces/challenge-data';
|
import {
|
||||||
|
ChallengeData,
|
||||||
|
ChallengeDataWithBlock
|
||||||
|
} from '../../../interfaces/challenge-data';
|
||||||
import { API_LOCATION } from '../../utils/handle-request';
|
import { API_LOCATION } from '../../utils/handle-request';
|
||||||
import './block.css';
|
import './block.css';
|
||||||
|
|
||||||
@@ -24,6 +27,8 @@ const Block = () => {
|
|||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [items, setItems] = useState([] as ChallengeData[]);
|
const [items, setItems] = useState([] as ChallengeData[]);
|
||||||
|
const [blockName, setBlockName] = useState('');
|
||||||
|
const [superBlockName, setSuperBlockName] = useState('');
|
||||||
const params = useParams() as { superblock: string; block: string };
|
const params = useParams() as { superblock: string; block: string };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -34,11 +39,13 @@ const Block = () => {
|
|||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetch(`${API_LOCATION}/${params.superblock}/${params.block}`)
|
fetch(`${API_LOCATION}/${params.superblock}/${params.block}`)
|
||||||
.then(res => res.json() as Promise<ChallengeData[]>)
|
.then(res => res.json() as Promise<ChallengeDataWithBlock>)
|
||||||
.then(
|
.then(
|
||||||
superblocks => {
|
superblocks => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setItems(superblocks);
|
setItems(superblocks.steps);
|
||||||
|
setBlockName(superblocks.currentBlock);
|
||||||
|
setSuperBlockName(superblocks.currentSuperBlock);
|
||||||
},
|
},
|
||||||
(error: Error) => {
|
(error: Error) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -64,8 +71,8 @@ const Block = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{params.block}</h1>
|
<h1>{blockName}</h1>
|
||||||
<span className='breadcrumb'>{params.superblock}</span>
|
<span className='breadcrumb'>{superBlockName}</span>
|
||||||
<ul className='step-grid'>
|
<ul className='step-grid'>
|
||||||
{items.map((challenge, i) => (
|
{items.map((challenge, i) => (
|
||||||
<li key={challenge.name}>
|
<li key={challenge.name}>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.step-grid {
|
||||||
|
column-count: 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
ChallengeData,
|
||||||
|
ChallengeDataWithBlock
|
||||||
|
} from '../../../interfaces/challenge-data';
|
||||||
|
import { API_LOCATION } from '../../utils/handle-request';
|
||||||
|
import './chapter-block.css';
|
||||||
|
|
||||||
|
const stepBasedSuperblocks = [
|
||||||
|
'scientific-computing-with-python',
|
||||||
|
'responsive-web-design-22',
|
||||||
|
'javascript-algorithms-and-data-structures-22',
|
||||||
|
'front-end-development'
|
||||||
|
];
|
||||||
|
|
||||||
|
const taskBasedSuperblocks = [
|
||||||
|
'a2-english-for-developers',
|
||||||
|
'b1-english-for-developers',
|
||||||
|
'a2-professional-spanish',
|
||||||
|
'a2-professional-chinese',
|
||||||
|
'a1-professional-chinese'
|
||||||
|
];
|
||||||
|
|
||||||
|
const ChapterBasedBlock = () => {
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [blockName, setBlockName] = useState('');
|
||||||
|
const [superBlockName, setSuperBlockName] = useState('');
|
||||||
|
const [items, setItems] = useState([] as ChallengeData[]);
|
||||||
|
const params = useParams() as {
|
||||||
|
superblock: string;
|
||||||
|
chapter: string;
|
||||||
|
module: string;
|
||||||
|
block: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
fetch(`${API_LOCATION}/${params.superblock}/${params.block}`)
|
||||||
|
.then(res => res.json() as Promise<ChallengeDataWithBlock>)
|
||||||
|
.then(
|
||||||
|
superblocks => {
|
||||||
|
setLoading(false);
|
||||||
|
setItems(superblocks.steps);
|
||||||
|
setBlockName(superblocks.currentBlock);
|
||||||
|
setSuperBlockName(superblocks.currentSuperBlock);
|
||||||
|
},
|
||||||
|
(error: Error) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Error: {error.message}</div>;
|
||||||
|
}
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isStepBasedSuperblock = stepBasedSuperblocks.includes(
|
||||||
|
params.superblock
|
||||||
|
);
|
||||||
|
|
||||||
|
const isTaskBasedSuperblock = taskBasedSuperblocks.includes(
|
||||||
|
params.superblock
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{blockName}</h1>
|
||||||
|
<span className='breadcrumb'>{superBlockName}</span>
|
||||||
|
<ul className='step-grid'>
|
||||||
|
{items.map((challenge, i) => (
|
||||||
|
<li key={challenge.name}>
|
||||||
|
{!isStepBasedSuperblock && <span>{`${i + 1}: `}</span>}
|
||||||
|
<Link
|
||||||
|
to={`/${params.superblock}/${params.block}/${challenge.path}`}
|
||||||
|
>
|
||||||
|
{challenge.name}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<Link to={`/${params.superblock}`}>Return to Blocks</Link>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<h2>Project Controls</h2>
|
||||||
|
{isStepBasedSuperblock ? (
|
||||||
|
<p>
|
||||||
|
Looking to add, remove, or edit steps?{' '}
|
||||||
|
<Link to={`/${params.superblock}/${params.block}/_tools`}>
|
||||||
|
Use the step tools.
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
) : isTaskBasedSuperblock ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Looking to add or remove challenges? Navigate to <br />
|
||||||
|
<code>
|
||||||
|
freeCodeCamp/curriculum/challenges/english
|
||||||
|
{`/${params.block}/`}
|
||||||
|
</code>
|
||||||
|
<br />
|
||||||
|
in your terminal and run the following commands:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>pnpm create-next-task</code>: Create the next task style
|
||||||
|
challenge in this block
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm create-next-challenge</code>: Create the next challenge
|
||||||
|
of a different style in this block
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm insert-task</code>: Create a new task style challenge
|
||||||
|
in the middle of this block.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm delete-task</code>: Delete a task style challenge in
|
||||||
|
this block.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm reorder-tasks</code>: Rename the tasks to the correct
|
||||||
|
order.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Refresh the page after running a command to see the changes
|
||||||
|
reflected.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Looking to add or remove challenges? Navigate to <br />
|
||||||
|
<code>
|
||||||
|
freeCodeCamp/curriculum/challenges/english
|
||||||
|
{`/${params.superblock}/${params.block}/`}
|
||||||
|
</code>
|
||||||
|
<br />
|
||||||
|
in your terminal and run the following commands:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>pnpm create-next-challenge</code>: Create a new challenge at
|
||||||
|
the end of this block.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm insert-challenge</code>: Create a new challenge in the
|
||||||
|
middle of this block.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>pnpm delete-challenge</code>: Delete a challenge in this
|
||||||
|
block.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Refresh the page after running a command to see the changes
|
||||||
|
reflected.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChapterBasedBlock;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.step-grid {
|
||||||
|
column-count: 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import { API_LOCATION } from '../../utils/handle-request';
|
||||||
|
import { Module, ChaptersWithLocation } from '../../../interfaces/chapter';
|
||||||
|
|
||||||
|
const ChapterLanding = () => {
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [items, setItems] = useState([] as Module[]);
|
||||||
|
const [chapterName, setChapterName] = useState('');
|
||||||
|
const [superBlockName, setSuperBlockName] = useState('');
|
||||||
|
const params = useParams() as { superblock: string; chapter: string };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
fetch(`${API_LOCATION}/${params.superblock}/chapters/${params.chapter}`)
|
||||||
|
.then(res => res.json() as Promise<ChaptersWithLocation>)
|
||||||
|
.then(
|
||||||
|
blockData => {
|
||||||
|
setLoading(false);
|
||||||
|
setItems(blockData.modules);
|
||||||
|
setChapterName(blockData.currentChapter);
|
||||||
|
setSuperBlockName(blockData.currentSuperBlock);
|
||||||
|
},
|
||||||
|
(error: Error) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Error: {error.message}</div>;
|
||||||
|
}
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{chapterName}</h1>
|
||||||
|
<ul>
|
||||||
|
{items.map(chapter => (
|
||||||
|
<li key={chapter.name}>
|
||||||
|
<Link
|
||||||
|
to={`/${params.superblock}/chapters/${params.chapter}/${chapter.path}`}
|
||||||
|
>
|
||||||
|
{chapter.name}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<Link to={`/${params.superblock}`}>Return to {superBlockName}</Link>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<h2>Create New Project</h2>
|
||||||
|
<p>
|
||||||
|
Want to create a new project? Open your terminal and run{' '}
|
||||||
|
<code>pnpm run create-new-project</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChapterLanding;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.step-grid {
|
||||||
|
column-count: 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import { API_LOCATION } from '../../utils/handle-request';
|
||||||
|
import { Block, BlocksWithModule } from '../../../interfaces/block';
|
||||||
|
|
||||||
|
const ModuleLanding = () => {
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [items, setItems] = useState([] as Block[]);
|
||||||
|
const [moduleName, setModuleName] = useState('');
|
||||||
|
const [chapterName, setChapterName] = useState('');
|
||||||
|
const params = useParams() as {
|
||||||
|
superblock: string;
|
||||||
|
chapter: string;
|
||||||
|
module: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
fetch(
|
||||||
|
`${API_LOCATION}/${params.superblock}/chapters/${params.chapter}/modules/${params.module}`
|
||||||
|
)
|
||||||
|
.then(res => res.json() as Promise<BlocksWithModule>)
|
||||||
|
.then(
|
||||||
|
moduleData => {
|
||||||
|
setLoading(false);
|
||||||
|
setItems(moduleData.blocks);
|
||||||
|
setModuleName(moduleData.currentModule);
|
||||||
|
setChapterName(moduleData.currentChapter);
|
||||||
|
},
|
||||||
|
(error: Error) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Error: {error.message}</div>;
|
||||||
|
}
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{moduleName}</h1>
|
||||||
|
<ul>
|
||||||
|
{items.map(block => (
|
||||||
|
<li key={block.path}>
|
||||||
|
<Link to={`/${params.superblock}/${block.path}`}>{block.name}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<Link to={`/${params.superblock}/chapters/${params.chapter}`}>
|
||||||
|
Return to {chapterName}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<h2>Create New Project</h2>
|
||||||
|
<p>
|
||||||
|
Want to create a new project? Open your terminal and run{' '}
|
||||||
|
<code>pnpm run create-new-project</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModuleLanding;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { Block } from '../../../interfaces/block';
|
import { Block, BlocksWithSuperBlock } from '../../../interfaces/block';
|
||||||
import { API_LOCATION } from '../../utils/handle-request';
|
import { API_LOCATION } from '../../utils/handle-request';
|
||||||
|
|
||||||
const SuperBlock = () => {
|
const SuperBlock = () => {
|
||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [items, setItems] = useState([] as Block[]);
|
const [items, setItems] = useState([] as Block[]);
|
||||||
|
const [superBlockName, setSuperBlockName] = useState('');
|
||||||
const params = useParams() as { superblock: string; block: string };
|
const params = useParams() as { superblock: string; block: string };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -17,11 +18,12 @@ const SuperBlock = () => {
|
|||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetch(`${API_LOCATION}/${params.superblock}`)
|
fetch(`${API_LOCATION}/${params.superblock}`)
|
||||||
.then(res => res.json() as Promise<Block[]>)
|
.then(res => res.json() as Promise<BlocksWithSuperBlock>)
|
||||||
.then(
|
.then(
|
||||||
blocks => {
|
blockData => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setItems(blocks);
|
setItems(blockData.blocks);
|
||||||
|
setSuperBlockName(blockData.currentSuperBlock);
|
||||||
},
|
},
|
||||||
(error: Error) => {
|
(error: Error) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -38,11 +40,11 @@ const SuperBlock = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{params.superblock}</h1>
|
<h1>{superBlockName}</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{items.map(block => (
|
{items.map(block => (
|
||||||
<li key={block.name}>
|
<li key={block.name}>
|
||||||
<Link to={`/${params.superblock}/${block.name}`}>{block.name}</Link>
|
<Link to={`/${params.superblock}/${block.path}`}>{block.name}</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user