diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index da0e0da5..4bf794d5 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -22,6 +22,7 @@ import { Save } from "lucide-react"; import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; import PDFBlockComponent from "./Extensions/PDF/PDFBlockComponent"; import PDFBlock from "./Extensions/PDF/PDFBlock"; +import QuizBlock from "./Extensions/Quiz/QuizBlock"; interface Editor { content: string; @@ -65,6 +66,10 @@ function Editor(props: Editor) { editable: true, lecture: props.lecture, }), + QuizBlock.configure({ + editable: true, + lecture: props.lecture, + }), Youtube.configure({ controls: true, modestBranding: true, @@ -114,7 +119,7 @@ function Editor(props: Editor) { - + @@ -279,8 +284,8 @@ export const EditorContentWrapper = styled.div` padding-left: 20px; padding-right: 20px; padding-bottom: 20px; + padding-top: 20px; - padding-top: 1px; &:focus { outline: none !important; outline-style: none !important; diff --git a/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx b/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx index 6d1a9a5d..950d4740 100644 --- a/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx +++ b/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx @@ -41,7 +41,7 @@ function ImageBlockComponent(props: any) { {fileObject && ( { + let modifiedQuestions = [...questions]; + modifiedQuestions.splice(index, 1); + setQuestions(modifiedQuestions); + console.log(questions); + + // remove the answers from the answers array + let modifiedAnswers = [...answers]; + modifiedAnswers = modifiedAnswers.filter((answer: any) => answer.question_id !== questions[index].question_id); + setAnswers(modifiedAnswers); + }; + + const onQuestionChange = (e: any, index: number) => { + let modifiedQuestions = [...questions]; + modifiedQuestions[index].question_value = e.target.value; + setQuestions(modifiedQuestions); + }; + + const addOption = (question_id: string) => { + // find the question index from the question_id and add the option to that question index + let modifiedQuestions = [...questions]; + let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); + modifiedQuestions[questionIndex].options.push({ + option_id: "option_" + uuidv4(), + option_data: "", + option_type: "text", + }); + setQuestions(modifiedQuestions); + }; + + const deleteOption = (question_id: string, option_id: string) => { + // find the option index from the option_id and delete the option from that option index + let modifiedQuestions = [...questions]; + let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); + let optionIndex = modifiedQuestions[questionIndex].options.findIndex((option: any) => option.option_id === option_id); + modifiedQuestions[questionIndex].options.splice(optionIndex, 1); + setQuestions(modifiedQuestions); + + // remove the answer from the answers array + let answerIndex = answers.findIndex((answer: any) => answer.option_id === option_id); + if (answerIndex !== -1) { + let modifiedAnswers = [...answers]; + modifiedAnswers.splice(answerIndex, 1); + setAnswers(modifiedAnswers); + } + }; + + const markOptionAsCorrect = (question_id: string, option_id: string) => { + // find the option index from the option_id and mark the option as correct + let answer = { + question_id: question_id, + option_id: option_id, + }; + setAnswers([...answers, answer]); + console.log(answers); + }; + + const saveQuiz = async () => { + // save the questions and answers to the backend + console.log("saving quiz"); + console.log(questions); + console.log(answers); + try { + let res = await submitQuizBlock(props.extension.options.lecture.lecture_id, {questions : questions , answers : answers}) + console.log(res.block_id); + props.updateAttributes({ + quizId: { + value : res.block_id + }, + }); + + } + catch (error) { + console.log(error); + } + + + }; + + const onOptionChange = (e: any, questionIndex: number, optionIndex: number) => { + let modifiedQuestions = [...questions]; + modifiedQuestions[questionIndex].options[optionIndex].option_data = e.target.value; + setQuestions(modifiedQuestions); + }; + + React.useEffect(() => { + // fetch the questions and options from the backend + console.log("fetching questions"); + console.log(questions); + console.log(answers); + }, [questions, answers]); + + return ( + + + Questions +
+ {questions.map((question: any, qIndex: number) => ( + <> +
+ Question : onQuestionChange(e, qIndex)} /> + +
+ Answers :
+ {question.options.map((option: any, oIndex: number) => ( + <> +
+ onOptionChange(e, qIndex, oIndex)} /> + + + + // check if checkbox is checked or not + // if checked then add the answer to the answers array + // if unchecked then remove the answer from the answers array + e.target.checked ? markOptionAsCorrect(question.question_id, option.option_id) : null + } + /> +
+ + ))} + + + ))} +
+
+ ); +} + +const QuizBlockWrapper = styled.div` + background-color: #0000001d; + border-radius: 5px; + padding: 20px; + height: 100%; +`; +export default ImageBlockComponent; diff --git a/front/components/Editor/Toolbar/ToolbarButtons.tsx b/front/components/Editor/Toolbar/ToolbarButtons.tsx index 17af1615..4116e2a5 100644 --- a/front/components/Editor/Toolbar/ToolbarButtons.tsx +++ b/front/components/Editor/Toolbar/ToolbarButtons.tsx @@ -1,8 +1,8 @@ import styled from "styled-components"; import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons"; -import { AlertCircle, AlertTriangle, FileText, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; +import { AlertCircle, AlertTriangle, FileText, GraduationCap, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; -export const ToolbarButtons = ({ editor }: any) => { +export const ToolbarButtons = ({ editor, props }: any) => { if (!editor) { return null; } @@ -116,6 +116,19 @@ export const ToolbarButtons = ({ editor }: any) => { > + + editor + .chain() + .focus() + .insertContent({ + type: "blockQuiz", + }) + .run() + } + > + + ); }; diff --git a/front/package-lock.json b/front/package-lock.json index db8ab2bd..d23c0f84 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -26,6 +26,7 @@ "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", + "uuid": "^9.0.0", "y-indexeddb": "^9.0.9", "y-webrtc": "^10.2.3", "yjs": "^13.5.42" @@ -37,6 +38,7 @@ "@types/react-dom": "18.0.6", "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", + "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.12", "eslint": "8.23.1", "eslint-config-next": "^13.0.6", @@ -3058,6 +3060,12 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", + "dev": true + }, "node_modules/@typescript-eslint/parser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", @@ -7088,6 +7096,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", @@ -9309,6 +9325,12 @@ "csstype": "^3.0.2" } }, + "@types/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", + "dev": true + }, "@typescript-eslint/parser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", @@ -12128,6 +12150,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, "w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", diff --git a/front/package.json b/front/package.json index 325ba9c8..fd630495 100644 --- a/front/package.json +++ b/front/package.json @@ -27,6 +27,7 @@ "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", + "uuid": "^9.0.0", "y-indexeddb": "^9.0.9", "y-webrtc": "^10.2.3", "yjs": "^13.5.42" @@ -38,6 +39,7 @@ "@types/react-dom": "18.0.6", "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", + "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.12", "eslint": "8.23.1", "eslint-config-next": "^13.0.6", diff --git a/front/services/blocks/Quiz/quiz.ts b/front/services/blocks/Quiz/quiz.ts new file mode 100644 index 00000000..e881c624 --- /dev/null +++ b/front/services/blocks/Quiz/quiz.ts @@ -0,0 +1,10 @@ +import { getAPIUrl } from "@services/config"; +import { RequestBody, RequestBodyForm } from "@services/utils/requests"; + + +export async function submitQuizBlock(lecture_id: string, data: any) { + const result: any = await fetch(`${getAPIUrl()}blocks/quiz/${lecture_id}"`, RequestBody("POST", data)) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); + return result; +} \ No newline at end of file diff --git a/src/routers/blocks.py b/src/routers/blocks.py index ea9264d9..b9b68707 100644 --- a/src/routers/blocks.py +++ b/src/routers/blocks.py @@ -56,7 +56,7 @@ async def api_get_document_file_block(request: Request, file_id: str, current_us return await get_document_file(request, file_id, current_user) -@router.post("/quiz") +@router.post("/quiz/{lecture_id}") async def api_create_quiz_block(request: Request, quiz_block: quizBlock, lecture_id: str, current_user: PublicUser = Depends(get_current_user)): """ Create new document file diff --git a/src/services/blocks/quizBlock/quizBlock.py b/src/services/blocks/quizBlock/quizBlock.py index 7e9591a1..3bcb9d73 100644 --- a/src/services/blocks/quizBlock/quizBlock.py +++ b/src/services/blocks/quizBlock/quizBlock.py @@ -19,6 +19,7 @@ class answer(BaseModel): class question(BaseModel): question_id: str + question_value:str options: List[option]