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:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 >
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
Reference in New Issue
Block a user