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

View File

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