Added Speaking to level, fixed a bug where it was causing level to crash if the listening was already created and the section was switched, added true false exercises to listening

This commit is contained in:
Carlos-Mesquita
2024-11-13 20:32:59 +00:00
parent 153d7f5448
commit 8cb09e349f
23 changed files with 1122 additions and 292 deletions

View File

@@ -51,6 +51,12 @@ const ListeningContext: React.FC<Props> = ({ sectionId, module, listeningSection
},
});
useEffect(()=> {
if (listeningPart.script == undefined) {
setScriptLocal(undefined);
}
}, [listeningPart])
useEffect(() => {
if (genResult && generating === "listeningScript") {
setEditing(true);

View File

@@ -55,6 +55,13 @@ const ReadingContext: React.FC<Props> = ({ sectionId, module, level = false }) =
}
});
useEffect(()=> {
if (readingPart.text === undefined) {
setTitle('');
setContent('');
}
}, [readingPart])
useEffect(() => {
if (genResult && genResult.generating === "passage") {
setEditing(true);

View File

@@ -7,7 +7,9 @@ import TrueFalse from "../../Exercises/TrueFalse";
import fillBlanks from "./fillBlanks";
import MatchSentences from "../../Exercises/MatchSentences";
import Writing from "../../Exercises/Writing";
import Speaking from "../../Exercises/Speaking";
import Speaking2 from "../../Exercises/Speaking/Speaking2";
import Speaking1 from "../../Exercises/Speaking/Speaking1";
import InteractiveSpeaking from "../../Exercises/Speaking/InteractiveSpeaking";
const getExerciseItems = (exercises: Exercise[], sectionId: number): ExerciseItem[] => {
const items: ExerciseItem[] = exercises.map((exercise, index) => {
@@ -79,41 +81,42 @@ const getExerciseItems = (exercises: Exercise[], sectionId: number): ExerciseIte
),
content: <Writing key={exercise.id} exercise={exercise} sectionId={sectionId} index={index} module="level" />
};
case "speaking":
case "speaking":
return {
exerciseId: exercise.id,
id: index.toString(),
sectionId,
label: (
<ExerciseLabel
type={`Speaking Section 2`}
firstId={exercise.sectionId!.toString()}
lastId={exercise.sectionId!.toString()}
type={`Speaking Section 2: Question`}
firstId={(index+1).toString()}
lastId={(index+1).toString()}
prompt={exercise.prompts[2]}
/>
),
content: <Speaking key={exercise.id} exercise={exercise} sectionId={sectionId} qId={index} module="level" />
content: <Speaking2 key={exercise.id} exercise={exercise} sectionId={sectionId} module="level" />
};
case "interactiveSpeaking":
const content = exercise.sectionId === 1 ? <Speaking1 key={exercise.id} exercise={exercise} sectionId={sectionId} module="level" /> :
<InteractiveSpeaking key={exercise.id} exercise={exercise} sectionId={sectionId} module="level"/>
return {
exerciseId: exercise.id,
id: index.toString(),
sectionId,
label: (
<ExerciseLabel
type={`Speaking Section 2`}
firstId={exercise.sectionId!.toString()}
lastId={exercise.sectionId!.toString()}
type={`${exercise.sectionId === 1 ? 'Speaking Section 1': 'Interactive Speaking'}: Question`}
firstId={(index+1).toString()}
lastId={(index+1).toString()}
prompt={exercise.prompts[2].text}
/>
),
content: <Speaking key={exercise.id} exercise={exercise} sectionId={sectionId} qId={index} module="level" />
content: content
};
default:
return {} as unknown as ExerciseItem;
}
}).filter(isExerciseItem);
/*return mappedItems.filter((item): item is ExerciseItem =>
item !== null && isExerciseItem(item)
);*/
return items;
};

View File

@@ -1,6 +1,6 @@
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import SortableSection from "../../Shared/SortableSection";
import { Exercise, LevelPart, ListeningPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
import { Exercise, InteractiveSpeakingExercise, LevelPart, ListeningPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
import ExerciseItem from "./types";
import Dropdown from "@/components/Dropdown";
import useExamEditorStore from "@/stores/examEditor";
@@ -20,6 +20,7 @@ import React from "react";
import getExerciseItems from "./exercises";
import { Action } from "@/stores/examEditor/reducers";
import { writingTask } from "@/stores/examEditor/sections";
import { createSpeakingExercise } from "./speaking";
interface QuestionItemsResult {
@@ -119,14 +120,14 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
sectionId,
module: "level",
update: {
exercises: [...(sectionState as ExamPart).exercises,
...results.map((res)=> {
return {
...writingTask(res.generating === "writing_letter" ? 1 : 2),
prompt: res.result[0].prompt,
variant: res.generating === "writing_letter" ? "letter" : "essay"
} as WritingExercise;
})
exercises: [...(sectionState as ExamPart).exercises,
...results.map((res) => {
return {
...writingTask(res.generating === "writing_letter" ? 1 : 2),
prompt: res.result[0].prompt,
variant: res.generating === "writing_letter" ? "letter" : "essay"
} as WritingExercise;
})
]
}
}
@@ -156,6 +157,46 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelGenResults, sectionState, levelGenerating, sectionId, currentModule]);
useEffect(() => {
if (levelGenResults && levelGenResults.some(res => res.generating.startsWith("speaking"))) {
const results = levelGenResults.filter(res => res.generating.startsWith("speaking"));
const updates = [
{
type: "UPDATE_SECTION_STATE",
payload: {
sectionId,
module: "level",
update: {
exercises: [...(sectionState as ExamPart).exercises,
...results.map(createSpeakingExercise)
]
}
}
},
{
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "levelGenerating",
value: levelGenerating?.filter(g => !results.flatMap(res => res.generating as Generating).includes(g))
}
},
{
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "levelGenResults",
value: levelGenResults.filter(res => !results.flatMap(res => res.generating as Generating).includes(res.generating))
}
}
] as Action[];
updates.forEach(update => dispatch(update));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelGenResults, sectionState, levelGenerating, sectionId, currentModule]);
const currentSection = sections.find((s) => s.sectionId === sectionId)!;
@@ -199,7 +240,13 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
const filteredItems = (questions.items ?? []).filter(isValidItem);
// #############################################################################
console.log(levelGenerating);
const onFocus = (questionId: string, id: string | undefined) => {
if (id) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { module: currentModule, sectionId, field: "focusedExercise", value: { questionId, id} } })
}
}
return (
<DndContext
sensors={sensors}
@@ -223,7 +270,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
customTitle={item.label}
contentWrapperClassName="rounded-xl"
>
<div className="p-4 shadow-inner border border-gray-200 bg-gray-50 rounded-xl">
<div tabIndex={4} className="p-4 shadow-inner border border-gray-200 bg-gray-50 rounded-xl" onFocus={() => onFocus(item.id, item.exerciseId)}>
{item.content}
</div>
</Dropdown>
@@ -237,9 +284,9 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
{currentModule === "level" && (
<>
{
questions.ids?.length === 0 && !levelGenerating?.some((g) => g?.startsWith("exercises") || g?.startsWith("writing")) && generating !== "exercises"
questions.ids?.length === 0 && !levelGenerating?.some((g) => g?.startsWith("exercises") || g?.startsWith("writing") || g?.startsWith("speaking")) && generating !== "exercises"
&& background(<span className="flex justify-center">Generated exercises will appear here!</span>)}
{levelGenerating?.some((g) => g?.startsWith("exercises") || g?.startsWith("writing")) && <GenLoader module={currentModule} className="mt-4" />}
{levelGenerating?.some((g) => g?.startsWith("exercises") || g?.startsWith("writing") || g?.startsWith("speaking")) && <GenLoader module={currentModule} className="mt-4" />}
</>)
}
</DndContext >

View File

@@ -0,0 +1,47 @@
import { InteractiveSpeakingExercise, SpeakingExercise } from "@/interfaces/exam";
import { speakingTask } from "@/stores/examEditor/sections";
export const createSpeakingExercise = (res: any) => {
const taskNumber = Number(res.generating.split("_")[1]);
const baseExercise = speakingTask(taskNumber);
return {
...baseExercise,
...getSpeakingTaskData(taskNumber, res.result[0])
} as SpeakingExercise | InteractiveSpeakingExercise;
};
const getSpeakingTaskData = (taskNumber: number, data: any) => {
switch (taskNumber) {
case 1:
return {
first_title: data.first_topic,
second_title: data.second_topic,
prompts: [
...data.prompts.map((item: any) => ({
text: item,
video_url: ""
}))
],
sectionId: 1,
};
case 2:
return {
title: data.topic,
text: data.question,
prompts: data.prompts,
sectionId: 2,
type: "speaking"
};
case 3:
return {
title: data.topic,
prompts: data.questions.map((item: any) => ({
text: item || "",
video_url: ""
})),
sectionId: 3,
};
default:
return data;
}
};

View File

@@ -3,6 +3,7 @@ export default interface ExerciseItem {
sectionId: number;
label: React.ReactNode;
content: React.ReactNode;
exerciseId?: string;
}
export function isExerciseItem(item: unknown): item is ExerciseItem {

View File

@@ -0,0 +1,66 @@
import { Action } from "@/stores/examEditor/reducers";
import { ExamPart, Generating } from "@/stores/examEditor/types";
import { createSpeakingExercise } from "./speaking";
import { writingTask } from "@/stores/examEditor/sections";
import { WritingExercise } from "@/interfaces/exam";
const getResults = (results: any[], type: 'writing' | 'speaking') => {
return results.map((res) => {
if (type === 'writing') {
return {
...writingTask(res.generating === "writing_letter" ? 1 : 2),
prompt: res.result[0].prompt,
variant: res.generating === "writing_letter" ? "letter" : "essay"
} as WritingExercise;
}
return createSpeakingExercise(res);
});
};
const updates = (
results: any[],
sectionState: ExamPart,
sectionId: number,
currentModule: string,
levelGenerating: any[],
levelGenResults: any[],
type: 'writing' | 'speaking'
): Action[] => {
return [
{
type: "UPDATE_SECTION_STATE",
payload: {
sectionId,
module: "level",
update: {
exercises: [
...(sectionState as ExamPart).exercises,
...getResults(results, type)
]
}
}
},
{
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "levelGenerating",
value: levelGenerating?.filter(g =>
!results.flatMap(res => res.generating as Generating).includes(g)
)
}
},
{
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "levelGenResults",
value: levelGenResults.filter(res =>
!results.flatMap(res => res.generating as Generating).includes(res.generating)
)
}
}
] as Action[];
};