From 79087ca9fd352c67c92dd04566baf08d4a0c801d Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Mon, 26 Jan 2026 12:27:08 +0100 Subject: [PATCH] refactor(tools): replace challenge-editor with submodule (#65459) --- .gitmodules | 4 + .prettierignore | 1 + package.json | 5 +- pnpm-workspace.yaml | 2 - tools/challenge-editor | 1 + tools/challenge-editor/api/.lintstagedrc.mjs | 4 - tools/challenge-editor/api/configs/paths.ts | 43 ----- .../api/configs/super-block-list.ts | 118 ------------ tools/challenge-editor/api/eslint.config.mjs | 3 - .../api/interfaces/challenge-data.ts | 5 - .../challenge-editor/api/interfaces/intro.ts | 11 -- .../api/interfaces/partial-meta.ts | 5 - .../api/interfaces/superblock-meta.ts | 14 -- .../challenge-editor/api/interfaces/tools.ts | 16 -- tools/challenge-editor/api/package.json | 29 --- tools/challenge-editor/api/reset.d.ts | 1 - .../api/routes/block-route.ts | 13 -- .../api/routes/index-route.ts | 6 - .../api/routes/module-block-route.ts | 13 -- .../api/routes/module-route.ts | 13 -- .../api/routes/module-step-route.ts | 9 - .../challenge-editor/api/routes/save-route.ts | 15 -- .../challenge-editor/api/routes/step-route.ts | 9 - .../api/routes/super-block-route.ts | 13 -- .../api/routes/tools-route.ts | 56 ------ tools/challenge-editor/api/sample.env | 1 - tools/challenge-editor/api/server.ts | 56 ------ tools/challenge-editor/api/tsconfig.json | 3 - .../challenge-editor/api/utils/get-blocks.ts | 69 ------- .../api/utils/get-full-stack-blocks.ts | 131 ------------- .../api/utils/get-step-contents.ts | 17 -- tools/challenge-editor/api/utils/get-steps.ts | 81 -------- tools/challenge-editor/api/utils/save-step.ts | 21 --- tools/challenge-editor/client/.gitignore | 23 --- .../challenge-editor/client/.lintstagedrc.mjs | 4 - .../challenge-editor/client/eslint.config.mjs | 10 - tools/challenge-editor/client/index.html | 18 -- .../client/interfaces/block.ts | 15 -- .../client/interfaces/challenge-content.ts | 5 - .../client/interfaces/challenge-data.ts | 11 -- .../client/interfaces/chapter.ts | 10 - .../client/interfaces/prop-types.ts | 9 - .../client/interfaces/super-block.ts | 4 - tools/challenge-editor/client/package.json | 30 --- tools/challenge-editor/client/sample.env | 2 - tools/challenge-editor/client/src/app.tsx | 37 ---- .../client/src/components/block/block.css | 3 - .../client/src/components/block/block.tsx | 173 ----------------- .../components/buttons/create-empty-steps.tsx | 38 ---- .../components/buttons/create-next-step.tsx | 20 -- .../src/components/buttons/delete-step.tsx | 36 ---- .../src/components/buttons/insert-step.tsx | 36 ---- .../src/components/buttons/save-challenge.tsx | 27 --- .../components/buttons/update-step-titles.tsx | 20 -- .../chapter-based-block/chapter-block.css | 3 - .../chapter-based-block/chapter-block.tsx | 177 ------------------ .../client/src/components/chapter/chapter.css | 3 - .../client/src/components/chapter/chapter.tsx | 70 ------- .../client/src/components/editor/editor.css | 15 -- .../client/src/components/editor/editor.tsx | 117 ------------ .../client/src/components/header/header.css | 16 -- .../client/src/components/header/header.tsx | 14 -- .../client/src/components/landing/landing.tsx | 51 ----- .../client/src/components/module/module.css | 3 - .../client/src/components/module/module.tsx | 74 -------- .../src/components/superblock/super-block.tsx | 64 ------- .../client/src/components/tools/tools.css | 13 -- .../client/src/components/tools/tools.tsx | 51 ----- .../client/src/fonts/Lato-Regular.woff | Bin 37524 -> 0 bytes tools/challenge-editor/client/src/index.css | 62 ------ tools/challenge-editor/client/src/index.tsx | 11 -- .../client/src/utils/handle-request.ts | 22 --- .../client/src/vite-app-env.d.ts | 9 - tools/challenge-editor/client/tsconfig.json | 20 -- tools/challenge-editor/client/vite.config.ts | 12 -- 75 files changed, 8 insertions(+), 2118 deletions(-) create mode 160000 tools/challenge-editor delete mode 100644 tools/challenge-editor/api/.lintstagedrc.mjs delete mode 100644 tools/challenge-editor/api/configs/paths.ts delete mode 100644 tools/challenge-editor/api/configs/super-block-list.ts delete mode 100644 tools/challenge-editor/api/eslint.config.mjs delete mode 100644 tools/challenge-editor/api/interfaces/challenge-data.ts delete mode 100644 tools/challenge-editor/api/interfaces/intro.ts delete mode 100644 tools/challenge-editor/api/interfaces/partial-meta.ts delete mode 100644 tools/challenge-editor/api/interfaces/superblock-meta.ts delete mode 100644 tools/challenge-editor/api/interfaces/tools.ts delete mode 100644 tools/challenge-editor/api/package.json delete mode 100644 tools/challenge-editor/api/reset.d.ts delete mode 100644 tools/challenge-editor/api/routes/block-route.ts delete mode 100644 tools/challenge-editor/api/routes/index-route.ts delete mode 100644 tools/challenge-editor/api/routes/module-block-route.ts delete mode 100644 tools/challenge-editor/api/routes/module-route.ts delete mode 100644 tools/challenge-editor/api/routes/module-step-route.ts delete mode 100644 tools/challenge-editor/api/routes/save-route.ts delete mode 100644 tools/challenge-editor/api/routes/step-route.ts delete mode 100644 tools/challenge-editor/api/routes/super-block-route.ts delete mode 100644 tools/challenge-editor/api/routes/tools-route.ts delete mode 100644 tools/challenge-editor/api/sample.env delete mode 100644 tools/challenge-editor/api/server.ts delete mode 100644 tools/challenge-editor/api/tsconfig.json delete mode 100644 tools/challenge-editor/api/utils/get-blocks.ts delete mode 100644 tools/challenge-editor/api/utils/get-full-stack-blocks.ts delete mode 100644 tools/challenge-editor/api/utils/get-step-contents.ts delete mode 100644 tools/challenge-editor/api/utils/get-steps.ts delete mode 100644 tools/challenge-editor/api/utils/save-step.ts delete mode 100644 tools/challenge-editor/client/.gitignore delete mode 100644 tools/challenge-editor/client/.lintstagedrc.mjs delete mode 100644 tools/challenge-editor/client/eslint.config.mjs delete mode 100644 tools/challenge-editor/client/index.html delete mode 100644 tools/challenge-editor/client/interfaces/block.ts delete mode 100644 tools/challenge-editor/client/interfaces/challenge-content.ts delete mode 100644 tools/challenge-editor/client/interfaces/challenge-data.ts delete mode 100644 tools/challenge-editor/client/interfaces/chapter.ts delete mode 100644 tools/challenge-editor/client/interfaces/prop-types.ts delete mode 100644 tools/challenge-editor/client/interfaces/super-block.ts delete mode 100644 tools/challenge-editor/client/package.json delete mode 100644 tools/challenge-editor/client/sample.env delete mode 100644 tools/challenge-editor/client/src/app.tsx delete mode 100644 tools/challenge-editor/client/src/components/block/block.css delete mode 100644 tools/challenge-editor/client/src/components/block/block.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/create-next-step.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/delete-step.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/insert-step.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/save-challenge.tsx delete mode 100644 tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx delete mode 100644 tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css delete mode 100644 tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx delete mode 100644 tools/challenge-editor/client/src/components/chapter/chapter.css delete mode 100644 tools/challenge-editor/client/src/components/chapter/chapter.tsx delete mode 100644 tools/challenge-editor/client/src/components/editor/editor.css delete mode 100644 tools/challenge-editor/client/src/components/editor/editor.tsx delete mode 100644 tools/challenge-editor/client/src/components/header/header.css delete mode 100644 tools/challenge-editor/client/src/components/header/header.tsx delete mode 100644 tools/challenge-editor/client/src/components/landing/landing.tsx delete mode 100644 tools/challenge-editor/client/src/components/module/module.css delete mode 100644 tools/challenge-editor/client/src/components/module/module.tsx delete mode 100644 tools/challenge-editor/client/src/components/superblock/super-block.tsx delete mode 100644 tools/challenge-editor/client/src/components/tools/tools.css delete mode 100644 tools/challenge-editor/client/src/components/tools/tools.tsx delete mode 100644 tools/challenge-editor/client/src/fonts/Lato-Regular.woff delete mode 100644 tools/challenge-editor/client/src/index.css delete mode 100644 tools/challenge-editor/client/src/index.tsx delete mode 100644 tools/challenge-editor/client/src/utils/handle-request.ts delete mode 100644 tools/challenge-editor/client/src/vite-app-env.d.ts delete mode 100644 tools/challenge-editor/client/tsconfig.json delete mode 100644 tools/challenge-editor/client/vite.config.ts diff --git a/.gitmodules b/.gitmodules index 513eae4a9a7..fc8e491adcf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = curriculum/i18n-curriculum url = https://github.com/freeCodeCamp/i18n-curriculum.git ignore = dirty +[submodule "tools/challenge-editor"] + path = tools/challenge-editor + url = https://github.com/freeCodeCamp/challenge-editor.git + ignore = dirty diff --git a/.prettierignore b/.prettierignore index 9cb0f95bbd8..808f6557f0b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,4 +20,5 @@ shared/utils/get-lines.test.js shared/utils/is-audited.js shared/utils/validate.js shared/utils/validate.test.js +tools/challenge-editor dist diff --git a/package.json b/package.json index 46e292354f1..1ce04128a70 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,8 @@ "build:client": "cd ./client && pnpm run build", "build:curriculum": "pnpm -F=curriculum run build && pnpm -F=client run build:external-curriculum", "build:api": "cd ./api && pnpm run build", - "challenge-editor": "npm-run-all -p challenge-editor:*", - "challenge-editor:client": "cd ./tools/challenge-editor/client && pnpm start", - "challenge-editor:server": "cd ./tools/challenge-editor/api && pnpm start", + "challenge-editor": "cd tools/challenge-editor && pnpm dev", + "challenge-editor-setup": "git submodule update --init tools/challenge-editor && cd tools/challenge-editor && pnpm install", "clean": "npm-run-all -p clean:client clean:api clean:curriculum --serial clean:packages", "clean-and-develop": "pnpm run clean && pnpm install && pnpm run develop", "clean:api": "cd api && pnpm clean", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 90f48091f5b..2d929c727e7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,8 +4,6 @@ packages: - 'curriculum' - 'e2e' - 'shared' - - 'tools/challenge-editor/api' - - 'tools/challenge-editor/client' - 'tools/challenge-helper-scripts' - 'tools/challenge-parser' - 'tools/client-plugins/*' diff --git a/tools/challenge-editor b/tools/challenge-editor new file mode 160000 index 00000000000..2c28b22fe04 --- /dev/null +++ b/tools/challenge-editor @@ -0,0 +1 @@ +Subproject commit 2c28b22fe040c551e45b7e40eefa19f91d59cfde diff --git a/tools/challenge-editor/api/.lintstagedrc.mjs b/tools/challenge-editor/api/.lintstagedrc.mjs deleted file mode 100644 index 2cb8879f45f..00000000000 --- a/tools/challenge-editor/api/.lintstagedrc.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable filenames-simple/naming-convention */ -import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; - -export default createLintStagedConfig(import.meta.dirname); diff --git a/tools/challenge-editor/api/configs/paths.ts b/tools/challenge-editor/api/configs/paths.ts deleted file mode 100644 index fc185059ffe..00000000000 --- a/tools/challenge-editor/api/configs/paths.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { join } from 'path'; - -export const SUPERBLOCK_META_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'structure', - 'superblocks' -); - -export const BLOCK_META_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'structure', - 'blocks' -); - -export const CHALLENGE_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'challenges', - 'english', - 'blocks' -); - -export const ENGLISH_LANG_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'client', - 'i18n', - 'locales', - 'english' -); diff --git a/tools/challenge-editor/api/configs/super-block-list.ts b/tools/challenge-editor/api/configs/super-block-list.ts deleted file mode 100644 index 745c2f1fb6e..00000000000 --- a/tools/challenge-editor/api/configs/super-block-list.ts +++ /dev/null @@ -1,118 +0,0 @@ -export const superBlockList = [ - { - name: 'Legacy Responsive Web Design', - path: 'responsive-web-design' - }, - { - name: 'Legacy JavaScript Algorithms and Data Structures', - path: 'javascript-algorithms-and-data-structures' - }, - { - name: 'Front End Development Libraries', - path: 'front-end-development-libraries' - }, - { - name: 'Data Visualization', - path: 'data-visualization' - }, - { - name: 'Back End Development and APIs', - path: 'back-end-development-and-apis' - }, - { - name: 'Quality Assurance', - path: 'quality-assurance' - }, - { - name: 'Scientific Computing with Python', - path: 'scientific-computing-with-python' - }, - { - name: 'Data Analysis with Python', - path: 'data-analysis-with-python' - }, - { - name: 'Information Security', - path: 'information-security' - }, - { - name: 'Coding Interview Prep', - path: 'coding-interview-prep' - }, - { - name: 'Machine Learning with Python', - path: 'machine-learning-with-python' - }, - { - name: 'Relational Databases', - path: 'relational-databases' - }, - { - name: 'Responsive Web Design', - path: 'responsive-web-design-22' - }, - { - name: 'JavaScript Algorithms and Data Structures', - path: 'javascript-algorithms-and-data-structures-22' - }, - { - name: 'The Odin Project', - path: 'the-odin-project' - }, - { - name: 'College Algebra with Python', - path: 'college-algebra-with-python' - }, - { - name: 'Project Euler', - path: 'project-euler' - }, - { - name: '(New) Foundational C# with Microsoft', - path: 'foundational-c-sharp-with-microsoft' - }, - { - name: 'A2 English for Developers', - path: 'a2-english-for-developers' - }, - { - name: 'Rosetta Code', - path: 'rosetta-code' - }, - { - name: 'Python For Everybody', - path: 'python-for-everybody' - }, - { - name: 'B1 English for Developers (Beta)', - path: 'b1-english-for-developers' - }, - { - name: 'Certified Full Stack Developer', - path: 'full-stack-developer' - }, - { - name: 'A1 Professional Spanish (Beta)', - path: 'a1-professional-spanish' - }, - { - name: 'A2 Professional Spanish (Beta)', - path: 'a2-professional-spanish' - }, - { - name: 'A2 Professional Chinese (Beta)', - path: 'a2-professional-chinese' - }, - { - name: 'Basic HTML', - path: 'basic-html' - }, - { - name: 'Semantic HTML', - path: 'semantic-html' - }, - { - name: 'A1 Professional Chinese (Beta)', - path: 'a1-professional-chinese' - } -]; diff --git a/tools/challenge-editor/api/eslint.config.mjs b/tools/challenge-editor/api/eslint.config.mjs deleted file mode 100644 index 7f82ebde353..00000000000 --- a/tools/challenge-editor/api/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { configTypeChecked } from '@freecodecamp/eslint-config/base'; - -export default configTypeChecked; diff --git a/tools/challenge-editor/api/interfaces/challenge-data.ts b/tools/challenge-editor/api/interfaces/challenge-data.ts deleted file mode 100644 index 911a8bfea94..00000000000 --- a/tools/challenge-editor/api/interfaces/challenge-data.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ChallengeData { - name: string; - id: string; - path: string; -} diff --git a/tools/challenge-editor/api/interfaces/intro.ts b/tools/challenge-editor/api/interfaces/intro.ts deleted file mode 100644 index fca6969a8d8..00000000000 --- a/tools/challenge-editor/api/interfaces/intro.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface SuperBlock { - title: string; - intro: string[]; - blocks: string[]; - modules?: string[]; - chapters?: string[]; -} - -export interface Intro { - [key: string]: SuperBlock; -} diff --git a/tools/challenge-editor/api/interfaces/partial-meta.ts b/tools/challenge-editor/api/interfaces/partial-meta.ts deleted file mode 100644 index 711e613a4a9..00000000000 --- a/tools/challenge-editor/api/interfaces/partial-meta.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface PartialMeta { - name: string; - dashedName: string; - challengeOrder: { id: string; title: string }[]; -} diff --git a/tools/challenge-editor/api/interfaces/superblock-meta.ts b/tools/challenge-editor/api/interfaces/superblock-meta.ts deleted file mode 100644 index 7a93317e6e3..00000000000 --- a/tools/challenge-editor/api/interfaces/superblock-meta.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface SuperBlockModule { - dashedName: string; - blocks?: string[]; -} - -export interface SuperBlockChapter { - dashedName: string; - modules: SuperBlockModule[]; -} - -export interface SuperBlockMeta { - blocks?: string[]; - chapters?: SuperBlockChapter[]; -} diff --git a/tools/challenge-editor/api/interfaces/tools.ts b/tools/challenge-editor/api/interfaces/tools.ts deleted file mode 100644 index ec22589f2bb..00000000000 --- a/tools/challenge-editor/api/interfaces/tools.ts +++ /dev/null @@ -1,16 +0,0 @@ -type ToolsFunction = ( - directory: string -) => Promise<{ stdout: string; stderr: string }>; - -type ToolsFunctionWithArg = ( - directory: string, - start: number -) => Promise<{ stdout: string; stderr: string }>; - -export interface ToolsSwitch { - 'create-next-step': ToolsFunction; - 'create-empty-steps': ToolsFunctionWithArg; - 'insert-step': ToolsFunctionWithArg; - 'delete-step': ToolsFunctionWithArg; - 'update-step-titles': ToolsFunction; -} diff --git a/tools/challenge-editor/api/package.json b/tools/challenge-editor/api/package.json deleted file mode 100644 index f7a41bab1d9..00000000000 --- a/tools/challenge-editor/api/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@freecodecamp/challenge-editor-api", - "version": "1.0.0", - "private": true, - "description": "Editor to help with new challenge structure", - "scripts": { - "start": "tsx server.ts", - "postinstall": "shx cp ./sample.env ./.env", - "lint": "eslint --max-warnings 0", - "type-check": "tsc --noEmit" - }, - "author": "freeCodeCamp", - "license": "BSD-3-Clause", - "dependencies": { - "cors": "2.8.5", - "express": "4.18.2", - "gray-matter": "4.0.3" - }, - "devDependencies": { - "@freecodecamp/eslint-config": "workspace:*", - "@total-typescript/ts-reset": "^0.6.1", - "@types/cors": "^2.8.13", - "@types/express": "4.17.21", - "dotenv": "16.4.5", - "eslint": "^9.39.1", - "shx": "0.3.4", - "typescript": "5.9.3" - } -} diff --git a/tools/challenge-editor/api/reset.d.ts b/tools/challenge-editor/api/reset.d.ts deleted file mode 100644 index 12bd3edc94a..00000000000 --- a/tools/challenge-editor/api/reset.d.ts +++ /dev/null @@ -1 +0,0 @@ -import '@total-typescript/ts-reset'; diff --git a/tools/challenge-editor/api/routes/block-route.ts b/tools/challenge-editor/api/routes/block-route.ts deleted file mode 100644 index c12955fd289..00000000000 --- a/tools/challenge-editor/api/routes/block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getSteps } from '../utils/get-steps'; - -export const blockRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, block } = req.params; - - const steps = await getSteps(superblock, block); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/index-route.ts b/tools/challenge-editor/api/routes/index-route.ts deleted file mode 100644 index 6721d6bebe2..00000000000 --- a/tools/challenge-editor/api/routes/index-route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Request, Response } from 'express'; -import { superBlockList } from '../configs/super-block-list'; - -export const indexRoute = (req: Request, res: Response): void => { - res.json(superBlockList); -}; diff --git a/tools/challenge-editor/api/routes/module-block-route.ts b/tools/challenge-editor/api/routes/module-block-route.ts deleted file mode 100644 index 61cb2ac9896..00000000000 --- a/tools/challenge-editor/api/routes/module-block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getBlocks } from '../utils/get-full-stack-blocks'; - -export const moduleBlockRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, chapter, module } = req.params; - - const steps = await getBlocks(superblock, chapter, module); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/module-route.ts b/tools/challenge-editor/api/routes/module-route.ts deleted file mode 100644 index bf393caaf58..00000000000 --- a/tools/challenge-editor/api/routes/module-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getModules } from '../utils/get-full-stack-blocks'; - -export const moduleRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, chapter } = req.params; - - const steps = await getModules(superblock, chapter); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/module-step-route.ts b/tools/challenge-editor/api/routes/module-step-route.ts deleted file mode 100644 index e04d2b60280..00000000000 --- a/tools/challenge-editor/api/routes/module-step-route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Request, Response } from 'express'; -import { getStepContent } from '../utils/get-step-contents'; - -export const stepRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - - const stepContents = await getStepContent(superblock, block, step); - res.json(stepContents); -}; diff --git a/tools/challenge-editor/api/routes/save-route.ts b/tools/challenge-editor/api/routes/save-route.ts deleted file mode 100644 index beceeb8f357..00000000000 --- a/tools/challenge-editor/api/routes/save-route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Request, Response } from 'express'; -import { saveStep } from '../utils/save-step'; - -export const saveRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - const content = (req.body as { content: string }).content; - - const success = await saveStep(superblock, block, step, content); - - const message = success - ? 'Your changes have been saved and are ready to commit!' - : 'There was an error when saving your changes. Please try again.'; - - res.json({ message }); -}; diff --git a/tools/challenge-editor/api/routes/step-route.ts b/tools/challenge-editor/api/routes/step-route.ts deleted file mode 100644 index e04d2b60280..00000000000 --- a/tools/challenge-editor/api/routes/step-route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Request, Response } from 'express'; -import { getStepContent } from '../utils/get-step-contents'; - -export const stepRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - - const stepContents = await getStepContent(superblock, block, step); - res.json(stepContents); -}; diff --git a/tools/challenge-editor/api/routes/super-block-route.ts b/tools/challenge-editor/api/routes/super-block-route.ts deleted file mode 100644 index ad50c4bb529..00000000000 --- a/tools/challenge-editor/api/routes/super-block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getBlocks } from '../utils/get-blocks'; - -export const superblockRoute = async ( - req: Request, - res: Response -): Promise => { - const sup = req.params.superblock; - - const blocks = await getBlocks(sup); - - res.json(blocks); -}; diff --git a/tools/challenge-editor/api/routes/tools-route.ts b/tools/challenge-editor/api/routes/tools-route.ts deleted file mode 100644 index ff93fb470d8..00000000000 --- a/tools/challenge-editor/api/routes/tools-route.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { exec } from 'child_process'; -import { join } from 'path'; -import { promisify } from 'util'; - -import { Request, Response } from 'express'; -import { ToolsSwitch } from '../interfaces/tools'; - -const asyncExec = promisify(exec); - -const toolsSwitch: ToolsSwitch = { - 'create-next-step': directory => { - return asyncExec(`cd ${directory} && pnpm run create-next-step`); - }, - 'create-empty-steps': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run create-empty-steps ${num}`); - }, - 'insert-step': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run insert-step ${num}`); - }, - 'delete-step': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run delete-step ${num}`); - }, - 'update-step-titles': directory => { - return asyncExec(`cd ${directory} && pnpm run update-step-titles`); - } -}; - -export const toolsRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, block, command } = req.params; - const { num } = req.body as Record; - const directory = join( - __dirname, - '..', - '..', - '..', - '..', - 'curriculum', - 'challenges', - 'english', - superblock, - block - ); - - if (!(command in toolsSwitch)) { - res.json({ stdout: '', stderr: 'Command not found' }); - return; - } - - const parsed = command as keyof ToolsSwitch; - - const { stdout, stderr } = await toolsSwitch[parsed](directory, num); - res.json({ stdout, stderr }); -}; diff --git a/tools/challenge-editor/api/sample.env b/tools/challenge-editor/api/sample.env deleted file mode 100644 index e1749a00c07..00000000000 --- a/tools/challenge-editor/api/sample.env +++ /dev/null @@ -1 +0,0 @@ -CHALLENGE_EDITOR_CLIENT_LOCATION=http://localhost:3300 diff --git a/tools/challenge-editor/api/server.ts b/tools/challenge-editor/api/server.ts deleted file mode 100644 index 1a201ac049d..00000000000 --- a/tools/challenge-editor/api/server.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as dotenv from 'dotenv'; -dotenv.config(); - -import cors from 'cors'; -import express from 'express'; -import { blockRoute } from './routes/block-route'; -import { indexRoute } from './routes/index-route'; -import { saveRoute } from './routes/save-route'; -import { stepRoute } from './routes/step-route'; -import { superblockRoute } from './routes/super-block-route'; -import { toolsRoute } from './routes/tools-route'; -import { moduleRoute } from './routes/module-route'; -import { moduleBlockRoute } from './routes/module-block-route'; - -const app = express(); - -app.use( - cors({ - origin: process.env.CHALLENGE_EDITOR_CLIENT_LOCATION - }) -); - -app.use(express.static('public')); -app.use(express.json()); - -app.post('/:superblock/:block/_tools/:command', (req, res, next) => { - toolsRoute(req, res).catch(next); -}); - -app.post('/:superblock/:block/:step', (req, res, 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) => { - stepRoute(req, res).catch(next); -}); - -app.get('/:superblock/:block', (req, res, next) => { - blockRoute(req, res).catch(next); -}); - -app.get('/:superblock', (req, res, next) => { - superblockRoute(req, res).catch(next); -}); - -app.get('/', indexRoute); - -app.listen(3200, () => console.log('App is live on 3200!')); diff --git a/tools/challenge-editor/api/tsconfig.json b/tools/challenge-editor/api/tsconfig.json deleted file mode 100644 index 94fe2cca852..00000000000 --- a/tools/challenge-editor/api/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig-base.json" -} diff --git a/tools/challenge-editor/api/utils/get-blocks.ts b/tools/challenge-editor/api/utils/get-blocks.ts deleted file mode 100644 index 586c6c33b0f..00000000000 --- a/tools/challenge-editor/api/utils/get-blocks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -import { chapterBasedSuperBlocks } from '../../../../packages/shared/src/config/curriculum'; -import { - SUPERBLOCK_META_DIR, - BLOCK_META_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; - -import { SuperBlockMeta } from '../interfaces/superblock-meta'; -import { PartialMeta } from '../interfaces/partial-meta'; - -import { Intro } from '../interfaces/intro'; - -type Block = { - name: string; - path: string; -}; - -type BlockLocation = { - blocks: Block[]; - currentSuperBlock: string; -}; - -export const getBlocks = async (sup: string): Promise => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, sup + '.json'); - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - 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 }[] = []; - - if (chapterBasedSuperBlocks.includes(sup)) { - blocks = superBlockMeta.chapters!.map(chapter => { - const chapters = Object.entries(introData[sup]['chapters']!); - const chapterTrueName = chapters.filter( - x => x[0] === chapter.dashedName - )[0][1]; - return { - name: chapterTrueName, - path: 'chapters/' + chapter.dashedName - }; - }); - } else { - blocks = await Promise.all( - superBlockMeta.blocks!.map(async block => { - const blockStructurePath = join(BLOCK_META_DIR, block + '.json'); - const blockMetaFile = await readFile(blockStructurePath, { - encoding: 'utf8' - }); - const blockMeta = JSON.parse(blockMetaFile) as PartialMeta; - return { - name: blockMeta.name, - path: block - }; - }) - ); - } - - return { blocks: blocks, currentSuperBlock: introData[sup]?.title }; -}; diff --git a/tools/challenge-editor/api/utils/get-full-stack-blocks.ts b/tools/challenge-editor/api/utils/get-full-stack-blocks.ts deleted file mode 100644 index d798843f717..00000000000 --- a/tools/challenge-editor/api/utils/get-full-stack-blocks.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import { - SUPERBLOCK_META_DIR, - BLOCK_META_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; -import { SuperBlockMeta } from '../interfaces/superblock-meta'; -import { PartialMeta } from '../interfaces/partial-meta'; -import { Intro } from '../interfaces/intro'; - -type Block = { - name: string; - path: string; -}; - -type Module = { - name: string; - path: string; -}; - -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 => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json'); - - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - 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( - x => x.dashedName === chap - )[0]; - - const chapterTrueName = chapters.filter(x => x[0] === chap)[0][1]; - - let modules: Module[] = []; - - modules = 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 ( - superBlock: string, - chapterName: string, - moduleName: string -): Promise => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json'); - - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - 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 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 }[] = []; - - blocks = await Promise.all( - foundModule!.blocks!.map(async block => { - const blockStructurePath = join(BLOCK_META_DIR, block + '.json'); - const blockMetaFile = await readFile(blockStructurePath, { - encoding: 'utf8' - }); - const blockMeta = JSON.parse(blockMetaFile) as PartialMeta; - return { - name: blockMeta.name, - path: block - }; - }) - ); - - return { - blocks: blocks, - currentModule: moduleTrueName, - currentChapter: chapterTrueName - }; -}; diff --git a/tools/challenge-editor/api/utils/get-step-contents.ts b/tools/challenge-editor/api/utils/get-step-contents.ts deleted file mode 100644 index dd71a2627fb..00000000000 --- a/tools/challenge-editor/api/utils/get-step-contents.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import matter from 'gray-matter'; -import { CHALLENGE_DIR } from '../configs/paths'; - -export const getStepContent = async ( - sup: string, - block: string, - step: string -): Promise<{ name: string; dashedName: string; fileData: string }> => { - const filePath = join(CHALLENGE_DIR, block, step); - - const fileData = await readFile(filePath, 'utf8'); - const name = matter(fileData).data.title as string; - const dashedName = matter(fileData).data.dashedName as string; - return { name, dashedName, fileData }; -}; diff --git a/tools/challenge-editor/api/utils/get-steps.ts b/tools/challenge-editor/api/utils/get-steps.ts deleted file mode 100644 index 586af180864..00000000000 --- a/tools/challenge-editor/api/utils/get-steps.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -import matter from 'gray-matter'; - -import { PartialMeta } from '../interfaces/partial-meta'; -import { - BLOCK_META_DIR, - CHALLENGE_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; -import { Intro } from '../interfaces/intro'; - -const getFileOrder = (id: string, meta: PartialMeta) => { - return meta.challengeOrder.findIndex(({ id: f }) => f === id); -}; - -type Step = { - name: string; - id: string; - path: string; -}; - -type StepLocation = { - steps: Step[]; - currentBlock: string; - currentSuperBlock: string; -}; - -export const getSteps = async ( - sup: string, - block: string -): Promise => { - //const superMetaPath = join(SUPERBLOCK_META_DIR, sup + ".json"); - - //const superMetaData = JSON.parse( - // await readFile(superMetaPath, 'utf8') - //) as Partial; - - const stepDirectory = join(CHALLENGE_DIR, block); - - 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( - await readFile(blockFolderPath, { encoding: 'utf8' }) - ) as PartialMeta; - - const stepFileNames = blockMetaData.challengeOrder.map(x => x.id + '.md'); - - const stepData = await Promise.all( - stepFileNames.map(async filename => { - const stepPath = join(stepDirectory, filename); - const step = await readFile(stepPath, 'utf8'); - const frontMatter = matter(step); - - return { - name: frontMatter.data.title as string, - id: frontMatter.data.id as string, - path: filename - }; - }) - ); - - const steps = stepData.sort( - (a, b) => - getFileOrder(a.id, blockMetaData) - getFileOrder(b.id, blockMetaData) - ); - - return { - steps: steps, - currentBlock: blockMetaData.name, - currentSuperBlock: introData[sup]?.title - }; -}; diff --git a/tools/challenge-editor/api/utils/save-step.ts b/tools/challenge-editor/api/utils/save-step.ts deleted file mode 100644 index f8333a9ecbe..00000000000 --- a/tools/challenge-editor/api/utils/save-step.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { writeFile } from 'fs/promises'; -import { join } from 'path'; -import { CHALLENGE_DIR } from '../configs/paths'; - -export const saveStep = async ( - sup: string, - block: string, - step: string, - content: string -): Promise => { - try { - const filePath = join(CHALLENGE_DIR, block, step); - - await writeFile(filePath, content); - - return true; - } catch (err) { - console.log(err); - return false; - } -}; diff --git a/tools/challenge-editor/client/.gitignore b/tools/challenge-editor/client/.gitignore deleted file mode 100644 index 4d29575de80..00000000000 --- a/tools/challenge-editor/client/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/tools/challenge-editor/client/.lintstagedrc.mjs b/tools/challenge-editor/client/.lintstagedrc.mjs deleted file mode 100644 index 2cb8879f45f..00000000000 --- a/tools/challenge-editor/client/.lintstagedrc.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable filenames-simple/naming-convention */ -import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; - -export default createLintStagedConfig(import.meta.dirname); diff --git a/tools/challenge-editor/client/eslint.config.mjs b/tools/challenge-editor/client/eslint.config.mjs deleted file mode 100644 index f75c186aa50..00000000000 --- a/tools/challenge-editor/client/eslint.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { - configTypeChecked, - configReact -} from '@freecodecamp/eslint-config/base'; - -export default [ - ...configTypeChecked, - ...configReact, - { settings: { react: { version: '17.0.2' } } } -]; diff --git a/tools/challenge-editor/client/index.html b/tools/challenge-editor/client/index.html deleted file mode 100644 index b683202f72c..00000000000 --- a/tools/challenge-editor/client/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - freeCodeCamp Challenge Editor - - - -
- - - diff --git a/tools/challenge-editor/client/interfaces/block.ts b/tools/challenge-editor/client/interfaces/block.ts deleted file mode 100644 index 563e6a33779..00000000000 --- a/tools/challenge-editor/client/interfaces/block.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Block { - name: string; - path: string; -} - -export interface BlocksWithSuperBlock { - blocks: Block[]; - currentSuperBlock: string; -} - -export interface BlocksWithModule { - blocks: Block[]; - currentModule: string; - currentChapter: string; -} diff --git a/tools/challenge-editor/client/interfaces/challenge-content.ts b/tools/challenge-editor/client/interfaces/challenge-content.ts deleted file mode 100644 index 60474e17201..00000000000 --- a/tools/challenge-editor/client/interfaces/challenge-content.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ChallengeContent { - name: string; - fileData: string; - dashedName: string; -} diff --git a/tools/challenge-editor/client/interfaces/challenge-data.ts b/tools/challenge-editor/client/interfaces/challenge-data.ts deleted file mode 100644 index b1248bb2c7b..00000000000 --- a/tools/challenge-editor/client/interfaces/challenge-data.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface ChallengeData { - name: string; - id: string; - path: string; -} - -export interface ChallengeDataWithBlock { - steps: ChallengeData[]; - currentBlock: string; - currentSuperBlock: string; -} diff --git a/tools/challenge-editor/client/interfaces/chapter.ts b/tools/challenge-editor/client/interfaces/chapter.ts deleted file mode 100644 index ff2473572bb..00000000000 --- a/tools/challenge-editor/client/interfaces/chapter.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface Module { - name: string; - path: string; -} - -export interface ChaptersWithLocation { - modules: Module[]; - currentSuperBlock: string; - currentChapter: string; -} diff --git a/tools/challenge-editor/client/interfaces/prop-types.ts b/tools/challenge-editor/client/interfaces/prop-types.ts deleted file mode 100644 index 2bb76daf4be..00000000000 --- a/tools/challenge-editor/client/interfaces/prop-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface BlockRequiredProps { - superblock?: string; - block?: string; -} - -export interface ChallengeContentRequiredProps extends BlockRequiredProps { - challenge?: string; - content: string; -} diff --git a/tools/challenge-editor/client/interfaces/super-block.ts b/tools/challenge-editor/client/interfaces/super-block.ts deleted file mode 100644 index 43f83133125..00000000000 --- a/tools/challenge-editor/client/interfaces/super-block.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SuperBlock { - name: string; - path: string; -} diff --git a/tools/challenge-editor/client/package.json b/tools/challenge-editor/client/package.json deleted file mode 100644 index 454eb5581be..00000000000 --- a/tools/challenge-editor/client/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@freecodecamp/challenge-editor-client", - "version": "0.1.0", - "private": true, - "dependencies": { - "@vitejs/plugin-react": "4.2.1", - "codemirror": "5.65.16", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-router-dom": "6.18.0", - "vite": "4.5.2" - }, - "scripts": { - "start": "PORT=3300 vite", - "build": "tsc && vite build", - "lint": "eslint --max-warnings 0", - "type-check": "tsc --noEmit", - "postinstall": "shx cp ./sample.env ./.env" - }, - "devDependencies": { - "@freecodecamp/eslint-config": "workspace:*", - "@types/codemirror": "5.60.15", - "@types/react": "17.0.83", - "@types/react-dom": "17.0.19", - "@uiw/react-codemirror": "3.2.10", - "eslint": "^9.39.1", - "shx": "0.3.4", - "typescript": "5.9.3" - } -} diff --git a/tools/challenge-editor/client/sample.env b/tools/challenge-editor/client/sample.env deleted file mode 100644 index f9c1536d2cd..00000000000 --- a/tools/challenge-editor/client/sample.env +++ /dev/null @@ -1,2 +0,0 @@ -CHALLENGE_EDITOR_API_LOCATION=http://localhost:3200 -CHALLENGE_EDITOR_LEARN_CLIENT_LOCATION=http://localhost:8000 diff --git a/tools/challenge-editor/client/src/app.tsx b/tools/challenge-editor/client/src/app.tsx deleted file mode 100644 index 7d014b84680..00000000000 --- a/tools/challenge-editor/client/src/app.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import Header from './components/header/header'; -import Landing from './components/landing/landing'; -import SuperBlock from './components/superblock/super-block'; -import Block from './components/block/block'; -import Editor from './components/editor/editor'; -import Tools from './components/tools/tools'; -import ChapterLanding from './components/chapter/chapter'; -import ModuleLanding from './components/module/module'; - -const App = () => { - return ( -
-
- - - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - - -
- ); -}; - -export default App; diff --git a/tools/challenge-editor/client/src/components/block/block.css b/tools/challenge-editor/client/src/components/block/block.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/block/block.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/block/block.tsx b/tools/challenge-editor/client/src/components/block/block.tsx deleted file mode 100644 index fa94d2a7cac..00000000000 --- a/tools/challenge-editor/client/src/components/block/block.tsx +++ /dev/null @@ -1,173 +0,0 @@ -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 './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', - 'a1-professional-spanish', - 'a2-professional-spanish', - 'a2-professional-chinese', - 'a1-professional-chinese' -]; - -const Block = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as ChallengeData[]); - const [blockName, setBlockName] = useState(''); - const [superBlockName, setSuperBlockName] = useState(''); - const params = useParams() as { superblock: 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) - .then( - superblocks => { - setLoading(false); - setItems(superblocks.steps); - setBlockName(superblocks.currentBlock); - setSuperBlockName(superblocks.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - - const isStepBasedSuperblock = stepBasedSuperblocks.includes( - params.superblock - ); - - const isTaskBasedSuperblock = taskBasedSuperblocks.includes( - params.superblock - ); - - return ( -
-

{blockName}

- {superBlockName} -
    - {items.map((challenge, i) => ( -
  • - {!isStepBasedSuperblock && {`${i + 1}: `}} - - {challenge.name} - -
  • - ))} -
-

- Return to Blocks -

-
-

Project Controls

- {isStepBasedSuperblock ? ( -

- Looking to add, remove, or edit steps?{' '} - - Use the step tools. - -

- ) : isTaskBasedSuperblock ? ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-task: Create the next task style - challenge in this block -
  • -
  • - pnpm create-next-challenge: Create the next challenge - of a different style in this block -
  • -
  • - pnpm insert-task: Create a new task style challenge - in the middle of this block. -
  • -
  • - pnpm delete-task: Delete a task style challenge in - this block. -
  • -
  • - pnpm reorder-tasks: Rename the tasks to the correct - order. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - ) : ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-challenge: Create a new challenge at - the end of this block. -
  • -
  • - pnpm insert-challenge: Create a new challenge in the - middle of this block. -
  • -
  • - pnpm delete-challenge: Delete a challenge in this - block. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - )} -
- ); -}; - -export default Block; diff --git a/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx b/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx deleted file mode 100644 index 4fef6ad54c8..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const CreateEmptySteps = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/create-empty-steps`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default CreateEmptySteps; diff --git a/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx b/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx deleted file mode 100644 index e191b18541a..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const CreateNextStep = ({ superblock, block }: BlockRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/create-next-step`, - { - method: 'POST' - } - ) - ); - - return ; -}; - -export default CreateNextStep; diff --git a/tools/challenge-editor/client/src/components/buttons/delete-step.tsx b/tools/challenge-editor/client/src/components/buttons/delete-step.tsx deleted file mode 100644 index 0735f04bf75..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/delete-step.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const DeleteStep = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/_tools/delete-step`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default DeleteStep; diff --git a/tools/challenge-editor/client/src/components/buttons/insert-step.tsx b/tools/challenge-editor/client/src/components/buttons/insert-step.tsx deleted file mode 100644 index 288ab091aa2..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/insert-step.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const InsertStep = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/_tools/insert-step`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default InsertStep; diff --git a/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx b/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx deleted file mode 100644 index 4dc00e0652d..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { ChallengeContentRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const SaveChallenge = ({ - superblock, - block, - challenge, - content -}: ChallengeContentRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/${challenge || ''}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ content }) - } - ) - ); - - return ; -}; - -export default SaveChallenge; diff --git a/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx b/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx deleted file mode 100644 index a1707ab2191..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const UpdateStepTitles = ({ superblock, block }: BlockRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/update-step-titles`, - { - method: 'POST' - } - ) - ); - - return ; -}; - -export default UpdateStepTitles; diff --git a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css b/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx b/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx deleted file mode 100644 index 4ef076f2c22..00000000000 --- a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx +++ /dev/null @@ -1,177 +0,0 @@ -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(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) - .then( - superblocks => { - setLoading(false); - setItems(superblocks.steps); - setBlockName(superblocks.currentBlock); - setSuperBlockName(superblocks.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - - const isStepBasedSuperblock = stepBasedSuperblocks.includes( - params.superblock - ); - - const isTaskBasedSuperblock = taskBasedSuperblocks.includes( - params.superblock - ); - - return ( -
-

{blockName}

- {superBlockName} -
    - {items.map((challenge, i) => ( -
  • - {!isStepBasedSuperblock && {`${i + 1}: `}} - - {challenge.name} - -
  • - ))} -
-

- Return to Blocks -

-
-

Project Controls

- {isStepBasedSuperblock ? ( -

- Looking to add, remove, or edit steps?{' '} - - Use the step tools. - -

- ) : isTaskBasedSuperblock ? ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-task: Create the next task style - challenge in this block -
  • -
  • - pnpm create-next-challenge: Create the next challenge - of a different style in this block -
  • -
  • - pnpm insert-task: Create a new task style challenge - in the middle of this block. -
  • -
  • - pnpm delete-task: Delete a task style challenge in - this block. -
  • -
  • - pnpm reorder-tasks: Rename the tasks to the correct - order. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - ) : ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-challenge: Create a new challenge at - the end of this block. -
  • -
  • - pnpm insert-challenge: Create a new challenge in the - middle of this block. -
  • -
  • - pnpm delete-challenge: Delete a challenge in this - block. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - )} -
- ); -}; - -export default ChapterBasedBlock; diff --git a/tools/challenge-editor/client/src/components/chapter/chapter.css b/tools/challenge-editor/client/src/components/chapter/chapter.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/chapter/chapter.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/chapter/chapter.tsx b/tools/challenge-editor/client/src/components/chapter/chapter.tsx deleted file mode 100644 index b982394cb86..00000000000 --- a/tools/challenge-editor/client/src/components/chapter/chapter.tsx +++ /dev/null @@ -1,70 +0,0 @@ -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(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) - .then( - blockData => { - setLoading(false); - setItems(blockData.modules); - setChapterName(blockData.currentChapter); - setSuperBlockName(blockData.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{chapterName}

-
    - {items.map(chapter => ( -
  • - - {chapter.name} - -
  • - ))} -
-

- Return to {superBlockName} -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default ChapterLanding; diff --git a/tools/challenge-editor/client/src/components/editor/editor.css b/tools/challenge-editor/client/src/components/editor/editor.css deleted file mode 100644 index 77515ad18b9..00000000000 --- a/tools/challenge-editor/client/src/components/editor/editor.css +++ /dev/null @@ -1,15 +0,0 @@ -textarea { - display: block; - margin: auto; - width: 500px; - max-width: 100vw; - height: 500px; -} - -.CodeMirror { - /* need to add important to overwrite the specificity of the classes in the dependency */ - height: 70vh !important; - max-width: 80vw; - margin: auto; - text-align: left; -} diff --git a/tools/challenge-editor/client/src/components/editor/editor.tsx b/tools/challenge-editor/client/src/components/editor/editor.tsx deleted file mode 100644 index 87c26fcd7f4..00000000000 --- a/tools/challenge-editor/client/src/components/editor/editor.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import CodeMirror from '@uiw/react-codemirror'; -import * as codemirror from 'codemirror'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/theme/material.css'; -import 'codemirror/mode/markdown/markdown'; -// we need to import this mode to get the fenced codeblock highlighting -import 'codemirror/mode/css/css'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/jsx/jsx'; -import { Link, useParams } from 'react-router-dom'; -import { ChallengeContent } from '../../../interfaces/challenge-content'; -import SaveChallenge from '../buttons/save-challenge'; -import './editor.css'; -import { API_LOCATION } from '../../utils/handle-request'; - -// only includes superblocks whose folder names don't match their dashed names? -export const superBlockNameMap: { [key: string]: string } = { - 'responsive-web-design-22': '2022/responsive-web-design', - 'javascript-algorithms-and-data-structures-22': - 'javascript-algorithms-and-data-structures-v8', - 'front-end-development': 'full-stack-developer' -}; - -const Editor = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState({ - name: '', - dashedName: '', - fileData: '' - }); - const [stepContent, setStepContent] = useState(''); - const { superblock = '', block = '', challenge = '' } = useParams(); - - const superblockUrl = - superblock in superBlockNameMap - ? superBlockNameMap[superblock] - : superblock; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${superblock}/${block}/${challenge}`) - .then(res => res.json() as Promise) - .then( - content => { - setLoading(false); - setItems(content); - setStepContent(content.fileData); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - const handleChange = (instance: codemirror.Editor) => { - const editedContent = instance.getValue(); - setStepContent(editedContent); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{items.name}

- - {superblock} / {block} - - - -

- Return to Block -

-

- - View Live Version of the Challenge in your running development - environment - -

-
- ); -}; - -export default Editor; diff --git a/tools/challenge-editor/client/src/components/header/header.css b/tools/challenge-editor/client/src/components/header/header.css deleted file mode 100644 index 1f5201753c3..00000000000 --- a/tools/challenge-editor/client/src/components/header/header.css +++ /dev/null @@ -1,16 +0,0 @@ -.header { - width: 100vw; - margin-top: 0; - text-align: center; - background: var(--background); - color: var(--content); -} - -.header a { - color: var(--content); -} - -.header p { - margin: 0; - font-size: 2rem; -} diff --git a/tools/challenge-editor/client/src/components/header/header.tsx b/tools/challenge-editor/client/src/components/header/header.tsx deleted file mode 100644 index 2574a31310f..00000000000 --- a/tools/challenge-editor/client/src/components/header/header.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import './header.css'; - -const Header = () => { - return ( - - ); -}; - -export default Header; diff --git a/tools/challenge-editor/client/src/components/landing/landing.tsx b/tools/challenge-editor/client/src/components/landing/landing.tsx deleted file mode 100644 index 1d301159c8d..00000000000 --- a/tools/challenge-editor/client/src/components/landing/landing.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { SuperBlock } from '../../../interfaces/super-block'; -import { API_LOCATION } from '../../utils/handle-request'; - -const Landing = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as SuperBlock[]); - - useEffect(() => { - fetchData(); - }, []); - - const fetchData = () => { - setLoading(true); - fetch(API_LOCATION) - .then(res => res.json() as Promise) - .then( - superblocks => { - setLoading(false); - setItems(superblocks); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

Superblocks

-
    - {items.map(superblock => ( -
  • - {superblock.name} -
  • - ))} -
-
- ); -}; - -export default Landing; diff --git a/tools/challenge-editor/client/src/components/module/module.css b/tools/challenge-editor/client/src/components/module/module.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/module/module.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/module/module.tsx b/tools/challenge-editor/client/src/components/module/module.tsx deleted file mode 100644 index 27cb6f58f16..00000000000 --- a/tools/challenge-editor/client/src/components/module/module.tsx +++ /dev/null @@ -1,74 +0,0 @@ -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(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) - .then( - moduleData => { - setLoading(false); - setItems(moduleData.blocks); - setModuleName(moduleData.currentModule); - setChapterName(moduleData.currentChapter); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{moduleName}

-
    - {items.map(block => ( -
  • - {block.name} -
  • - ))} -
-

- - Return to {chapterName} - -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default ModuleLanding; diff --git a/tools/challenge-editor/client/src/components/superblock/super-block.tsx b/tools/challenge-editor/client/src/components/superblock/super-block.tsx deleted file mode 100644 index ee7869e83b1..00000000000 --- a/tools/challenge-editor/client/src/components/superblock/super-block.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { Block, BlocksWithSuperBlock } from '../../../interfaces/block'; -import { API_LOCATION } from '../../utils/handle-request'; - -const SuperBlock = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as Block[]); - const [superBlockName, setSuperBlockName] = useState(''); - const params = useParams() as { superblock: string; block: string }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${params.superblock}`) - .then(res => res.json() as Promise) - .then( - blockData => { - setLoading(false); - setItems(blockData.blocks); - setSuperBlockName(blockData.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{superBlockName}

-
    - {items.map(block => ( -
  • - {block.name} -
  • - ))} -
-

- Return to Superblocks -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default SuperBlock; diff --git a/tools/challenge-editor/client/src/components/tools/tools.css b/tools/challenge-editor/client/src/components/tools/tools.css deleted file mode 100644 index 7f845671bb1..00000000000 --- a/tools/challenge-editor/client/src/components/tools/tools.css +++ /dev/null @@ -1,13 +0,0 @@ -label { - display: block; - font-size: 1.2rem; - margin-bottom: 0.5rem; -} - -input { - background-color: var(--grey); - border: 2px solid var(--content); - padding: 6px 12px; - margin-left: 10px; - color: white; -} diff --git a/tools/challenge-editor/client/src/components/tools/tools.tsx b/tools/challenge-editor/client/src/components/tools/tools.tsx deleted file mode 100644 index 448356c50a4..00000000000 --- a/tools/challenge-editor/client/src/components/tools/tools.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { Link, useParams } from 'react-router-dom'; -import CreateEmptySteps from '../buttons/create-empty-steps'; -import CreateNextStep from '../buttons/create-next-step'; -import DeleteStep from '../buttons/delete-step'; -import InsertStep from '../buttons/insert-step'; -import UpdateStepTitles from '../buttons/update-step-titles'; - -import './tools.css'; - -const Tools = () => { - const { block, superblock } = useParams() as { - block: string; - superblock: string; - }; - return ( -
-

Editing Steps for {block}

-

These tools will allow you to create, delete, and reorder steps.

-

Create Next Step

-

This tool creates a new step at the end of the project.

- -

Create Empty Steps

-

- This tool creates n number of empty steps at the end of the - project. -

- -

Insert Step

-

- This tool inserts a new step as the nth step. -

- -

Delete Step

-

- This tool deletes step n. -

- -

Update Step Titles

-

- This reorders the existing steps, updating the meta for the block. You - should not need to use this one unless you've manually changed the - file order. -

- -
- Return to Block -
- ); -}; - -export default Tools; diff --git a/tools/challenge-editor/client/src/fonts/Lato-Regular.woff b/tools/challenge-editor/client/src/fonts/Lato-Regular.woff deleted file mode 100644 index 9fb190c51643601a1c52c9f287766c172a37df16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37524 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 diff --git a/tools/challenge-editor/client/src/index.css b/tools/challenge-editor/client/src/index.css deleted file mode 100644 index bc6d251a541..00000000000 --- a/tools/challenge-editor/client/src/index.css +++ /dev/null @@ -1,62 +0,0 @@ -:root { - --nav-background: #0a0a23; - --background: #1b1b32; - --content: #f5f6f7; - --grey: #3b3b4f; - --font-family-sans-serif: 'Lato', sans-serif; - --font-family-monospace: 'Hack-ZeroSlash', monospace; -} - -@font-face { - font-family: 'Lato'; - src: url('./fonts/Lato-Regular.woff'); -} - -body { - margin: 0; - padding: 0; - font-family: var(--font-family-sans-serif); - text-align: center; - background: var(--background); - color: var(--content); -} - -ul { - list-style-type: none; - padding: 0; -} - -p, -li { - font-size: 1.2rem; -} - -a { - color: var(--content); -} - -button { - text-align: center; - vertical-align: middle; - border: 3px solid var(--content); - font-size: 16pt; - padding: 6px 12px; - margin: 10px auto; - background: var(--grey); - color: var(--content); - cursor: pointer; -} - -button:active { - color: var(--background); - background: var(--content); -} - -code { - background: var(--grey); -} - -.breadcrumb { - font-size: 1.2rem; - font-style: italic; -} diff --git a/tools/challenge-editor/client/src/index.tsx b/tools/challenge-editor/client/src/index.tsx deleted file mode 100644 index d6f53d10cda..00000000000 --- a/tools/challenge-editor/client/src/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './app'; - -ReactDOM.render( - - - , - document.getElementById('root') -); diff --git a/tools/challenge-editor/client/src/utils/handle-request.ts b/tools/challenge-editor/client/src/utils/handle-request.ts deleted file mode 100644 index 12671c8c0be..00000000000 --- a/tools/challenge-editor/client/src/utils/handle-request.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const handleRequest = (makeRequest: () => Promise) => () => { - makeRequest() - .then( - res => - res.json() as Promise<{ - stdout?: string; - stderr?: string; - message?: string; - }> - ) - .then(data => { - if (data.message) { - alert(data.message); - } else { - alert(JSON.stringify(data)); - } - }) - .catch(err => console.error(err)); -}; - -export const API_LOCATION = import.meta.env - .CHALLENGE_EDITOR_API_LOCATION as string; diff --git a/tools/challenge-editor/client/src/vite-app-env.d.ts b/tools/challenge-editor/client/src/vite-app-env.d.ts deleted file mode 100644 index 35412df1a73..00000000000 --- a/tools/challenge-editor/client/src/vite-app-env.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/// - -interface ImportMetaEnv { - readonly CHALLENGE_EDITOR_LEARN_CLIENT_LOCATION: string; -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} diff --git a/tools/challenge-editor/client/tsconfig.json b/tools/challenge-editor/client/tsconfig.json deleted file mode 100644 index 7ab42d87f17..00000000000 --- a/tools/challenge-editor/client/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "types": ["node"] - } -} diff --git a/tools/challenge-editor/client/vite.config.ts b/tools/challenge-editor/client/vite.config.ts deleted file mode 100644 index a28154f931d..00000000000 --- a/tools/challenge-editor/client/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - base: '/', - plugins: [react()], - envPrefix: 'CHALLENGE_EDITOR_', - server: { - port: 3300 - } -});