Added an exercise to write in blank spaces

This commit is contained in:
Tiago Ribeiro
2023-03-28 16:10:43 +01:00
parent 2b38f9df9b
commit 780d208675
8 changed files with 156 additions and 27 deletions

View File

@@ -28,6 +28,7 @@
"react-chartjs-2": "^5.2.0",
"react-dom": "18.2.0",
"react-lineto": "^3.3.0",
"react-player": "^2.12.0",
"react-string-replace": "^1.1.0",
"typescript": "4.9.5",
"zustand": "^4.3.6"

View File

@@ -1,12 +1,12 @@
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import {FillBlanksExercise} from "@/interfaces/exam";
import {Dialog, Transition} from "@headlessui/react";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react";
import clsx from "clsx";
import {Fragment, useState} from "react";
import reactStringReplace from "react-string-replace";
import { CommonProps } from ".";
import {CommonProps} from ".";
interface WordsPopoutProps {
words: {word: string; isDisabled: boolean}[];
@@ -79,7 +79,7 @@ export default function FillBlanks({allowRepetition, prompt, solutions, text, wo
const renderLines = (line: string) => {
return (
<span>
{reactStringReplace(line, /({{\d}})/g, (match) => {
{reactStringReplace(line, /({{\d+}})/g, (match) => {
const id = match.replaceAll(/[\{\}]/g, "");
const userSolution = userSolutions.find((x) => x.id === id);

View File

@@ -61,7 +61,7 @@ export default function MultipleChoice({prompt, questions, onNext, onBack}: Mult
};
const next = () => {
if (questionIndex === questions.length) {
if (questionIndex === questions.length - 1) {
onNext();
} else {
setQuestionIndex((prev) => prev + 1);

View File

@@ -0,0 +1,56 @@
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import {WriteBlanksExercise} from "@/interfaces/exam";
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react";
import clsx from "clsx";
import {useState} from "react";
import reactStringReplace from "react-string-replace";
import {CommonProps} from ".";
export default function WriteBlanks({prompt, maxWords, solutions, text, onNext, onBack}: WriteBlanksExercise & CommonProps) {
const [userSolutions, setUserSolutions] = useState<{id: string; solution: string}[]>([]);
const renderLines = (line: string) => {
return (
<span>
{reactStringReplace(line, /({{\d+}})/g, (match) => {
const id = match.replaceAll(/[\{\}]/g, "");
const userSolution = userSolutions.find((x) => x.id === id);
return <input className="input border rounded-xl px-2 text-blue-400 border-blue-400 py-1" placeholder={id} />;
})}
</span>
);
};
return (
<>
<div className="flex flex-col">
<span className="text-lg font-medium text-center px-48">{prompt}</span>
<span>
{text.split("\n").map((line) => (
<>
{renderLines(line)}
<br />
</>
))}
</span>
</div>
<div className="self-end flex gap-8">
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
Back
</button>
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onNext}>
Next
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</div>
</>
);
}

View File

@@ -1,7 +1,8 @@
import {Exercise, FillBlanksExercise, MatchSentencesExercise, MultipleChoiceExercise} from "@/interfaces/exam";
import {Exercise, FillBlanksExercise, MatchSentencesExercise, MultipleChoiceExercise, WriteBlanksExercise} from "@/interfaces/exam";
import dynamic from "next/dynamic";
import FillBlanks from "./FillBlanks";
import MultipleChoice from "./MultipleChoice";
import WriteBlanks from "./WriteBlanks";
const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false});
@@ -18,5 +19,7 @@ export const renderExercise = (exercise: Exercise, onNext: () => void, onBack: (
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
case "multipleChoice":
return <MultipleChoice {...(exercise as MultipleChoiceExercise)} onNext={onNext} onBack={onBack} />;
case "writeBlanks":
return <WriteBlanks {...(exercise as WriteBlanksExercise)} onNext={onNext} onBack={onBack} />;
}
};

View File

@@ -59,6 +59,33 @@
]
}
]
},
{
"type": "writeBlanks",
"prompt": "Complete the notes below by writing NO MORE THAN THREE WORDS in the spaces provided.",
"maxWords": 3,
"text": "The Government plans to give ${{14}} to assist the farmers. This money was to be spent on improving Sydneys {{15}} but has now been re-allocated. Australia has experienced its worst drought in over fifty years. Farmers say that the money will not help them because it is {{16}}.",
"solutions": [
{
"id": "14",
"solution": [
"250 million"
]
},
{
"id": "15",
"solution": [
"roads",
"road system"
]
},
{
"id": "16",
"solution": [
"too late"
]
}
]
}
]
}

View File

@@ -16,7 +16,7 @@ export interface ListeningExam {
exercises: Exercise[];
}
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise;
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise | WriteBlanksExercise;
export interface FillBlanksExercise {
prompt: string; // *EXAMPLE: "Complete the summary below. Click a blank to select the corresponding word for it."
@@ -30,6 +30,17 @@ export interface FillBlanksExercise {
}[];
}
export interface WriteBlanksExercise {
prompt: string; // *EXAMPLE: "Complete the notes below by writing NO MORE THAN THREE WORDS in the spaces provided."
maxWords: number; // *EXAMPLE: 3 - The maximum amount of words allowed per blank, 0 for unlimited
type: "writeBlanks";
text: string; // *EXAMPLE: "The Government plans to give ${{14}}"
solutions: {
id: string; // *EXAMPLE: "14"
solution: string[]; // *EXAMPLE: ["Prescott"] - All possible solutions (case sensitive)
}[];
}
export interface MatchSentencesExercise {
type: "matchSentences";
prompt: string;

View File

@@ -716,6 +716,11 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^4.0.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
@@ -1689,6 +1694,11 @@ lilconfig@^2.0.5, lilconfig@^2.0.6:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
load-script@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4"
integrity sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -1720,6 +1730,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
memoize-one@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@@ -2080,6 +2095,11 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-fast-compare@^3.0.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz#53933d9e14f364281d6cba24bfed7a4afb808b5f"
integrity sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==
react-is@^16.13.1, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -2093,6 +2113,17 @@ react-lineto@^3.3.0:
prop-types "15.7.2"
react "17.0.2"
react-player@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.12.0.tgz#2fc05dbfec234c829292fbca563b544064bd14f0"
integrity sha512-rymLRz/2GJJD+Wc01S7S+i9pGMFYnNmQibR2gVE3KmHJCBNN8BhPAlOPTGZtn1uKpJ6p4RPLlzPQ1OLreXd8gw==
dependencies:
deepmerge "^4.0.0"
load-script "^1.0.0"
memoize-one "^5.1.1"
prop-types "^15.7.2"
react-fast-compare "^3.0.1"
react-string-replace@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-1.1.0.tgz#a3f7b458e697e77d70b0ea663caf38ab38f7cc17"