Updated the FillBlanks exercise and solution to the new design

This commit is contained in:
Tiago Ribeiro
2023-06-18 22:02:48 +01:00
parent 84b0b8ac42
commit 52218ff8b8
7 changed files with 136 additions and 20411 deletions

18267
.pnp.cjs generated

File diff suppressed because one or more lines are too long

2047
.pnp.loader.mjs generated

File diff suppressed because it is too large Load Diff

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
yarnPath: .yarn/releases/yarn-1.22.1.cjs

View File

@@ -54,5 +54,6 @@
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.2.4"
} },
"packageManager": "yarn@1.22.1"
} }

View File

@@ -4,75 +4,81 @@ 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, useEffect, useState} from "react";
import reactStringReplace from "react-string-replace"; import reactStringReplace from "react-string-replace";
import {CommonProps} from "."; import {CommonProps} from ".";
import Button from "../Low/Button";
interface WordsPopoutProps { interface WordsDrawerProps {
words: {word: string; isDisabled: boolean}[]; words: {word: string; isDisabled: boolean}[];
isOpen: boolean; isOpen: boolean;
blankId?: string;
previouslySelectedWord?: string;
onCancel: () => void; onCancel: () => void;
onAnswer: (answer: string) => void; onAnswer: (answer: string) => void;
} }
function WordsPopout({words, isOpen, onCancel, onAnswer}: WordsPopoutProps) { function WordsDrawer({words, isOpen, blankId, previouslySelectedWord, onCancel, onAnswer}: WordsDrawerProps) {
return ( const [selectedWord, setSelectedWord] = useState<string | undefined>(previouslySelectedWord);
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onCancel}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto"> useEffect(() => setSelectedWord(previouslySelectedWord), [previouslySelectedWord]);
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child return (
as={Fragment} <>
enter="ease-out duration-300" <div
enterFrom="opacity-0 scale-95" className={clsx(
enterTo="opacity-100 scale-100" "w-full h-full absolute top-0 left-0 bg-gradient-to-t from-mti-black to-transparent z-10",
leave="ease-in duration-200" isOpen ? "visible opacity-10" : "invisible opacity-0",
leaveFrom="opacity-100 scale-100" )}
leaveTo="opacity-0 scale-95"> />
<Dialog.Panel className="w-fit transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all flex flex-col"> <div
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900"> className={clsx(
List of words "absolute w-full bg-white px-7 py-8 bottom-0 left-0 shadow-2xl rounded-2xl z-20 flex flex-col gap-8 transition-opacity duration-300 ease-in-out",
</Dialog.Title> isOpen ? "visible opacity-100" : "invisible opacity-0",
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> )}>
{words.map((word) => ( <div className="w-full flex gap-2">
<div className="rounded-full w-6 h-6 flex items-center justify-center text-white bg-mti-green-light">{blankId}</div>
<span> Choose the correct word:</span>
</div>
<div className="grid grid-cols-6 gap-6">
{words.map(({word, isDisabled}) => (
<button <button
key={word.word} key={word}
onClick={() => onAnswer(word.word)} onClick={() => setSelectedWord((prev) => (prev === word ? undefined : word))}
disabled={word.isDisabled} className={clsx(
className={clsx("btn sm:btn-wide gap-4 relative text-white", infoButtonStyle)}> "rounded-full py-3 text-center transition duration-300 ease-in-out",
{word.word} selectedWord === word ? "text-white bg-mti-green-light" : "bg-mti-green-ultralight",
!isDisabled && "hover:text-white hover:bg-mti-green",
"disabled:cursor-not-allowed disabled:text-mti-gray-dim",
)}
disabled={isDisabled}>
{word}
</button> </button>
))} ))}
</div> </div>
<div className="flex justify-between w-full">
<div className="mt-4 self-end"> <Button color="green" variant="outline" className="max-w-[200px] w-full" onClick={onCancel}>
<button onClick={onCancel} className={clsx("btn md:btn-wide gap-4 relative text-white", errorButtonStyle)}> Back
Close </Button>
</button> <Button color="green" className="max-w-[200px] w-full" onClick={() => onAnswer(selectedWord!)} disabled={!selectedWord}>
</div> Confirm
</Dialog.Panel> </Button>
</Transition.Child>
</div> </div>
</div> </div>
</Dialog> </>
</Transition>
); );
} }
export default function FillBlanks({id, allowRepetition, type, prompt, solutions, text, words, onNext, onBack}: FillBlanksExercise & CommonProps) { export default function FillBlanks({id, allowRepetition, type, prompt, solutions, text, words, onNext, onBack}: FillBlanksExercise & CommonProps) {
const [userSolutions, setUserSolutions] = useState<{id: string; solution: string}[]>([]); const [userSolutions, setUserSolutions] = useState<{id: string; solution: string}[]>([]);
const [currentBlankId, setCurrentBlankId] = useState<string>(); const [currentBlankId, setCurrentBlankId] = useState<string>();
const [blankSelectedWord, setBlankSelectedWord] = useState<string>();
useEffect(() => {
setBlankSelectedWord(currentBlankId ? userSolutions.find((x) => x.id === currentBlankId)?.solution : undefined);
}, [userSolutions, currentBlankId]);
useEffect(() => console.log({blankSelectedWord}), [blankSelectedWord]);
const calculateScore = () => { const calculateScore = () => {
const total = text.match(/({{\d+}})/g)?.length || 0; const total = text.match(/({{\d+}})/g)?.length || 0;
@@ -81,15 +87,26 @@ export default function FillBlanks({id, allowRepetition, type, prompt, solutions
return {total, correct}; return {total, correct};
}; };
useEffect(() => {
console.log(currentBlankId, userSolutions.find((x) => x.id === currentBlankId)?.solution);
}, [userSolutions, currentBlankId]);
const renderLines = (line: string) => { const renderLines = (line: string) => {
return ( return (
<span> <span className="text-base leading-5">
{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);
return ( return (
<button className="border-2 rounded-xl px-4 text-blue-400 border-blue-400 my-2" onClick={() => setCurrentBlankId(id)}> <button
className={clsx(
"rounded-full hover:text-white hover:bg-mti-green transition duration-300 ease-in-out my-1",
!userSolution && "w-6 h-6 text-center text-mti-green-light bg-mti-green-ultralight",
currentBlankId === id && "text-white !bg-mti-green-light ",
userSolution && "px-5 py-2 text-center text-white bg-mti-green-light",
)}
onClick={() => setCurrentBlankId(id)}>
{userSolution ? userSolution.solution : id} {userSolution ? userSolution.solution : id}
</button> </button>
); );
@@ -100,9 +117,11 @@ export default function FillBlanks({id, allowRepetition, type, prompt, solutions
return ( return (
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4 mt-4 h-full mb-20">
<WordsPopout <WordsDrawer
blankId={currentBlankId}
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)}))}
previouslySelectedWord={blankSelectedWord}
isOpen={!!currentBlankId} isOpen={!!currentBlankId}
onCancel={() => setCurrentBlankId(undefined)} onCancel={() => setCurrentBlankId(undefined)}
onAnswer={(solution: string) => { onAnswer={(solution: string) => {
@@ -110,7 +129,7 @@ export default function FillBlanks({id, allowRepetition, type, prompt, solutions
setCurrentBlankId(undefined); setCurrentBlankId(undefined);
}} }}
/> />
<span className="text-base md:text-lg font-medium text-center px-2 md:px-4 lg:px-48"> <span className="text-sm w-full leading-6">
{prompt.split("\\n").map((line, index) => ( {prompt.split("\\n").map((line, index) => (
<Fragment key={index}> <Fragment key={index}>
{line} {line}
@@ -118,31 +137,27 @@ export default function FillBlanks({id, allowRepetition, type, prompt, solutions
</Fragment> </Fragment>
))} ))}
</span> </span>
<span> <span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
{text.split("\\n").map((line, index) => ( {text.split("\\n").map((line, index) => (
<Fragment key={index}> <p key={index}>
{renderLines(line)} {renderLines(line)}
<br /> <br />
</Fragment> </p>
))} ))}
</span> </span>
</div> </div>
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8"> <div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}> <Button color="green" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
Back Back
</button> </Button>
<button
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} <Button
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}> color="green"
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
className="max-w-[200px] self-end w-full">
Next Next
<div className="absolute right-4"> </Button>
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</div> </div>
</> </>
); );

View File

@@ -16,7 +16,10 @@ export default function Layout({user, children, className}: Props) {
<div className="h-full w-full flex py-4 pb-8 gap-2"> <div className="h-full w-full flex py-4 pb-8 gap-2">
<Sidebar path={window.location.pathname} /> <Sidebar path={window.location.pathname} />
<div <div
className={clsx("w-5/6 min-h-full h-fit mr-8 bg-white shadow-md rounded-2xl p-12 pb-8 flex flex-col gap-12 relative", className)}> className={clsx(
"w-5/6 min-h-full h-fit mr-8 bg-white shadow-md rounded-2xl p-12 pb-8 flex flex-col gap-12 relative overflow-hidden",
className,
)}>
{children} {children}
</div> </div>
</div> </div>

View File

@@ -6,6 +6,7 @@ import clsx from "clsx";
import reactStringReplace from "react-string-replace"; import reactStringReplace from "react-string-replace";
import {CommonProps} from "."; import {CommonProps} from ".";
import {Fragment} from "react"; import {Fragment} from "react";
import Button from "../Low/Button";
export default function FillBlanksSolutions({prompt, solutions, text, userSolutions, onNext, onBack}: FillBlanksExercise & CommonProps) { export default function FillBlanksSolutions({prompt, solutions, text, userSolutions, onNext, onBack}: FillBlanksExercise & CommonProps) {
const renderLines = (line: string) => { const renderLines = (line: string) => {
@@ -18,23 +19,46 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
if (!userSolution) { if (!userSolution) {
return ( return (
<> <button
<button className={clsx("border-2 rounded-xl px-4 text-gray-500 border-gray-500 my-2")}>{solution.solution}</button> className={clsx(
</> "rounded-full hover:text-white hover:bg-mti-blue transition duration-300 ease-in-out my-1",
userSolution && "px-5 py-2 text-center text-white bg-mti-blue-light",
)}>
{solution.solution}
</button>
); );
} }
if (userSolution.solution === solution.solution) { if (userSolution.solution === solution.solution) {
return <button className={clsx("border-2 rounded-xl px-4 text-green-500 border-green-500 my-2")}>{solution.solution}</button>; return (
<button
className={clsx(
"rounded-full hover:text-white hover:bg-mti-green transition duration-300 ease-in-out my-1",
userSolution && "px-5 py-2 text-center text-white bg-mti-green-light",
)}>
{solution.solution}
</button>
);
} }
if (userSolution.solution !== solution.solution) { if (userSolution.solution !== solution.solution) {
return ( return (
<> <>
<button className={clsx("border-2 rounded-xl px-4 text-red-500 border-red-500 mr-1 my-2")}> <button
className={clsx(
"rounded-full hover:text-white hover:bg-mti-orange transition duration-300 ease-in-out my-1",
userSolution && "px-5 py-2 text-center text-white bg-mti-orange-light",
)}>
{userSolution.solution} {userSolution.solution}
</button> </button>
<button className={clsx("border-2 rounded-xl px-4 text-green-400 border-green-400 my-2")}>{solution.solution}</button>
<button
className={clsx(
"rounded-full hover:text-white hover:bg-mti-green transition duration-300 ease-in-out my-1",
userSolution && "px-5 py-2 text-center text-white bg-mti-green-light",
)}>
{solution.solution}
</button>
</> </>
); );
} }
@@ -45,8 +69,8 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
return ( return (
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4 mt-4 h-full mb-20">
<span className="text-base md:text-lg font-medium text-center px-2 md:px-4 lg:px-48"> <span className="text-sm w-full leading-6">
{prompt.split("\\n").map((line, index) => ( {prompt.split("\\n").map((line, index) => (
<Fragment key={index}> <Fragment key={index}>
{line} {line}
@@ -54,29 +78,24 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
</Fragment> </Fragment>
))} ))}
</span> </span>
<span> <span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
{text.split("\\n").map((line, index) => ( {text.split("\\n").map((line, index) => (
<Fragment key={index}> <p key={index}>
{renderLines(line)} {renderLines(line)}
<br /> <br />
</Fragment> </p>
))} ))}
</span> </span>
</div> </div>
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8"> <div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}> <Button color="green" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
Back Back
</button> </Button>
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onNext}>
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
Next Next
<div className="absolute right-4"> </Button>
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</div> </div>
</> </>
); );