diff --git a/.gitignore b/.gitignore
index 46096f2..46d2bf0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
node_modules
package-lock.json
.DS_Store
+client/src/Exercises
+src/Exercises
\ No newline at end of file
diff --git a/client/exercises/01-stuff.md b/client/exercises/01-stuff.md
new file mode 100644
index 0000000..e58d853
--- /dev/null
+++ b/client/exercises/01-stuff.md
@@ -0,0 +1,5 @@
+## Some stuff
+
+This lesson is about some stuff™️.
+
+I'm writing some things here things, blah, blah, blah.
\ No newline at end of file
diff --git a/client/exercises/02-more-stuff.md b/client/exercises/02-more-stuff.md
new file mode 100644
index 0000000..058f13e
--- /dev/null
+++ b/client/exercises/02-more-stuff.md
@@ -0,0 +1,5 @@
+## Some more stuff
+
+This lesson is about some more stuff™️.
+
+I'm writing some more things here, blah, blah, blah.
\ No newline at end of file
diff --git a/client/exercises/03-test-content.md b/client/exercises/03-test-content.md
new file mode 100644
index 0000000..276d72e
--- /dev/null
+++ b/client/exercises/03-test-content.md
@@ -0,0 +1,7 @@
+## Test lesson
+
+This lesson is about some more stuff™️.
+
+I'm writing some more things here, with element by ID, blah, blah.
+
+Something something set content something else
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index 8b0b3e1..ee589a4 100644
--- a/client/package.json
+++ b/client/package.json
@@ -21,9 +21,10 @@
"web-vitals": "^2.1.4"
},
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "gen-exercises": "node ./scripts/generate-exercises.js ",
+ "start": "npm run gen-exercises && react-scripts start",
+ "build": "npm run gen-exercises && react-scripts build",
+ "test": "npm run gen-exercises && react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
diff --git a/client/scripts/blocks-to-categories.json b/client/scripts/blocks-to-categories.json
new file mode 100644
index 0000000..17dba87
--- /dev/null
+++ b/client/scripts/blocks-to-categories.json
@@ -0,0 +1,23 @@
+{
+ "things": {
+ "categoryType": "Values",
+ "blockType": "get_randomWord"
+ },
+ "here": {
+ "categoryType": "HTML",
+ "blockType": "on_start"
+ },
+ "blah": {
+ "categoryType": "Values",
+ "blockType": "text"
+ },
+ "with element by ID": {
+ "categoryType": "HTML",
+ "blockType": "with_element_by_id"
+ },
+ "set content": {
+ "categoryType": "HTML",
+ "blockType": "set_content",
+ "blockxml": " "
+ }
+}
\ No newline at end of file
diff --git a/client/scripts/generate-exercises.js b/client/scripts/generate-exercises.js
new file mode 100644
index 0000000..6e1497c
--- /dev/null
+++ b/client/scripts/generate-exercises.js
@@ -0,0 +1,122 @@
+const fs = require('fs')
+const path = require('path');
+
+const btcContents = fs.readFileSync(path.join(__dirname, 'blocks-to-categories.json'))
+const btcJson = JSON.parse(btcContents)
+
+function parseToolbox(text) {
+ const blockPattern = /(\w+)/g
+
+ let blocks = [...text.matchAll(blockPattern)]
+ .map((block) => block[1])
+
+ blocks = [...new Set(blocks)]
+
+ let categories = {}
+ let toolboxContents = []
+
+ for (let blockName of blocks) {
+ if (!(blockName in btcJson)) {
+ console.warn(`${blockName} not defined in blocks-to-categories.json`)
+ continue
+ }
+
+ const blockDetails = btcJson[blockName]
+ const categoryName = blockDetails.categoryType
+
+ const blockEntry = {
+ kind: 'block',
+ type: blockDetails.blockType
+ }
+
+ if ('blockxml' in blockDetails) { blockEntry.blockxml = blockDetails.blockxml }
+
+ if (categoryName in categories) {
+ categories[categoryName].push(blockEntry)
+ } else {
+ categories[categoryName] = [blockEntry]
+ }
+ }
+
+ for (let category in categories) {
+ toolboxContents.push({
+ kind: "category",
+ name: category,
+ contents: categories[category]
+ })
+ }
+
+ return JSON.stringify({
+ kind: "categoryToolbox",
+ contents: toolboxContents
+ }, null, 2)
+}
+
+function writeExercisesIndexJs(exerciseInputDir) {
+ let exercisesFileNames = fs.readdirSync(exerciseInputDir).filter((filename) => filename.endsWith('.md'))
+
+ exercisesFileNames.forEach((filename) => {
+ const filePath = path.join(exerciseInputDir, filename)
+ const fileText = fs.readFileSync(filePath, 'utf8')
+ const filenameNoExt = filename.slice(0, -3)
+
+ const exerciseOutputDir = path.join(__dirname, '..', 'src', 'Exercises', filenameNoExt)
+ const exerciseMd = path.join(exerciseOutputDir, 'lesson.md')
+ const exerciseIndexJs = path.join(exerciseOutputDir, 'index.js')
+
+ fs.mkdirSync(exerciseOutputDir, { recursive: true })
+ fs.copyFileSync(filePath, exerciseMd)
+
+ const exerciseIndexJsContent = `
+import LessonMarkdown from "../../LessonMarkdown";
+import markdownUrl from "./lesson.md";
+
+export function Lesson() {
+ return ;
+}
+
+export const toolbox = ${parseToolbox(fileText)}`
+
+ fs.writeFileSync(exerciseIndexJs, exerciseIndexJsContent)
+ })
+}
+
+function writeRootExercisesIndexJs(exerciseInputDir) {
+ let exercisesFileNames = fs.readdirSync(exerciseInputDir).filter((filename) => filename.endsWith('.md'))
+ const exerciseAliases = []
+
+ let rootIndexJsContent = ""
+ const rootIndexJs = path.join(__dirname, '..', 'src', 'Exercises', 'index.js')
+
+ exercisesFileNames.forEach((filename) => {
+ let exerciseNoExt = filename.slice(0,-3)
+
+ // Starting with _ so that the alias is ES6 compatible, in case file name starts with number
+ let exerciseAlias = `_${exerciseNoExt.replaceAll('-', '_')}`
+
+ exerciseAliases.push(exerciseAlias)
+
+ rootIndexJsContent += `import * as ${exerciseAlias} from "./${exerciseNoExt}"\n`
+ })
+
+ rootIndexJsContent += `\n\nexport default ${JSON
+ .stringify(exerciseAliases, null, 2)
+ .replaceAll('"', '')}`
+
+ fs.writeFileSync(rootIndexJs, rootIndexJsContent)
+}
+
+function generateExercises() {
+ const rootDir = path.join(__dirname, '..')
+ const relExerciseInputDir = './exercises'
+
+ const absExerciseInputDir = path.join(rootDir, './exercises')
+
+ // Create exercises folders and index
+ writeExercisesIndexJs(absExerciseInputDir)
+
+ // Create root exercises index
+ writeRootExercisesIndexJs(absExerciseInputDir)
+}
+
+generateExercises()
\ No newline at end of file
diff --git a/client/src/App.js b/client/src/App.js
index ae8ace5..24bc488 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -7,8 +7,7 @@ import "./Blocks/dom";
import "./Blocks/cyf";
import useBlockly from "./Blockly/useBlockly";
-import * as Exercise1 from "./Exercises/01-stuff";
-import * as Exercise2 from "./Exercises/02-more-stuff";
+import exercises from "./Exercises"
import Split from "react-split-grid";
import TextPanel from "./TextPanel/TextPanel";
@@ -16,16 +15,12 @@ import Output from "./Output/Output";
import Header from "./Layout/Header/Header";
import Menu from "./Layout/Menu/Menu";
import Footer from "./Layout/Footer/Footer";
-import Button from "./Button/Button";
import "./App.scss";
import { ReactComponent as Background } from "../src/svgs/Humaaans-Phone.svg";
Blockly.setLocale(locale);
-// just left all this and presumed you will pass whatever you decide to do into the text panel
-const exercises = [Exercise1, Exercise2];
-
function useExercise() {
const [exerciseIndex, setExerciseIndex] = useState(0);
diff --git a/client/src/Exercises/01-stuff/index.js b/client/src/Exercises/01-stuff/index.js
deleted file mode 100644
index 155e81e..0000000
--- a/client/src/Exercises/01-stuff/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import LessonMarkdown from "../../LessonMarkdown";
-import markdownUrl from "./lesson.md";
-
-export function Lesson() {
- return ;
-}
-
-export const toolbox = {
- kind: "categoryToolbox",
- contents: [
- {
- kind: "category",
- name: "Values",
- contents: [
- {
- kind: "block",
- type: "text",
- },
- {
- kind: "block",
- type: "get_randomWord",
- },
- ],
- },
- {
- kind: "category",
- name: "HTML",
- contents: [
- {
- kind: "block",
- type: "on_start",
- },
- {
- kind: "block",
- type: "with_element_by_id",
- },
- {
- kind: "block",
- type: "set_content",
- blockxml:
- " ",
- },
- ],
- },
- ],
-};
diff --git a/client/src/Exercises/01-stuff/lesson.md b/client/src/Exercises/01-stuff/lesson.md
deleted file mode 100644
index fd1b8ee..0000000
--- a/client/src/Exercises/01-stuff/lesson.md
+++ /dev/null
@@ -1,5 +0,0 @@
-## Some stuff
-
-This lesson is about some stuff™️.
-
-I'm writing some things here, blah, blah, blah.
diff --git a/client/src/Exercises/02-more-stuff/index.js b/client/src/Exercises/02-more-stuff/index.js
deleted file mode 100644
index 111e3c6..0000000
--- a/client/src/Exercises/02-more-stuff/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import LessonMarkdown from "../../LessonMarkdown";
-import markdownUrl from "./lesson.md";
-
-export function Lesson() {
- return ;
-}
-
-export const toolbox = {
- kind: "categoryToolbox",
- contents: [
- {
- kind: "category",
- name: "Values",
- contents: [
- {
- kind: "block",
- type: "math_number",
- },
- {
- kind: "block",
- type: "text",
- },
- {
- kind: "block",
- type: "colour_picker",
- },
- {
- kind: "block",
- type: "logic_boolean",
- },
- ],
- },
- {
- kind: "category",
- name: "HTML",
- contents: [
- {
- kind: "block",
- type: "on_start",
- },
- {
- kind: "block",
- type: "with_element_by_id",
- },
- {
- kind: "block",
- type: "set_content",
- blockxml:
- " ",
- },
- {
- kind: "block",
- type: "set_attribute",
- },
- ],
- },
- ],
-};
diff --git a/client/src/Exercises/02-more-stuff/lesson.md b/client/src/Exercises/02-more-stuff/lesson.md
deleted file mode 100644
index b8863ec..0000000
--- a/client/src/Exercises/02-more-stuff/lesson.md
+++ /dev/null
@@ -1,5 +0,0 @@
-## Some more stuff
-
-This lesson is about some more stuff™️.
-
-I'm writing some more things here, blah, blah, blah.