ENCOA-228 Now when user navigates between modules the generation items persist. Reading, listening and writing added to level module

This commit is contained in:
Carlos-Mesquita
2024-11-12 14:17:54 +00:00
parent 696c968ebc
commit fdf411d133
66 changed files with 2546 additions and 1635 deletions

View File

@@ -1,40 +1,41 @@
import { useCallback, useEffect, useState } from "react";
import useExamEditorStore from "@/stores/examEditor";
import ExamEditorStore from "@/stores/examEditor/types";
import ExamEditorStore, { Generating } from "@/stores/examEditor/types";
import Header from "../../Shared/Header";
import { Module } from "@/interfaces";
import GenLoader from "../../Exercises/Shared/GenLoader";
interface Props {
sectionId: number;
title: string;
description: string;
editing: boolean;
renderContent: (editing: boolean) => React.ReactNode;
renderContent: (editing: boolean, listeningSection?: number) => React.ReactNode;
mode?: "edit" | "delete";
onSave: () => void;
onDiscard: () => void;
onEdit?: () => void;
module?: Module;
module: Module;
listeningSection?: number;
context: Generating;
}
const SectionContext: React.FC<Props> = ({ sectionId, title, description, renderContent, editing, onSave, onDiscard, onEdit, mode = "edit", module}) => {
const { currentModule, dispatch } = useExamEditorStore();
const { generating } = useExamEditorStore(
const SectionContext: React.FC<Props> = ({ sectionId, title, description, renderContent, editing, onSave, onDiscard, onEdit, mode = "edit", module, context, listeningSection }) => {
const { currentModule } = useExamEditorStore();
const { generating, levelGenerating } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const [loading, setLoading] = useState(generating && generating == "context");
const updateRoot = useCallback((updates: Partial<ExamEditorStore>) => {
dispatch({ type: 'UPDATE_ROOT', payload: { updates } });
}, [dispatch]);
const [loading, setLoading] = useState(generating && generating === context);
useEffect(() => {
const loading = generating && generating == "context";
setLoading(loading);
const gen = module === "level" ? levelGenerating.find(g => g === context) !== undefined : generating && generating === context;
if (loading !== gen) {
setLoading(gen);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [generating, updateRoot]);
}, [generating, levelGenerating]);
return (
<div className="p-8 shadow-inner border border-gray-200 bg-gray-50 rounded-xl">
@@ -45,21 +46,15 @@ const SectionContext: React.FC<Props> = ({ sectionId, title, description, render
editing={editing}
handleSave={onSave}
handleDiscard={onDiscard}
modeHandle={onEdit}
mode={mode}
handleEdit={onEdit}
module={module}
/>
</div>
<div className="mt-4">
{loading ? (
<div className="w-full cursor-text px-7 py-8 border-2 border-mti-gray-platinum bg-white rounded-3xl">
<div className="flex flex-col items-center justify-center animate-pulse">
<span className={`loading loading-infinity w-32 bg-ielts-${currentModule}`} />
<span className={`font-bold text-2xl text-ielts-${currentModule}`}>Generating...</span>
</div>
</div>
<GenLoader module={module} />
) : (
renderContent(editing)
renderContent(editing, listeningSection)
)}
</div>
</div>

View File

@@ -0,0 +1,33 @@
import useExamEditorStore from "@/stores/examEditor";
import ListeningContext from "./listening";
import ReadingContext from "./reading";
import GenLoader from "../../Exercises/Shared/GenLoader";
interface Props {
sectionId: number;
}
const LevelContext: React.FC<Props> = ({ sectionId }) => {
const { currentModule } = useExamEditorStore();
const { generating, readingSection, listeningSection } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
return (
<>
{generating && (
(generating === "passage" && <GenLoader module="reading" />) ||
(generating === "listeningScript" && <GenLoader module="listening" />)
)}
{(readingSection || listeningSection) && (
<div className="space-y-4 mb-4">
{readingSection && <ReadingContext sectionId={sectionId} module="level" />}
{listeningSection && <ListeningContext sectionId={sectionId} listeningSection={listeningSection} module="level" level={true}/>}
</div>
)}
</>
);
};
export default LevelContext;

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { ListeningPart } from "@/interfaces/exam";
import { LevelPart, ListeningPart } from "@/interfaces/exam";
import SectionContext from ".";
import useExamEditorStore from "@/stores/examEditor";
import useSectionEdit from "../../Hooks/useSectionEdit";
@@ -9,25 +9,42 @@ import Dropdown from "@/components/Dropdown";
import AudioPlayer from "@/components/Low/AudioPlayer";
import { MdHeadphones } from "react-icons/md";
import clsx from "clsx";
import { Module } from "@/interfaces";
import GenLoader from "../../Exercises/Shared/GenLoader";
interface Props {
module: Module;
sectionId: number;
listeningSection?: number;
level?: boolean;
}
const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
const { currentModule, dispatch } = useExamEditorStore();
const { genResult, state, generating } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
const ListeningContext: React.FC<Props> = ({ sectionId, module, listeningSection, level = false }) => {
const { dispatch } = useExamEditorStore();
const { genResult, state, generating, levelGenResults, levelGenerating } = useExamEditorStore(
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
);
const listeningPart = state as ListeningPart;
const listeningPart = state as ListeningPart | LevelPart;
const [isDialogDropdownOpen, setIsDialogDropdownOpen] = useState(false);
const [scriptLocal, setScriptLocal] = useState(listeningPart.script);
const { editing, handleSave, handleDiscard, modeHandle, setEditing } = useSectionEdit({
const { editing, handleSave, handleDiscard, setEditing, handleEdit } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
const newState = { ...listeningPart };
newState.script = scriptLocal;
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } })
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module } })
setEditing(false);
if (genResult) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "genResult", value: undefined } })
}
if (levelGenResults.find((res) => res.generating === "listeningScript")) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "levelGenResults", value: levelGenResults.filter((res) => res.generating !== "listeningScript") } })
}
},
onDiscard: () => {
setScriptLocal(listeningPart.script);
@@ -35,15 +52,42 @@ const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
});
useEffect(() => {
if (genResult !== undefined && generating === "context") {
if (genResult && generating === "listeningScript") {
setEditing(true);
setScriptLocal(genResult[0].script);
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined } })
setScriptLocal(genResult.result[0].script);
setIsDialogDropdownOpen(true);
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "generating", value: undefined } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, dispatch, sectionId, setEditing, currentModule]);
}, [genResult]);
const renderContent = (editing: boolean) => {
useEffect(() => {
if (genResult && generating === "audio") {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "generating", value: undefined } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult]);
useEffect(() => {
const scriptRes = levelGenResults.find((res) => res.generating === "listeningScript");
if (levelGenResults && scriptRes) {
setEditing(true);
setScriptLocal(scriptRes.result[0].script);
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "levelGenerating", value: levelGenerating.filter(g => g !== "listeningScript") } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelGenResults]);
useEffect(() => {
const scriptRes = levelGenResults.find((res) => res.generating === "audio");
if (levelGenResults && scriptRes) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "levelGenerating", value: levelGenerating.filter(g => g !== "audio") } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelGenResults]);
const renderContent = (editing: boolean, listeningSection?: number) => {
if (scriptLocal === undefined && !editing) {
return (
<Card>
@@ -53,16 +97,18 @@ const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
</Card>
);
}
return (
<>
{listeningPart.audio?.source && (
<AudioPlayer
key={sectionId}
src={listeningPart.audio?.source ?? ''}
color="listening"
/>
{generating === "audio" ? (<GenLoader module="listening" custom="Generating audio ..." />) : (
<>
{listeningPart.audio?.source && (
<AudioPlayer
key={sectionId}
src={listeningPart.audio?.source ?? ''}
color="listening"
/>
)}
</>
)}
<Dropdown
className="mt-8 w-full flex items-center justify-between p-4 bg-white hover:bg-gray-50 transition-colors border rounded-xl border-gray-200"
@@ -71,16 +117,22 @@ const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
<div className="flex items-center space-x-3">
<MdHeadphones className={clsx(
"h-5 w-5",
`text-ielts-${currentModule}`
`text-ielts-${module}`
)} />
<span className="font-medium text-gray-900">{(sectionId === 1 || sectionId === 3) ? "Conversation" : "Monologue"}</span>
<span className="font-medium text-gray-900">{
listeningSection === undefined ?
([1, 3].includes(sectionId) ? "Conversation" : "Monologue") :
([1, 3].includes(listeningSection) ? "Conversation" : "Monologue")}
</span>
</div>
}
open={isDialogDropdownOpen}
setIsOpen={setIsDialogDropdownOpen}
>
<ScriptRender
local={scriptLocal}
setLocal={setScriptLocal}
section={sectionId}
section={level ? listeningSection! : sectionId}
editing={editing}
/>
</Dropdown>
@@ -91,14 +143,20 @@ const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
return (
<SectionContext
sectionId={sectionId}
title={(sectionId === 1 || sectionId === 3) ? "Conversation" : "Monologue"}
title={
listeningSection === undefined ?
([1, 3].includes(sectionId) ? "Conversation" : "Monologue") :
([1, 3].includes(listeningSection) ? "Conversation" : "Monologue")
}
description={`Enter the section's ${(sectionId === 1 || sectionId === 3) ? "conversation" : "monologue"} or import your own`}
renderContent={renderContent}
editing={editing}
onSave={handleSave}
onEdit={modeHandle}
onEdit={handleEdit}
onDiscard={handleDiscard}
module={currentModule}
module={module}
context="listeningScript"
listeningSection={listeningSection}
/>
);
};

View File

@@ -1,53 +1,81 @@
import { useEffect, useState } from "react";
import { ReadingPart } from "@/interfaces/exam";
import { LevelPart, ReadingPart } from "@/interfaces/exam";
import Input from "@/components/Low/Input";
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
import Passage from "../../Shared/Passage";
import SectionContext from ".";
import useExamEditorStore from "@/stores/examEditor";
import useSectionEdit from "../../Hooks/useSectionEdit";
import { Module } from "@/interfaces";
interface Props {
module: Module;
sectionId: number;
level?: boolean;
}
const ReadingContext: React.FC<{sectionId: number;}> = ({sectionId}) => {
const {currentModule, dispatch } = useExamEditorStore();
const { genResult, state, generating } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
const ReadingContext: React.FC<Props> = ({ sectionId, module, level = false }) => {
const { dispatch } = useExamEditorStore();
const sectionState = useExamEditorStore(
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
);
const readingPart = state as ReadingPart;
const [title, setTitle] = useState(readingPart.text.title);
const [content, setContent] = useState(readingPart.text.content);
const { genResult, state, levelGenResults, levelGenerating } = sectionState;
const readingPart = state as ReadingPart | LevelPart;
const [title, setTitle] = useState(readingPart.text?.title || '');
const [content, setContent] = useState(readingPart.text?.content || '');
const [passageOpen, setPassageOpen] = useState(false);
const { editing, handleSave, handleDiscard, modeHandle, setEditing } = useSectionEdit({
const { editing, handleSave, handleDiscard, handleEdit, setEditing } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
let newState = {...state} as ReadingPart;
newState.text.title = title;
newState.text.content = content;
dispatch({type: 'UPDATE_SECTION_STATE', payload: {sectionId, update: newState}})
let newState = { ...state } as ReadingPart | LevelPart;
newState.text = {
title, content
}
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module } })
setEditing(false);
if (genResult) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "genResult", value: undefined } })
}
if (levelGenResults.find((res) => res.generating === "passage")) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "levelGenResults", value: levelGenResults.filter((res) => res.generating !== "passage") } })
}
},
onDiscard: () => {
setTitle(readingPart.text.title);
setContent(readingPart.text.content);
setTitle(readingPart.text?.title || '');
setContent(readingPart.text?.content || '');
},
onMode: () => {
onEdit: () => {
setPassageOpen(false);
}
});
useEffect(()=> {
if (genResult !== undefined && generating === "context") {
useEffect(() => {
if (genResult && genResult.generating === "passage") {
setEditing(true);
console.log(genResult);
setTitle(genResult[0].title);
setContent(genResult[0].text);
dispatch({type: "UPDATE_SECTION_SINGLE_FIELD", payload: {sectionId, module: currentModule, field: "genResult", value: undefined}})
setTitle(genResult.result[0].title);
setContent(genResult.result[0].text);
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "generating", value: undefined } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, dispatch, sectionId, setEditing, currentModule]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult]);
useEffect(() => {
const passageRes = levelGenResults.find((res) => res.generating === "passage");
if (levelGenResults && passageRes) {
setEditing(true);
setTitle(passageRes.result[0].title);
setContent(passageRes.result[0].text);
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "levelGenerating", value: levelGenerating.filter(g => g !== "passage") } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelGenResults]);
const renderContent = (editing: boolean) => {
@@ -98,9 +126,10 @@ const ReadingContext: React.FC<{sectionId: number;}> = ({sectionId}) => {
renderContent={renderContent}
editing={editing}
onSave={handleSave}
onEdit={modeHandle}
module={currentModule}
onEdit={handleEdit}
module={module}
onDiscard={handleDiscard}
context="passage"
/>
);
};