Added an exercise to write in blank spaces
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-lineto": "^3.3.0",
|
"react-lineto": "^3.3.0",
|
||||||
|
"react-player": "^2.12.0",
|
||||||
"react-string-replace": "^1.1.0",
|
"react-string-replace": "^1.1.0",
|
||||||
"typescript": "4.9.5",
|
"typescript": "4.9.5",
|
||||||
"zustand": "^4.3.6"
|
"zustand": "^4.3.6"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
||||||
import {FillBlanksExercise} from "@/interfaces/exam";
|
import {FillBlanksExercise} from "@/interfaces/exam";
|
||||||
import {Dialog, Transition} from "@headlessui/react";
|
import {Dialog, Transition} from "@headlessui/react";
|
||||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
||||||
import Icon from "@mdi/react";
|
import Icon from "@mdi/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {Fragment, useState} from "react";
|
import {Fragment, useState} from "react";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { CommonProps } from ".";
|
import {CommonProps} from ".";
|
||||||
|
|
||||||
interface WordsPopoutProps {
|
interface WordsPopoutProps {
|
||||||
words: {word: string; isDisabled: boolean}[];
|
words: {word: string; isDisabled: boolean}[];
|
||||||
@@ -79,7 +79,7 @@ export default function FillBlanks({allowRepetition, prompt, solutions, text, wo
|
|||||||
const renderLines = (line: string) => {
|
const renderLines = (line: string) => {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{reactStringReplace(line, /({{\d}})/g, (match) => {
|
{reactStringReplace(line, /({{\d+}})/g, (match) => {
|
||||||
const id = match.replaceAll(/[\{\}]/g, "");
|
const id = match.replaceAll(/[\{\}]/g, "");
|
||||||
const userSolution = userSolutions.find((x) => x.id === id);
|
const userSolution = userSolutions.find((x) => x.id === id);
|
||||||
|
|
||||||
@@ -95,28 +95,28 @@ export default function FillBlanks({allowRepetition, prompt, solutions, text, wo
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<WordsPopout
|
<WordsPopout
|
||||||
words={words.map((word) => ({word, isDisabled: allowRepetition ? false : userSolutions.map((x) => x.solution).includes(word)}))}
|
words={words.map((word) => ({word, isDisabled: allowRepetition ? false : userSolutions.map((x) => x.solution).includes(word)}))}
|
||||||
isOpen={!!currentBlankId}
|
isOpen={!!currentBlankId}
|
||||||
onCancel={() => setCurrentBlankId(undefined)}
|
onCancel={() => setCurrentBlankId(undefined)}
|
||||||
onAnswer={(solution: string) => {
|
onAnswer={(solution: string) => {
|
||||||
setUserSolutions((prev) => [...prev.filter((x) => x.id !== currentBlankId), {id: currentBlankId!, solution}]);
|
setUserSolutions((prev) => [...prev.filter((x) => x.id !== currentBlankId), {id: currentBlankId!, solution}]);
|
||||||
setCurrentBlankId(undefined);
|
setCurrentBlankId(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-lg font-medium text-center px-48">{prompt}</span>
|
<span className="text-lg font-medium text-center px-48">{prompt}</span>
|
||||||
<span>
|
<span>
|
||||||
{text.split("\n").map((line) => (
|
{text.split("\n").map((line) => (
|
||||||
<>
|
<>
|
||||||
{renderLines(line)}
|
{renderLines(line)}
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex gap-8">
|
<div className="self-end flex gap-8">
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
|
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
|
||||||
<div className="absolute left-4">
|
<div className="absolute left-4">
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
<Icon path={mdiArrowLeft} color="white" size={1} />
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default function MultipleChoice({prompt, questions, onNext, onBack}: Mult
|
|||||||
};
|
};
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (questionIndex === questions.length) {
|
if (questionIndex === questions.length - 1) {
|
||||||
onNext();
|
onNext();
|
||||||
} else {
|
} else {
|
||||||
setQuestionIndex((prev) => prev + 1);
|
setQuestionIndex((prev) => prev + 1);
|
||||||
|
|||||||
56
src/components/Exercises/WriteBlanks.tsx
Normal file
56
src/components/Exercises/WriteBlanks.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 dynamic from "next/dynamic";
|
||||||
import FillBlanks from "./FillBlanks";
|
import FillBlanks from "./FillBlanks";
|
||||||
import MultipleChoice from "./MultipleChoice";
|
import MultipleChoice from "./MultipleChoice";
|
||||||
|
import WriteBlanks from "./WriteBlanks";
|
||||||
|
|
||||||
const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false});
|
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} />;
|
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
case "multipleChoice":
|
case "multipleChoice":
|
||||||
return <MultipleChoice {...(exercise as MultipleChoiceExercise)} onNext={onNext} onBack={onBack} />;
|
return <MultipleChoice {...(exercise as MultipleChoiceExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
|
case "writeBlanks":
|
||||||
|
return <WriteBlanks {...(exercise as WriteBlanksExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 Sydney’s {{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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ export interface ListeningExam {
|
|||||||
exercises: Exercise[];
|
exercises: Exercise[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise;
|
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise | WriteBlanksExercise;
|
||||||
|
|
||||||
export interface FillBlanksExercise {
|
export interface FillBlanksExercise {
|
||||||
prompt: string; // *EXAMPLE: "Complete the summary below. Click a blank to select the corresponding word for it."
|
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 {
|
export interface MatchSentencesExercise {
|
||||||
type: "matchSentences";
|
type: "matchSentences";
|
||||||
prompt: string;
|
prompt: string;
|
||||||
|
|||||||
31
yarn.lock
31
yarn.lock
@@ -716,6 +716,11 @@ deep-is@^0.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
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:
|
define-lazy-prop@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
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"
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
|
||||||
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
|
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:
|
locate-path@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||||
@@ -1720,6 +1730,11 @@ lru-cache@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
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:
|
merge2@^1.3.0, merge2@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
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"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.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:
|
react-is@^16.13.1, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
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"
|
prop-types "15.7.2"
|
||||||
react "17.0.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:
|
react-string-replace@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-1.1.0.tgz#a3f7b458e697e77d70b0ea663caf38ab38f7cc17"
|
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-1.1.0.tgz#a3f7b458e697e77d70b0ea663caf38ab38f7cc17"
|
||||||
|
|||||||
Reference in New Issue
Block a user