-
+
-
- {((formik?.values as any)?.Questions || [])?.map(
- (item: Choice, parent_index: number) => {
- return (
-
-
-
-
-
-
-
- {formik?.values?.Questions?.[parent_index]?.answers?.length <
- 5 && (
-
- handleAddChoice(parent_index)}
- size={23}
- />{" "}
- {t("header.add_new_choice")}
-
- )}
-
-
- {ShowHint &&
-
-
- }
-
-
-
- );
- },
- )}
-
+
handleAddQuestion()} size={23} />{" "}
{t("header.add_new_question")}
diff --git a/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx
index 4b239f8..34760bb 100644
--- a/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx
+++ b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { Choice } from "../../../../../../types/Item";
-import { useFormikContext } from "formik";
+import { Field, useFormikContext } from "formik";
import { useTranslation } from "react-i18next";
import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber";
import TextField from "./TextField";
@@ -8,8 +8,11 @@ import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import { GoTrash } from "react-icons/go";
import { Popconfirm } from "antd";
+import LaTeXInput from "../../../../../../Components/LatextInput/LaTeXInput";
+import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo";
+import ImageBoxFieldMemo from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo";
-const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
+const QuestionFIeld = ({ index , setFieldValue,values }: { index: number , setFieldValue:any,values:any }) => {
const formik = useFormikContext();
const { setDeletedQuestions, DeletedQuestions } = useObjectToEdit();
@@ -31,11 +34,11 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
- const values = formik.values.Questions?.[index] ;
+ const value = formik.values.Questions?.[index] ;
const handelCanDeleteAnswers = ()=>{
- const content = values?.content ;
- const content_image = values?.content_image ;
+ const content = value?.content ;
+ const content_image = value?.content_image ;
if(!content && !content_image ){
return true
}
@@ -48,21 +51,18 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
<>
-
-
-
-
+
+
{handelCanDeleteAnswers() ?
{handleDeleteQuestion()}}>
diff --git a/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionList.tsx b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionList.tsx
new file mode 100644
index 0000000..89d547a
--- /dev/null
+++ b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionList.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+const QuestionList = () => {
+ return (
+
QuestionList
+ )
+}
+
+export default QuestionList
\ No newline at end of file
diff --git a/src/Pages/Admin/question/Model/Malty/components/MainInputs.tsx b/src/Pages/Admin/question/Model/Malty/components/MainInputs.tsx
new file mode 100644
index 0000000..518a947
--- /dev/null
+++ b/src/Pages/Admin/question/Model/Malty/components/MainInputs.tsx
@@ -0,0 +1,27 @@
+import { Field } from 'formik'
+import React from 'react'
+import LaTeXInputMemo from '../../../../../../Components/LatextInput/LaTeXInputMemo'
+import ImageBoxFieldMemo from '../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo'
+import { useTranslation } from 'react-i18next'
+
+const MainInputs = () => {
+ const [t] = useTranslation()
+ return (
+
+ )
+}
+
+export default MainInputs
\ No newline at end of file
diff --git a/src/Pages/Admin/question/Model/Malty/components/Question.tsx b/src/Pages/Admin/question/Model/Malty/components/Question.tsx
new file mode 100644
index 0000000..40b9ab3
--- /dev/null
+++ b/src/Pages/Admin/question/Model/Malty/components/Question.tsx
@@ -0,0 +1,67 @@
+import React, { useCallback } from "react";
+import { Choice } from "../../../../../../types/Item";
+import QuestionFIeld from "../QuestionFIeld/QuestionFIeld";
+import Choices from "../ChoiceField/Choices";
+import { FaCirclePlus } from "react-icons/fa6";
+import ValidationField from "../../../../../../Components/ValidationField/ValidationField";
+import MaltySelectTag from "../Tags/MaltySelectTag";
+import { toast } from "react-toastify";
+
+export const Question: React.FC
= React.memo(({ index, data }) => {
+ const { values, setFieldValue, ShowHint, t } = data;
+
+ const handleAddChoice = useCallback((
+ parent_index: number,
+ fromKeyCombination: boolean = false,
+ ) => {
+ setFieldValue(`Questions.[${parent_index}].answers`, [
+ ...(values?.Questions?.[parent_index]?.answers as Choice[]),
+ {
+ answer: null,
+ content_image: null,
+ content: null,
+ isCorrect: 0,
+ },
+ ]);
+
+ if (fromKeyCombination) {
+ toast.success(t("header.new_choice_have_been_added"));
+ }
+ }, [setFieldValue, values, t]);
+
+ return (
+
+
+
+
+
+
+
+ {values?.Questions?.[index]?.answers?.length < 5 && (
+
+ handleAddChoice(index)}
+ size={23}
+ />{" "}
+ {t("header.add_new_choice")}
+
+ )}
+
+
+ {ShowHint && (
+
+ )}
+
+
+
+ );
+});
diff --git a/src/Pages/Admin/question/Model/Malty/components/Questions.tsx b/src/Pages/Admin/question/Model/Malty/components/Questions.tsx
new file mode 100644
index 0000000..8feac27
--- /dev/null
+++ b/src/Pages/Admin/question/Model/Malty/components/Questions.tsx
@@ -0,0 +1,70 @@
+import React, { useCallback, useMemo, useRef, useEffect } from 'react';
+import { VariableSizeList as List } from 'react-window';
+import { useTranslation } from 'react-i18next';
+import { useObjectToEdit } from '../../../../../../zustand/ObjectToEditState';
+import { Question } from './Question';
+
+interface QuestionsProps {
+ setFieldValue: (field: string, value: any) => void;
+ values: {
+ Questions: any[];
+ };
+}
+
+const Questions: React.FC = React.memo(({ setFieldValue, values }) => {
+ const questions = values?.Questions || [];
+ const { ShowHint } = useObjectToEdit();
+ const [t] = useTranslation();
+ const listRef = useRef(null);
+
+ const getItemSize = useCallback((index: number) => {
+ const question = questions[index];
+ let height = 300; // Base height for QuestionField
+
+ height += (question.answers?.length || 0) * 212; // Height for each answer
+ if (question.answers?.length < 5) height += 40; // "Add new choice" button
+ if (ShowHint) height += 80; // Hint field
+ height += 50; // MaltySelectTag
+ console.log(height);
+
+ return height;
+ }, [questions, ShowHint]);
+
+ const itemData = useMemo(() => ({
+ values,
+ setFieldValue,
+ ShowHint,
+ t
+ }), [values, setFieldValue, ShowHint, t]);
+
+ useEffect(() => {
+ if (listRef.current) {
+ listRef.current.resetAfterIndex(0);
+ }
+ }, [questions, ShowHint]);
+
+ return (
+ <>
+ {/*
+
+ {Question}
+
*/}
+ {questions?.map((item:any,index:number)=>{
+ return (
+
+ )
+ })}
+ >
+ );
+}, (prevProps, nextProps) => prevProps?.values?.Questions === nextProps?.values?.Questions);
+
+export default Questions;
\ No newline at end of file
diff --git a/src/Pages/Admin/question/Model/ModelForm.tsx b/src/Pages/Admin/question/Model/ModelForm.tsx
index 25d0668..6265ae2 100644
--- a/src/Pages/Admin/question/Model/ModelForm.tsx
+++ b/src/Pages/Admin/question/Model/ModelForm.tsx
@@ -1,17 +1,18 @@
import { Row } from "reactstrap";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
-import { useFormikContext } from "formik";
+import { Field, useFormikContext } from "formik";
import { FaCirclePlus } from "react-icons/fa6";
-import { Choice } from "../../../../types/Item";
import { useTranslation } from "react-i18next";
import { useObjectToEdit } from "../../../../zustand/ObjectToEditState";
import { useEffect } from "react";
import Choices from "./Field/Choices";
-import ImageBoxField from "../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import SelectTag from "../../../../Components/CustomFields/SelectTag";
import useKeyCombination from "../../../../Hooks/useKeyCombination";
import { CombinationKeyEnum } from "../../../../enums/CombinationKeyEnum";
import { toast } from "react-toastify";
+import LaTeXInput from "../../../../Components/LatextInput/LaTeXInput";
+import LaTeXInputMemo from "../../../../Components/LatextInput/LaTeXInputMemo";
+import ImageBoxFieldMemo from "../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo";
const Form = () => {
const [t] = useTranslation();
@@ -47,17 +48,20 @@ const Form = () => {
}
}, [Success]);
-
+
return (
-
-
+
+
+
diff --git a/src/Pages/Admin/question/formUtil.ts b/src/Pages/Admin/question/formUtil.ts
index 59f488e..645dece 100644
--- a/src/Pages/Admin/question/formUtil.ts
+++ b/src/Pages/Admin/question/formUtil.ts
@@ -46,10 +46,9 @@ export const getValidationSchema = () => {
}).test(
"content-or-image-required",
"At least one of content or content_image must be provided",
- (obj:any) => {
+ (obj:any) => {
const isValid = !!obj.content || !!obj.content_image;
- console.log(isValid,"isValid");
-
+
return isValid;
}
);
@@ -80,10 +79,10 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
});
const questions = newQuestions ?? [{answers:[]}];
-
+
return {
id: objectToEdit?.id ?? null,
- content: objectToEdit?.content ?? "",
+ content: objectToEdit?.content ?? null,
content_image: objectToEdit?.content_image ?? "",
subject_id: objectToEdit?.subject_id ?? "",
isBase: 1,
diff --git a/src/Pages/Admin/subject/Table/Page.tsx b/src/Pages/Admin/subject/Table/Page.tsx
index 5f02289..0c57bcd 100644
--- a/src/Pages/Admin/subject/Table/Page.tsx
+++ b/src/Pages/Admin/subject/Table/Page.tsx
@@ -25,7 +25,6 @@ const TableWithHeader = () => {
const deleteMutation = useDeleteSubject();
const { grade_id } = useParams();
- console.log(grade_id);
const { data: grade } = useGetAllGrade({
show: grade_id,
diff --git a/src/Pages/Admin/subject/Table/useTableColumns.tsx b/src/Pages/Admin/subject/Table/useTableColumns.tsx
index b73808a..8b160ed 100644
--- a/src/Pages/Admin/subject/Table/useTableColumns.tsx
+++ b/src/Pages/Admin/subject/Table/useTableColumns.tsx
@@ -49,6 +49,7 @@ export const useColumns = () => {
dataIndex: "name",
key: "name",
align: "center",
+ ellipsis:true
},
{
title: t("columns.icon"),
diff --git a/src/Pages/Home/Dummy.tsx b/src/Pages/Home/Dummy.tsx
index d9ddf65..f6a55e1 100644
--- a/src/Pages/Home/Dummy.tsx
+++ b/src/Pages/Home/Dummy.tsx
@@ -1,23 +1,30 @@
+import { Formik,Form, Field } from "formik";
import React from "react";
import { useTranslation } from "react-i18next";
-import useFilter from "../../Components/FilterField/components/useFilter";
-import { Select } from "antd";
-
+import LaTeXInputMemo from "./LaTeXInputMemo";
const Dummy = () => {
const [t] = useTranslation();
- // const { FilterButton, FilterBody } = useFilter();
return (
- {/*
-
- karim
-
-
- */}
+
{}}>
+
+
+
);
};
diff --git a/src/Pages/Home/LaTeXInputMemo.tsx b/src/Pages/Home/LaTeXInputMemo.tsx
new file mode 100644
index 0000000..d60d6fd
--- /dev/null
+++ b/src/Pages/Home/LaTeXInputMemo.tsx
@@ -0,0 +1,63 @@
+import TextArea from 'antd/es/input/TextArea';
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...props }) => {
+ const { name ,value} = field;
+
+ const { setFieldValue } = form;
+ const [curCentValue, setCurrentValue] = useState(value)
+ const handleChangeInput = (e: React.ChangeEvent) => {
+ setFieldValue(name, e.target.value);
+ setCurrentValue(e.target.value)
+ };
+ console.log(name,"name");
+
+ const [t] = useTranslation();
+
+
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}, (
+ prevProps: any,
+ nextProps: any
+): boolean => {
+ const prevError = prevProps.form.errors[prevProps.field.name];
+ const nextError = nextProps.form.errors[nextProps.field.name];
+
+ const prevTouched = prevProps.form.touched[prevProps.field.name];
+ const nextTouched = nextProps.form.touched[nextProps.field.name];
+
+ const prevValue = prevProps.field.value;
+ const nextValue = nextProps.field.value;
+
+ return (
+ prevValue === nextValue
+ &&
+ prevError === nextError &&
+ prevTouched === nextTouched
+ )
+});
+
+export default LaTeXInputMemo;
\ No newline at end of file
diff --git a/src/Styles/App/App.scss b/src/Styles/App/App.scss
index 139918e..6c53271 100644
--- a/src/Styles/App/App.scss
+++ b/src/Styles/App/App.scss
@@ -109,4 +109,9 @@ svg{
&:hover{
scale: 1.1;
}
+}
+
+
+.LaTeXRenderer{
+ direction: ltr;
}
\ No newline at end of file
diff --git a/src/Styles/App/index.scss b/src/Styles/App/index.scss
index f17f397..b28a1a4 100644
--- a/src/Styles/App/index.scss
+++ b/src/Styles/App/index.scss
@@ -17,3 +17,6 @@
@import "../DataTable/index.scss";
@import "../Pages/index.scss";
+
+
+@import '../components/index.scss';
\ No newline at end of file
diff --git a/src/Styles/Pages/exercise.scss b/src/Styles/Pages/exercise.scss
index c4158e8..0067e88 100644
--- a/src/Styles/Pages/exercise.scss
+++ b/src/Styles/Pages/exercise.scss
@@ -8,6 +8,9 @@
margin-bottom: 30px;
// background: #000 !important;
}
+.exercise_form{
+ margin-bottom: 20px;
+}
.exercise_form,
.ChoiceFields {
.upload_image_button {
@@ -42,7 +45,7 @@
.exercise_add_main {
background: var(--bg);
padding: 10px 2vw;
- max-height: 84vh;
+ // max-height: 84vh;
overflow-y: scroll;
@include Scrollbar();
}
@@ -176,7 +179,7 @@
.answer_status {
display: flex;
flex-direction: column;
- justify-content: space-between;
+
padding-block: 30px;
align-items: flex-end;
}
@@ -350,4 +353,10 @@
transform: translateY(-40px);
opacity: 0;
}
+}
+
+
+.ListQuestions{
+ @include Scrollbar();
+
}
\ No newline at end of file
diff --git a/src/Styles/components/LaTeXInput.scss b/src/Styles/components/LaTeXInput.scss
new file mode 100644
index 0000000..11a0915
--- /dev/null
+++ b/src/Styles/components/LaTeXInput.scss
@@ -0,0 +1,74 @@
+.DummyHomePage{
+ padding: 30px;
+}
+
+.LaTeXInputArea{
+
+ width: 50vw;
+ position: relative;
+ margin-bottom: 10px;
+
+
+}
+.showPreviewInput{
+ background-color: var(--bg);
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ height: calc(100% - 44px);
+ word-wrap: break-word;
+ display: flex;
+ gap: 20px;
+ align-items: baseline;
+ align-content: flex-start;
+ flex-wrap: wrap;
+ border-radius: 8px;
+ border: 1px solid #d9d9d9;
+ padding: 5px 10px;
+ row-gap: 0px;
+
+
+}
+.addMML{
+ all: unset;
+ font-size: 12px;
+ font-weight: bold;
+ background: var(--primary);
+ color: var(--white);
+ padding: 2px 14px;
+ display: flex;
+ gap: 5px;
+ align-items: center;
+}
+
+.latexModal{
+ padding: 30px;
+ display: flex;
+ flex-direction: column;
+ .buttons{
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ padding: 20px;
+ gap: 20px;
+ margin-top: 20px;
+ }
+}
+.LatexPreview{
+ height: 40px;
+ display: flex;
+ align-items: center;
+}
+.LaTeXInputOptions{
+ display: flex;
+ padding: 10px;
+}
+.text{
+ margin-bottom: 7px !important;
+ font-weight: bold;
+ font-size: 19px;
+ > span {
+ color: transparent;
+ }
+}
\ No newline at end of file
diff --git a/src/Styles/components/index.scss b/src/Styles/components/index.scss
new file mode 100644
index 0000000..aeeb2cd
--- /dev/null
+++ b/src/Styles/components/index.scss
@@ -0,0 +1 @@
+@import './LaTeXInput.scss' ;
\ No newline at end of file
diff --git a/src/enums/LocalStorageEnum.ts b/src/enums/LocalStorageEnum.ts
index dc0a8fc..141f712 100644
--- a/src/enums/LocalStorageEnum.ts
+++ b/src/enums/LocalStorageEnum.ts
@@ -3,4 +3,6 @@ export enum LocalStorageEnum {
LANGUAGE_KEY = LocalStorageEnum.PROJECT_NAME + "_LANGUAGE",
TOKEN_KEY = LocalStorageEnum.PROJECT_NAME + "_TOKEN_KEY",
USER_KEY = LocalStorageEnum.PROJECT_NAME + "_USER_KEY",
+ HINT_INPUT = LocalStorageEnum.PROJECT_NAME + "HINT_INPUT",
+ LATEX_OPTION_INPUT = LocalStorageEnum.PROJECT_NAME + "LATEX_OPTION_INPUT",
}
diff --git a/src/translate/ar.json b/src/translate/ar.json
index 17879a2..e2f6fdd 100644
--- a/src/translate/ar.json
+++ b/src/translate/ar.json
@@ -51,7 +51,8 @@
"it_should_have_more_than_one_correct_answers": "يجب أن يحتوي على إجابة صحيحة",
"File_size_exceeds_2_MB_limit.":"حجم الملف يتجاوز الحد الأقصى البالغ 2 ميجابايت",
"one_of_image_and_content_should_be_enter_in_answer":"يجب إدخال صورة أو محتوى واحد على الأقل في الاجابة",
- "one_of_image_and_content_should_be_enter_in_question":"يجب إدخال صورة أو محتوى واحد على الأقل في السؤال"
+ "one_of_image_and_content_should_be_enter_in_question":"يجب إدخال صورة أو محتوى واحد على الأقل في السؤال",
+ "that_is_not_a_valid_mml":"هذا ليس mml صالح"
},
"header": {
"register_students": "تسجيل الطلاب",
@@ -143,7 +144,11 @@
"sales":"المبيعات",
"hide_hint":"اخفاء الشرح",
"show_hint":"عرض الشرح",
- "setting":"الإعدادات"
+ "setting":"الإعدادات",
+ "past_your_MMl_text":"ضع نص MMl الخاص بك",
+ "add_MML":"إضافة MML",
+ "show_preview":"عرض المعاينة",
+ "show_MMl":" MML عرض"
},
"columns": {
"id": "الرقم التعريفي",
diff --git a/src/utils/areFieldPropsEqual.ts b/src/utils/areFieldPropsEqual.ts
new file mode 100644
index 0000000..2675e74
--- /dev/null
+++ b/src/utils/areFieldPropsEqual.ts
@@ -0,0 +1,23 @@
+// utilityFunctions.ts
+import { FieldProps } from 'formik';
+
+export const areFieldPropsEqual = (
+ prevProps: any,
+ nextProps: any
+): boolean => {
+ const prevError = prevProps.form.errors[prevProps.field.name];
+ const nextError = nextProps.form.errors[nextProps.field.name];
+
+ const prevTouched = prevProps.form.touched[prevProps.field.name];
+ const nextTouched = nextProps.form.touched[nextProps.field.name];
+
+ const prevValue = prevProps.field.value;
+ const nextValue = nextProps.field.value;
+
+ return (
+ prevValue === nextValue
+ &&
+ prevError === nextError &&
+ prevTouched === nextTouched
+ );
+};
diff --git a/src/utils/convertMathMLToLaTeX.ts b/src/utils/convertMathMLToLaTeX.ts
new file mode 100644
index 0000000..8affa84
--- /dev/null
+++ b/src/utils/convertMathMLToLaTeX.ts
@@ -0,0 +1,5 @@
+import { MathMLToLaTeX } from 'mathml-to-latex';
+
+export function convertMathMLToLaTeX(mathml: string): string {
+ return MathMLToLaTeX.convert(mathml);
+}
\ No newline at end of file
diff --git a/src/utils/parseTextAndLatex.ts b/src/utils/parseTextAndLatex.ts
new file mode 100644
index 0000000..bd00de9
--- /dev/null
+++ b/src/utils/parseTextAndLatex.ts
@@ -0,0 +1,24 @@
+interface TextLatexPart {
+ text: string;
+ isLatex: boolean;
+ key:number
+ }
+
+ export const parseTextAndLatex = (input: string): TextLatexPart[] => {
+ const result: TextLatexPart[] = [];
+
+ const parts = input?.split(/(\$\$[^$]+\$\$)/g);
+
+ parts.forEach((part,index) => {
+ if (part.startsWith('$$') && part.endsWith('$$')) {
+
+ result.push({ text: part.slice(2, -2), isLatex: true , key:index });
+ } else if (part.trim()) {
+
+ result.push({ text: part, isLatex: false,key:index });
+ }
+ });
+
+ return result;
+ };
+
diff --git a/src/zustand/ObjectToEditState.ts b/src/zustand/ObjectToEditState.ts
index 56f69a7..45979b9 100644
--- a/src/zustand/ObjectToEditState.ts
+++ b/src/zustand/ObjectToEditState.ts
@@ -1,4 +1,5 @@
import { create } from "zustand";
+import { LocalStorageEnum } from "../enums/LocalStorageEnum";
interface ModelState {
objectToEdit: any;
@@ -27,6 +28,9 @@ interface ModelState {
ShowHint: any;
setShowHint: (data: any) => void;
+
+ ShowLatexOption: any;
+ setShowLatexOption: (data: any) => void;
}
export const useObjectToEdit = create((set) => ({
@@ -52,6 +56,8 @@ export const useObjectToEdit = create((set) => ({
setDeletedQuestions: (data) => set(() => ({ DeletedQuestions: data })),
SavedQuestionData: [],
setSavedQuestionData: (data) => set(() => ({ SavedQuestionData: data })),
- ShowHint: false,
+ ShowHint: localStorage?.getItem(LocalStorageEnum.HINT_INPUT) === "true",
setShowHint: (data) => set(() => ({ ShowHint: data })),
+ ShowLatexOption: localStorage?.getItem(LocalStorageEnum.LATEX_OPTION_INPUT) === "true" ,
+ setShowLatexOption: (data) => set(() => ({ ShowLatexOption: data })),
}));