Listening Convo Edit and a bunch of ts errors
This commit is contained in:
@@ -2,10 +2,10 @@ import { useEffect, useState } from "react";
|
||||
import { ListeningPart } from "@/interfaces/exam";
|
||||
import SectionContext from ".";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { FaFemale, FaMale } from "react-icons/fa";
|
||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||
import ScriptRender from "../../Exercises/Shared/Script";
|
||||
import ScriptRender from "../../Exercises/Script";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import Dropdown from "@/components/Dropdown";
|
||||
|
||||
|
||||
const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
@@ -15,50 +15,55 @@ const ListeningContext: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
);
|
||||
const listeningPart = state as ListeningPart;
|
||||
|
||||
const [script, setScript] = useState(listeningPart.script);
|
||||
const [scriptLocal, setScriptLocal] = useState(listeningPart.script);
|
||||
|
||||
const { editing, handleSave, handleDiscard, modeHandle, setEditing } = useSectionEdit({
|
||||
sectionId,
|
||||
mode: "edit",
|
||||
onSave: () => {
|
||||
const newState = { ...listeningPart };
|
||||
newState.script = script;
|
||||
newState.script = scriptLocal;
|
||||
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } })
|
||||
setEditing(false);
|
||||
},
|
||||
onDiscard: () => {
|
||||
setScript(listeningPart.script);
|
||||
setScriptLocal(listeningPart.script);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult !== undefined && generating === "context") {
|
||||
setEditing(true);
|
||||
setScript(genResult[0].script)
|
||||
setScriptLocal(genResult[0].script);
|
||||
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, dispatch, sectionId, setEditing, currentModule]);
|
||||
|
||||
const renderContent = (editing: boolean) => {
|
||||
|
||||
if (script === undefined && !editing) {
|
||||
return (<p className="w-full text-gray-600 px-7 py-8 border-2 bg-white rounded-3xl whitespace-pre-line">
|
||||
Generate or import audio to add exercises!
|
||||
</p>
|
||||
)
|
||||
if (scriptLocal === undefined) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-10">
|
||||
<span>Edit, generate or import your own audio.</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-10">
|
||||
<ScriptRender
|
||||
script={script}
|
||||
setScript={setScript}
|
||||
editing={editing}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dropdown
|
||||
className={`w-full text-left p-4 mb-2 bg-gradient-to-r from-ielts-${currentModule}/60 to-ielts-${currentModule} text-white rounded-lg shadow-lg transition-transform transform hover:scale-102`}
|
||||
title="Conversation"
|
||||
contentWrapperClassName="rounded-xl"
|
||||
>
|
||||
<ScriptRender
|
||||
local={scriptLocal}
|
||||
setLocal={setScriptLocal}
|
||||
section={sectionId}
|
||||
editing={editing}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
import SortableSection from "../../Shared/SortableSection";
|
||||
import getReadingQuestions from '../SectionExercises/reading';
|
||||
import { Exercise, LevelPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
|
||||
import { ReadingExercise } from "./types";
|
||||
import { Exercise, LevelPart, ListeningPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
|
||||
import ExerciseItem, { ReadingExercise } from "./types";
|
||||
import Dropdown from "@/components/Dropdown";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import Writing from "../../Exercises/Writing";
|
||||
import Speaking from "../../Exercises/Speaking";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { ReactElement, ReactNode, useEffect, useState } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
PointerSensor,
|
||||
@@ -15,15 +15,18 @@ import {
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
closestCenter,
|
||||
UniqueIdentifier,
|
||||
} from '@dnd-kit/core';
|
||||
import GenLoader from "../../Exercises/Shared/GenLoader";
|
||||
import { ExamPart } from "@/stores/examEditor/types";
|
||||
import getListeningItems from "./listening";
|
||||
import getLevelQuestionItems from "./level";
|
||||
import React from "react";
|
||||
|
||||
|
||||
export interface Props {
|
||||
sectionId: number;
|
||||
interface QuestionItemsResult {
|
||||
ids: string[];
|
||||
items: ExerciseItem[];
|
||||
}
|
||||
|
||||
const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
@@ -49,7 +52,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
})
|
||||
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, dispatch, sectionId, currentModule]);
|
||||
|
||||
const currentSection = sections.find((s) => s.sectionId === sectionId)!;
|
||||
@@ -58,27 +61,44 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
useSensor(PointerSensor),
|
||||
);
|
||||
|
||||
const questionItems = () => {
|
||||
let ids, items;
|
||||
const questionItems = (): QuestionItemsResult => {
|
||||
let result: QuestionItemsResult = {
|
||||
ids: [],
|
||||
items: []
|
||||
};
|
||||
|
||||
switch (currentModule) {
|
||||
case "reading":
|
||||
items = getReadingQuestions((currentSection.state as ReadingPart).exercises as ReadingExercise[], sectionId);
|
||||
ids = items.map(q => q.id.toString());
|
||||
case "reading": {
|
||||
const items = getReadingQuestions(
|
||||
(currentSection.state as ReadingPart).exercises as ReadingExercise[],
|
||||
sectionId
|
||||
);
|
||||
result.items = items.filter((item): item is ExerciseItem => item !== undefined);
|
||||
result.ids = result.items.map(item => item.id);
|
||||
break;
|
||||
case "listening":
|
||||
items = getListeningItems((currentSection.state as ReadingPart).exercises as ReadingExercise[], sectionId);
|
||||
ids = items.map(q => q?.id.toString());
|
||||
}
|
||||
case "listening": {
|
||||
const items = getListeningItems(
|
||||
(currentSection.state as ListeningPart).exercises as Exercise[],
|
||||
sectionId
|
||||
);
|
||||
result.items = items.filter((item): item is ExerciseItem => item !== undefined);
|
||||
result.ids = result.items.map(item => item.id);
|
||||
break;
|
||||
case "level":
|
||||
items = getLevelQuestionItems((currentSection.state as LevelPart).exercises as Exercise[], sectionId);
|
||||
ids = items.map(q => q.id.toString());
|
||||
}
|
||||
case "level": {
|
||||
const items = getLevelQuestionItems(
|
||||
(currentSection.state as LevelPart).exercises as Exercise[],
|
||||
sectionId
|
||||
);
|
||||
result.items = items.filter((item): item is ExerciseItem => item !== undefined);
|
||||
result.ids = result.items.map(item => item.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { ids, items }
|
||||
}
|
||||
|
||||
const questions = questionItems();
|
||||
return result;
|
||||
};
|
||||
|
||||
const background = (component: ReactNode) => {
|
||||
return (
|
||||
@@ -91,6 +111,20 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
if (currentModule == "writing") return background(<Writing sectionId={sectionId} exercise={currentSection.state as WritingExercise} />);
|
||||
if (currentModule == "speaking") return background(<Speaking sectionId={sectionId} exercise={currentSection.state as SpeakingExercise} />);
|
||||
|
||||
const questions = questionItems();
|
||||
|
||||
const filteredIds = (questions.ids ?? []).filter(Boolean);
|
||||
|
||||
function isValidItem(item: ExerciseItem | undefined): item is ExerciseItem {
|
||||
return item !== undefined &&
|
||||
typeof item.id === 'string' &&
|
||||
typeof item.sectionId === 'number' &&
|
||||
React.isValidElement(item.label) &&
|
||||
React.isValidElement(item.content);
|
||||
}
|
||||
|
||||
const filteredItems = (questions.items ?? []).filter(isValidItem);
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
@@ -107,11 +141,11 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
||||
questions.ids.length > 0 && (
|
||||
<div className="mt-4 p-6 rounded-xl shadow-inner border bg-gray-50">
|
||||
<SortableContext
|
||||
items={questions.ids}
|
||||
items={filteredIds}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{questions.items.map(item => (
|
||||
<SortableSection key={item.id.toString()} id={item.id.toString()}>
|
||||
{filteredItems.map(item => (
|
||||
<SortableSection key={item.id} id={item.id}>
|
||||
<Dropdown
|
||||
className={`w-full text-left p-4 mb-2 bg-gradient-to-r from-ielts-${currentModule}/60 to-ielts-${currentModule} text-white rounded-lg shadow-lg transition-transform transform hover:scale-102`}
|
||||
customTitle={item.label}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exercise } from "@/interfaces/exam";
|
||||
import ExerciseItem from "./types";
|
||||
import ExerciseItem, { isExerciseItem } from "./types";
|
||||
import ExerciseLabel from "../../Shared/ExerciseLabel";
|
||||
import MultipleChoice from "../../Exercises/MultipleChoice";
|
||||
import FillBlanksMC from "../../Exercises/Blanks/MultipleChoice";
|
||||
@@ -17,7 +17,7 @@ const getLevelQuestionItems = (exercises: Exercise[], sectionId: number): Exerci
|
||||
firstWordId = exercise.questions[0].id;
|
||||
lastWordId = exercise.questions[exercise.questions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -35,7 +35,7 @@ const getLevelQuestionItems = (exercises: Exercise[], sectionId: number): Exerci
|
||||
firstWordId = exercise.solutions[0].id;
|
||||
lastWordId = exercise.solutions[exercise.solutions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -52,9 +52,9 @@ const getLevelQuestionItems = (exercises: Exercise[], sectionId: number): Exerci
|
||||
default:
|
||||
return {} as unknown as ExerciseItem;
|
||||
}
|
||||
}).filter((item) => item !== undefined);
|
||||
}).filter(isExerciseItem);
|
||||
|
||||
return items || [];
|
||||
return items;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ExerciseItem from './types';
|
||||
import ExerciseItem, { isExerciseItem } from './types';
|
||||
import ExerciseLabel from '../../Shared/ExerciseLabel';
|
||||
import FillBlanksLetters from '../../Exercises/Blanks/Letters';
|
||||
import { Exercise, WriteBlanksExercise } from '@/interfaces/exam';
|
||||
@@ -7,15 +7,14 @@ import WriteBlanksForm from '../../Exercises/WriteBlanksForm';
|
||||
import WriteBlanksFill from '../../Exercises/Blanks/WriteBlankFill';
|
||||
import WriteBlanks from '../../Exercises/WriteBlanks';
|
||||
|
||||
|
||||
const writeBlanks = (exercise: WriteBlanksExercise, index: number, sectionId: number, previewLabel: (text: string) => string) => {
|
||||
const writeBlanks = (exercise: WriteBlanksExercise, index: number, sectionId: number, previewLabel: (text: string) => string): ExerciseItem => {
|
||||
const firstWordId = exercise.solutions[0].id;
|
||||
const lastWordId = exercise.solutions[exercise.solutions.length - 1].id;
|
||||
|
||||
switch (exercise.variant) {
|
||||
case 'form':
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -31,7 +30,7 @@ const writeBlanks = (exercise: WriteBlanksExercise, index: number, sectionId: nu
|
||||
};
|
||||
case 'fill':
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -47,7 +46,7 @@ const writeBlanks = (exercise: WriteBlanksExercise, index: number, sectionId: nu
|
||||
};
|
||||
case 'questions':
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -62,23 +61,23 @@ const writeBlanks = (exercise: WriteBlanksExercise, index: number, sectionId: nu
|
||||
content: <WriteBlanks exercise={exercise} sectionId={sectionId} title='Write Blanks: Questions' />
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getListeningItems = (exercises: Exercise[], sectionId: number) => {
|
||||
throw new Error(`Just so that typescript doesnt complain`);
|
||||
};
|
||||
|
||||
const getListeningItems = (exercises: Exercise[], sectionId: number): ExerciseItem[] => {
|
||||
const previewLabel = (text: string) => {
|
||||
return text !== undefined ? text.replaceAll('\\n', ' ').split(' ').slice(0, 15).join(' ') : "";
|
||||
}
|
||||
};
|
||||
|
||||
const items = exercises.map((exercise, index) => {
|
||||
const mappedItems = exercises.map((exercise, index): ExerciseItem | null => {
|
||||
let firstWordId, lastWordId;
|
||||
|
||||
switch (exercise.type) {
|
||||
case "fillBlanks":
|
||||
firstWordId = exercise.solutions[0].id;
|
||||
lastWordId = exercise.solutions[exercise.solutions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -91,15 +90,16 @@ const getListeningItems = (exercises: Exercise[], sectionId: number) => {
|
||||
/>
|
||||
),
|
||||
content: <FillBlanksLetters exercise={exercise} sectionId={sectionId} />
|
||||
|
||||
};
|
||||
|
||||
case "writeBlanks":
|
||||
return writeBlanks(exercise, index, sectionId, previewLabel);
|
||||
|
||||
case "multipleChoice":
|
||||
firstWordId = exercise.questions[0].id;
|
||||
lastWordId = exercise.questions[exercise.questions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -113,11 +113,15 @@ const getListeningItems = (exercises: Exercise[], sectionId: number) => {
|
||||
),
|
||||
content: <MultipleChoice exercise={exercise} sectionId={sectionId} />
|
||||
};
|
||||
}
|
||||
}).filter((item) => item !== undefined);
|
||||
|
||||
return items || [];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return mappedItems.filter((item): item is ExerciseItem =>
|
||||
item !== null && isExerciseItem(item)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default getListeningItems;
|
||||
export default getListeningItems;
|
||||
@@ -1,4 +1,4 @@
|
||||
import ExerciseItem, { ReadingExercise } from './types';
|
||||
import ExerciseItem, { isExerciseItem, ReadingExercise } from './types';
|
||||
import WriteBlanks from "@/editor/Exercises/WriteBlanks";
|
||||
import ExerciseLabel from '../../Shared/ExerciseLabel';
|
||||
import MatchSentences from '../../Exercises/MatchSentences';
|
||||
@@ -19,7 +19,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer
|
||||
firstWordId = exercise.solutions[0].id;
|
||||
lastWordId = exercise.solutions[exercise.solutions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -37,7 +37,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer
|
||||
firstWordId = exercise.solutions[0].id;
|
||||
lastWordId = exercise.solutions[exercise.solutions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -55,7 +55,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer
|
||||
firstWordId = exercise.sentences[0].id;
|
||||
lastWordId = exercise.sentences[exercise.sentences.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -73,7 +73,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer
|
||||
firstWordId = exercise.questions[0].id
|
||||
lastWordId = exercise.questions[exercise.questions.length - 1].id;
|
||||
return {
|
||||
id: index,
|
||||
id: index.toString(),
|
||||
sectionId,
|
||||
label: (
|
||||
<ExerciseLabel
|
||||
@@ -89,9 +89,9 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer
|
||||
};
|
||||
|
||||
}
|
||||
}).filter((item) => item !== undefined);
|
||||
}).filter(isExerciseItem);
|
||||
|
||||
return items || [];
|
||||
return items;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { FillBlanksExercise, MatchSentencesExercise, TrueFalseExercise, WriteBlanksExercise } from "@/interfaces/exam";
|
||||
|
||||
export default interface ExerciseItem {
|
||||
id: number;
|
||||
id: string;
|
||||
sectionId: number;
|
||||
label: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
export type ReadingExercise = FillBlanksExercise | TrueFalseExercise | MatchSentencesExercise | WriteBlanksExercise;
|
||||
|
||||
export function isExerciseItem(item: unknown): item is ExerciseItem {
|
||||
return item !== undefined &&
|
||||
item !== null &&
|
||||
typeof (item as ExerciseItem).id === 'string' &&
|
||||
typeof (item as ExerciseItem).sectionId === 'number' &&
|
||||
(item as ExerciseItem).label !== undefined &&
|
||||
(item as ExerciseItem).content !== undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user