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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user