Reverted Level to only utas placement test exercises, Speaking, bug fixes, placeholder

This commit is contained in:
Carlos-Mesquita
2024-11-10 04:24:23 +00:00
parent c507eae507
commit 322d7905c3
39 changed files with 1251 additions and 279 deletions

View File

@@ -88,6 +88,7 @@ const UnderlineMultipleChoice: React.FC<{exercise: MultipleChoiceExercise, secti
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } });
},
onDiscard: () => {
setAlerts([]);
setLocal(exercise);
setEditing(false);
},

View File

@@ -167,6 +167,8 @@ const MultipleChoice: React.FC<MultipleChoiceProps> = ({ exercise, sectionId, op
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } });
},
onDiscard: () => {
setEditing(false);
setAlerts([]);
setLocal(exercise);
},
onMode: () => {

View File

@@ -10,6 +10,8 @@ import useSectionEdit from "../../Hooks/useSectionEdit";
import useExamEditorStore from "@/stores/examEditor";
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
import { BsFileText } from "react-icons/bs";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
import { RiVideoLine } from "react-icons/ri";
interface Props {
sectionId: number;
@@ -20,6 +22,8 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const [local, setLocal] = useState(exercise);
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
@@ -86,6 +90,42 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise }) => {
const isUnedited = local.prompts.length === 0;
useEffect(() => {
if (genResult && generating === "media") {
setLocal({ ...local, prompts: genResult[0].prompts });
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { ...local, prompts: genResult[0].prompts } } });
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "generating",
value: undefined
}
});
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const handlePrevVideo = () => {
setCurrentVideoIndex((prev) => (prev > 0 ? prev - 1 : prev));
};
const handleNextVideo = () => {
setCurrentVideoIndex((prev) =>
(prev < local.prompts.length - 1 ? prev + 1 : prev)
);
};
return (
<>
<div className='relative pb-4'>
@@ -100,12 +140,65 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise }) => {
module="speaking"
/>
</div>
{generating ? (
{generating && generating === "context" ? (
<GenLoader module={currentModule} />
) : (
<>
{editing ? (
<>
{local.prompts.every((p) => p.video_url !== "") && (
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4 w-full justify-between items-center">
<div className="flex flex-row gap-4">
<RiVideoLine className="h-5 w-5 text-amber-500 mt-1" />
<h3 className="font-semibold text-xl">Videos</h3>
</div>
<div className="flex items-center gap-4">
<button
onClick={handlePrevVideo}
disabled={currentVideoIndex === 0}
className={`p-2 rounded-full ${currentVideoIndex === 0
? 'text-gray-400 cursor-not-allowed'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<FaChevronLeft className="w-4 h-4" />
</button>
<span className="text-sm text-gray-600">
{currentVideoIndex + 1} / {local.prompts.length}
</span>
<button
onClick={handleNextVideo}
disabled={currentVideoIndex === local.prompts.length - 1}
className={`p-2 rounded-full ${currentVideoIndex === local.prompts.length - 1
? 'text-gray-400 cursor-not-allowed'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<FaChevronRight className="w-4 h-4" />
</button>
</div>
</div>
<div className="flex flex-col gap-4 w-full items-center">
<div className="w-full">
<video
key={local.prompts[currentVideoIndex].video_url}
controls
className="w-full rounded-xl"
>
<source src={local.prompts[currentVideoIndex].video_url} />
</video>
</div>
</div>
</div>
</CardContent>
</Card>
)}
{generating && generating === "media" &&
<GenLoader module={currentModule} custom="Generating the videos ... This may take a while ..." />
}
<Card>
<CardContent>
<div className="flex flex-col py-2 mt-2">

View File

@@ -8,6 +8,8 @@ import useSectionEdit from "../../Hooks/useSectionEdit";
import useExamEditorStore from "@/stores/examEditor";
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
import { BsFileText } from "react-icons/bs";
import { RiVideoLine } from 'react-icons/ri';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
interface Props {
sectionId: number;
@@ -25,16 +27,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
return { ...exercise, prompts: defaultPrompts };
});
const updateAvatarName = (avatarName: string) => {
setLocal(prev => {
const updatedPrompts = [...prev.prompts];
updatedPrompts[0] = {
...updatedPrompts[0],
text: updatedPrompts[0].text.replace("{avatar}", avatarName)
};
return { ...prev, prompts: updatedPrompts };
});
};
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
@@ -87,7 +80,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const addPrompt = () => {
@@ -116,6 +109,44 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
const isUnedited = local.prompts.length === 2;
useEffect(() => {
if (genResult && generating === "media") {
console.log(genResult[0].prompts);
setLocal({ ...local, prompts: genResult[0].prompts });
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { ...local, prompts: genResult[0].prompts } } });
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "generating",
value: undefined
}
});
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const handlePrevVideo = () => {
setCurrentVideoIndex((prev) => (prev > 0 ? prev - 1 : prev));
};
const handleNextVideo = () => {
setCurrentVideoIndex((prev) =>
(prev < local.prompts.length - 1 ? prev + 1 : prev)
);
};
return (
<>
<div className='relative pb-4'>
@@ -130,7 +161,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
module="speaking"
/>
</div>
{generating ? (
{generating && generating === "context" ? (
<GenLoader module={currentModule} />
) : (
<>
@@ -224,6 +255,59 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
</p>
) : (
<div className="space-y-6">
{local.prompts.every((p) => p.video_url !== "") && (
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4 w-full justify-between items-center">
<div className="flex flex-row gap-4">
<RiVideoLine className="h-5 w-5 text-amber-500 mt-1" />
<h3 className="font-semibold text-xl">Videos</h3>
</div>
<div className="flex items-center gap-4">
<button
onClick={handlePrevVideo}
disabled={currentVideoIndex === 0}
className={`p-2 rounded-full ${currentVideoIndex === 0
? 'text-gray-400 cursor-not-allowed'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<FaChevronLeft className="w-4 h-4" />
</button>
<span className="text-sm text-gray-600">
{currentVideoIndex + 1} / {local.prompts.length}
</span>
<button
onClick={handleNextVideo}
disabled={currentVideoIndex === local.prompts.length - 1}
className={`p-2 rounded-full ${currentVideoIndex === local.prompts.length - 1
? 'text-gray-400 cursor-not-allowed'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<FaChevronRight className="w-4 h-4" />
</button>
</div>
</div>
<div className="flex flex-col gap-4 w-full items-center">
<div className="w-full">
<video
key={local.prompts[currentVideoIndex].video_url}
controls
className="w-full rounded-xl"
>
<source src={local.prompts[currentVideoIndex].video_url} />
</video>
</div>
</div>
</div>
</CardContent>
</Card>
)}
{generating && generating === "media" &&
<GenLoader module={currentModule} custom="Generating the videos ... This may take a while ..." />
}
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start">

View File

@@ -11,12 +11,14 @@ import { BsFileText } from 'react-icons/bs';
import { AiOutlineUnorderedList } from 'react-icons/ai';
import { BiQuestionMark, BiMessageRoundedDetail } from "react-icons/bi";
import GenLoader from "../Shared/GenLoader";
import { RiVideoLine } from 'react-icons/ri';
interface Props {
sectionId: number;
exercise: SpeakingExercise;
}
const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const [local, setLocal] = useState(exercise);
@@ -25,18 +27,12 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const updateTopic = (topic: string) => {
setLocal(prev => ({ ...prev, topic: topic }));
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { topic: topic } } });
};
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
setEditing(false);
console.log(local);
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local } });
},
onDiscard: () => {
@@ -69,6 +65,32 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
useEffect(() => {
if (genResult && generating === "media") {
setLocal({...local, video_url: genResult[0].video_url});
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: {...local, video_url: genResult[0].video_url} } });
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "generating",
value: undefined
}
});
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const addPrompt = () => {
setLocal(prev => ({
...prev,
@@ -91,9 +113,9 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
});
};
const isUnedited = local.text === "" ||
(local.title === undefined || local.title === "") ||
local.prompts.length === 0;
const isUnedited = local.text === "" ||
(local.title === undefined || local.title === "") ||
local.prompts.length === 0;
const tooltipContent = `
<div class='p-2 max-w-xs'>
@@ -122,7 +144,7 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
module="speaking"
/>
</div>
{generating ? (
{generating && generating === "context" ? (
<GenLoader module={currentModule} />
) : (
<>
@@ -222,6 +244,25 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
</p>
) : (
<div className="space-y-6">
{local.video_url && <Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<RiVideoLine className="h-5 w-5 text-amber-500 mt-1" />
<h3 className="font-semibold text-xl">Video</h3>
</div>
<div className="flex flex-col gap-4 w-full items-center">
<video controls className="w-full rounded-xl">
<source src={local.video_url } />
</video>
</div>
</div>
</CardContent>
</Card>
}
{generating && generating === "media" &&
<GenLoader module={currentModule} custom="Generating the video ... This may take a while ..." />
}
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">