Navigation rework, added prompt edit to components that were missing
This commit is contained in:
@@ -1,95 +1,88 @@
|
||||
import { renderExercise } from "@/components/Exercises";
|
||||
import ModuleTitle from "@/components/Medium/ModuleTitle";
|
||||
import { renderSolution } from "@/components/Solutions";
|
||||
import { UserSolution, WritingExam } from "@/interfaces/exam";
|
||||
import useExamStore, { usePersistentExamStore } from "@/stores/examStore";
|
||||
import { Exercise, UserSolution, WritingExam } from "@/interfaces/exam";
|
||||
import useExamStore, { usePersistentExamStore } from "@/stores/exam";
|
||||
import { countExercises } from "@/utils/moduleUtils";
|
||||
import PartDivider from "./Navigation/SectionDivider";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ExamProps } from "./types";
|
||||
import useExamTimer from "@/hooks/useExamTimer";
|
||||
import useExamNavigation from "./Navigation/useExamNavigation";
|
||||
import SectionNavbar from "./Navigation/SectionNavbar";
|
||||
import ProgressButtons from "./components/ProgressButtons";
|
||||
|
||||
interface Props {
|
||||
exam: WritingExam;
|
||||
showSolutions?: boolean;
|
||||
preview?: boolean;
|
||||
onFinish: (userSolutions: UserSolution[]) => void;
|
||||
}
|
||||
|
||||
export default function Writing({ exam, showSolutions = false, preview = false, onFinish }: Props) {
|
||||
const writingBgColor = "bg-ielts-writing-light";
|
||||
const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false, preview = false }) => {
|
||||
const updateTimers = useExamTimer(exam.module, preview);
|
||||
const userSolutionRef = useRef<(() => UserSolution) | null>(null);
|
||||
const [solutionWasUpdated, setSolutionWasUpdated] = useState(false);
|
||||
|
||||
const examState = useExamStore((state) => state);
|
||||
const persistentExamState = usePersistentExamStore((state) => state);
|
||||
|
||||
const {
|
||||
userSolutions,
|
||||
exerciseIndex,
|
||||
hasExamEnded,
|
||||
setBgColor,
|
||||
setUserSolutions,
|
||||
setHasExamEnded,
|
||||
setExerciseIndex,
|
||||
exerciseIndex, flags, setBgColor,
|
||||
setUserSolutions, setTimeIsUp, userSolutions,
|
||||
dispatch, navigation, timeSpentCurrentModule
|
||||
} = !preview ? examState : persistentExamState;
|
||||
|
||||
const [seenParts, setSeenParts] = useState<Set<number>>(new Set(showSolutions ? exam.exercises.map((_, index) => index) : []));
|
||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.exercises[0].intro === "string" && exam.exercises[0].intro !== "");
|
||||
const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60);
|
||||
|
||||
const { finalizeModule, timeIsUp } = flags;
|
||||
const { nextDisabled } = navigation;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExamEnded && exerciseIndex === -1) {
|
||||
setExerciseIndex(exerciseIndex + 1);
|
||||
}
|
||||
}, [hasExamEnded, exerciseIndex, setExerciseIndex]);
|
||||
if (finalizeModule || timeIsUp) {
|
||||
updateTimers();
|
||||
|
||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||
|
||||
useEffect(() => {
|
||||
if (!showSolutions && exam.exercises[exerciseIndex]?.intro !== undefined && exam.exercises[exerciseIndex]?.intro !== "" && !seenParts.has(exerciseIndex)) {
|
||||
setShowPartDivider(true);
|
||||
setBgColor(writingBgColor);
|
||||
if (timeIsUp) {
|
||||
setTimeIsUp(false);
|
||||
}
|
||||
dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: false } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [exerciseIndex]);
|
||||
}, [finalizeModule, timeIsUp])
|
||||
const {
|
||||
nextExercise, previousExercise,
|
||||
showPartDivider, setShowPartDivider,
|
||||
seenParts, setSeenParts,
|
||||
} = useExamNavigation({ exam, module: "writing", showSolutions, preview });
|
||||
|
||||
const registerSolution = useCallback((updateSolution: () => UserSolution) => {
|
||||
userSolutionRef.current = updateSolution;
|
||||
setSolutionWasUpdated(true);
|
||||
}, []);
|
||||
|
||||
const nextExercise = (solution?: UserSolution) => {
|
||||
scrollToTop();
|
||||
if (solution) {
|
||||
useEffect(() => {
|
||||
if (solutionWasUpdated && userSolutionRef.current) {
|
||||
const solution = userSolutionRef.current();
|
||||
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "writing", exam: exam.id }]);
|
||||
setSolutionWasUpdated(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [solutionWasUpdated])
|
||||
|
||||
if (exerciseIndex + 1 < exam.exercises.length) {
|
||||
setExerciseIndex(exerciseIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exerciseIndex >= exam.exercises.length) return;
|
||||
|
||||
setHasExamEnded(false);
|
||||
|
||||
if (solution) {
|
||||
onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "writing", exam: exam.id }]);
|
||||
} else {
|
||||
onFinish(userSolutions);
|
||||
}
|
||||
};
|
||||
|
||||
const previousExercise = (solution?: UserSolution) => {
|
||||
scrollToTop();
|
||||
if (solution) {
|
||||
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "writing", exam: exam.id }]);
|
||||
}
|
||||
|
||||
if (exerciseIndex > 0) {
|
||||
setExerciseIndex(exerciseIndex - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const getExercise = () => {
|
||||
const currentExercise = useMemo<Exercise>(() => {
|
||||
const exercise = exam.exercises[exerciseIndex];
|
||||
return {
|
||||
...exercise,
|
||||
userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
|
||||
};
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [exerciseIndex]);
|
||||
|
||||
const handlePartDividerClick = () => {
|
||||
setShowPartDivider(false);
|
||||
setBgColor("bg-white");
|
||||
setSeenParts((prev) => new Set(prev).add(exerciseIndex));
|
||||
}
|
||||
|
||||
const progressButtons = useMemo(() =>
|
||||
// Do not remove the ()=> in handle next
|
||||
<ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} nextDisabled={nextDisabled}/>
|
||||
, [nextExercise, previousExercise, nextDisabled]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -100,27 +93,32 @@ export default function Writing({ exam, showSolutions = false, preview = false,
|
||||
defaultTitle="Writing exam"
|
||||
section={exam.exercises[exerciseIndex]}
|
||||
sectionIndex={exerciseIndex}
|
||||
onNext={() => { setShowPartDivider(false); setBgColor("bg-white"); setSeenParts((prev) => new Set(prev).add(exerciseIndex))}}
|
||||
onNext={handlePartDividerClick}
|
||||
/> : (
|
||||
<div className="flex flex-col h-full w-full gap-8 items-center">
|
||||
{exam.exercises.length > 1 && <SectionNavbar
|
||||
module="writing"
|
||||
sectionLabel="Part"
|
||||
seenParts={seenParts}
|
||||
setShowPartDivider={setShowPartDivider}
|
||||
setSeenParts={setSeenParts}
|
||||
preview={preview}
|
||||
/>}
|
||||
<ModuleTitle
|
||||
minTimer={exam.minTimer}
|
||||
minTimer={timer.current}
|
||||
exerciseIndex={exerciseIndex + 1}
|
||||
module="writing"
|
||||
totalExercises={countExercises(exam.exercises)}
|
||||
disableTimer={showSolutions || preview}
|
||||
preview={preview}
|
||||
/>
|
||||
{exerciseIndex > -1 &&
|
||||
exerciseIndex < exam.exercises.length &&
|
||||
!showSolutions &&
|
||||
renderExercise(getExercise(), exam.id, nextExercise, previousExercise, preview)}
|
||||
{exerciseIndex > -1 &&
|
||||
exerciseIndex < exam.exercises.length &&
|
||||
showSolutions &&
|
||||
renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
|
||||
{!showPartDivider && !showSolutions && renderExercise(currentExercise, exam.id, registerSolution, preview, progressButtons, progressButtons)}
|
||||
{showSolutions && renderSolution(currentExercise, progressButtons, progressButtons)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Writing;
|
||||
|
||||
Reference in New Issue
Block a user