+
}
onClick={handleOnReturnButtonClick}
color="primary"
sx={{ marginLeft: '-0.5rem', fontSize: 16 }}
+ disabled={isSaving} // Disable button while saving
>
- Retour
+ {isSaving ? 'Enregistrement...' : 'Retour'}
- setShowDialog(false)}
- buttonOrderType="warning"
- />
);
};
diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
index 40b73bf..5d6d108 100644
--- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
+++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
@@ -11,12 +11,15 @@ import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview
import { QuizType } from '../../../Types/QuizType';
import './editorQuiz.css';
-import { Button, TextField, NativeSelect, Divider } from '@mui/material';
+import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent, MenuItem, Select, Snackbar } from '@mui/material';
import ReturnButton from 'src/components/ReturnButton/ReturnButton';
import ImageGalleryModal from 'src/components/ImageGallery/ImageGalleryModal/ImageGalleryModal';
import ApiService from '../../../services/ApiService';
import { escapeForGIFT } from '../../../utils/giftUtils';
+
+import { Upload } from '@mui/icons-material';
+import SaveIcon from '@mui/icons-material/Save';
import { ENV_VARIABLES } from 'src/constants';
interface EditQuizParams {
@@ -30,7 +33,7 @@ const QuizForm: React.FC = () => {
const [filteredValue, setFilteredValue] = useState
([]);
const { id } = useParams();
- const [value, setValue] = useState('');
+ const [values, setValues] = useState([]);
const [isNewQuiz, setNewQuiz] = useState(false);
const [quiz, setQuiz] = useState(null);
const navigate = useNavigate();
@@ -41,6 +44,27 @@ const QuizForm: React.FC = () => {
};
const [showScrollButton, setShowScrollButton] = useState(false);
+ const [isImagesCollapsed, setIsImagesCollapsed] = useState(true);
+ const [isCheatSheetCollapsed, setIsCheatSheetCollapsed] = useState(true);
+ const [isUploadCollapsed, setIsUploadCollapsed] = useState(true);
+ const [snackbarOpen, setSnackbarOpen] = useState(false);
+ const [snackbarMessage, setSnackbarMessage] = useState('');
+ const [copySuccess, setCopySuccess] = useState(null);
+
+ const QuestionVraiFaux = "::Exemple de question vrai/faux:: \n 2+2 \\= 4 ? {T} //Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}.";
+ const QuestionChoixMul = "::Ville capitale du Canada:: \nQuelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Rétroaction spécifique.\n} // Commentaire non visible (au besoin)";
+ const QuestionChoixMulMany = "::Villes canadiennes:: \n Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### Rétroaction globale de la question. \n} // Utilisez tilde (signe de vague) pour toutes les réponses. // On doit indiquer le pourcentage de chaque réponse.";
+ const QuestionCourte = "::Clé et porte:: \n Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n} // Permet de fournir plusieurs bonnes réponses. // Note: La casse n'est pas prise en compte.";
+ const QuestionNum = "::Question numérique avec marge:: \nQuel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n ::Question numérique avec plage:: \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\n::Question numérique avec plusieurs réponses::\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}";
+
+ const templates = [
+ { label: 'Vrai/Faux', value: QuestionVraiFaux },
+ { label: 'Choix multiples R1', value: QuestionChoixMul },
+ { label: 'Choix multiples R2+', value: QuestionChoixMulMany },
+ { label: 'Réponse courte', value: QuestionCourte },
+ { label: 'Numérique', value: QuestionNum },
+ ];
+
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
@@ -100,7 +124,7 @@ const QuizForm: React.FC = () => {
setQuizTitle(title);
setSelectedFolder(folderId);
setFilteredValue(content);
- setValue(quiz.content.join('\n\n'));
+ setValues(content);
} catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
@@ -112,21 +136,17 @@ const QuizForm: React.FC = () => {
fetchData();
}, [id]);
- function handleUpdatePreview(value: string) {
- if (value !== '') {
- setValue(value);
- }
+ const handleAddQuestion = () => {
+ console.log("Adding question");
+ console.log("Current values:", values); // Log current state
+ setValues([...values, '']);
+ console.log("Updated values:", [...values, '']); // Log new state
+ };
- // split value when there is at least one blank line
- const linesArray = value.split(/\n{2,}/);
-
- // if the first item in linesArray is blank, remove it
- if (linesArray[0] === '') linesArray.shift();
-
- if (linesArray[linesArray.length - 1] === '') linesArray.pop();
-
- setFilteredValue(linesArray);
- }
+ const handleUpdatePreview = (newValues: string[]) => {
+ setValues(newValues);
+ setFilteredValue(newValues.filter(value => value.trim() !== ''));
+ };
const handleQuizTitleChange = (event: React.ChangeEvent) => {
setQuizTitle(event.target.value);
@@ -134,7 +154,6 @@ const QuizForm: React.FC = () => {
const handleQuizSave = async () => {
try {
- // check if everything is there
if (quizTitle == '') {
alert("Veuillez choisir un titre");
return;
@@ -153,7 +172,8 @@ const QuizForm: React.FC = () => {
}
}
- navigate('/teacher/dashboard');
+ setSnackbarMessage('Quiz enregistré avec succès!');
+ setSnackbarOpen(true);
} catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
console.log(error)
@@ -165,10 +185,70 @@ const QuizForm: React.FC = () => {
return Chargement...
;
}
+ const handleSnackbarClose = () => {
+ setSnackbarOpen(false);
+ };
+
+ const handleSaveImage = async () => {
+ try {
+ const inputElement = document.getElementById('file-input') as HTMLInputElement;
+
+ if (!inputElement?.files || inputElement.files.length === 0) {
+ setDialogOpen(true);
+ return;
+ }
+
+ if (!inputElement.files || inputElement.files.length === 0) {
+ window.alert("Veuillez d'abord choisir une image à téléverser.")
+ return;
+ }
+
+ const imageUrl = await ApiService.uploadImage(inputElement.files[0]);
+
+ // Check for errors
+ if(imageUrl.indexOf("ERROR") >= 0) {
+ window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
+ return;
+ }
+
+ setImageLinks(prevLinks => [...prevLinks, imageUrl]);
+
+ // Reset the file input element
+ if (fileInputRef.current) {
+ fileInputRef.current.value = '';
+ }
+ } catch (error) {
+ window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`)
+
+ }
+ };
+
const handleCopyToClipboard = async (link: string) => {
navigator.clipboard.writeText(link);
}
+ const handleFocusQuestion = (index: number) => {
+ const previewElement = document.querySelector('.preview-column');
+ if (previewElement) {
+ const questionElements = previewElement.querySelectorAll('.question-item');
+ if (questionElements[index]) {
+ questionElements[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }
+ };
+
+ const copyToClipboard = (text: string, label: string) => {
+ navigator.clipboard.writeText(text)
+ .then(() => {
+ setCopySuccess(`Copié dans le presse-papier: ${label}`);
+ })
+ .catch((error) => console.error('Clipboard error:', error));
+ };
+
+ const handleSelectChange = (value: string, label: string) => {
+ copyToClipboard(value, label);
+ };
+
const handleCopyImage = (id: string) => {
const escLink = `${ENV_VARIABLES.BACKEND_URL}/api/image/get/${id}`;
setImageLinks(prevLinks => [...prevLinks, escLink]);
@@ -177,13 +257,24 @@ const QuizForm: React.FC = () => {
return (
-
+
-
Éditeur de quiz
+
+
+
Éditeur de Quiz
@@ -213,58 +304,163 @@ const QuizForm: React.FC = () => {
))}
-
+
+
+
+
+
+
setCopySuccess(null)}
+ message={copySuccess}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+ key={copySuccess ? 'open' : 'close'}
+ />
+
-
-
-
-
Mes images :
-
+ label=""
+ values={values}
+ onValuesChange={handleUpdatePreview}
+ onFocusQuestion={handleFocusQuestion} />
+
+
+
+ {/* Collapsible Upload Section */}
+
+
+ {!isUploadCollapsed && (
+
+
+
+
+ )}
-
-
+
+ {/* Collapsible Images Section */}
+
+
+ {!isImagesCollapsed && (
-
(Voir section
-
- 9. Images
-
-
ci-dessous
-
)
-
-
- Cliquez sur un lien pour le copier
-
-
- {imageLinks.map((link, index) => {
- const imgTag = `[markdown]} "texte de l'infobulle") {T}`;
- return (
- -
-
handleCopyToClipboard(imgTag)}>
- {imgTag}
-
-
- );
- })}
-
+
Mes images :
+
+
+
+ {imageLinks.map((link, index) => {
+ const imgTag = `} "texte de l'infobulle")`;
+ return (
+ -
+
handleCopyToClipboard(imgTag)}>
+ {imgTag}
+
+
+ );
+ })}
+
+
+
+ )}
+
+
+ {/* Collapsible CheatSheet Section */}
+
+
+ {!isCheatSheetCollapsed && }
-
-
-
-
+
Prévisualisation
@@ -272,7 +468,6 @@ const QuizForm: React.FC = () => {
-
{showScrollButton && (
@@ -286,9 +481,17 @@ const QuizForm: React.FC = () => {
↑
)}
+
+
);
-};
+};
const scrollToTopButtonStyle: CSSProperties = {
position: 'fixed',
diff --git a/client/src/pages/Teacher/EditorQuiz/editorQuiz.css b/client/src/pages/Teacher/EditorQuiz/editorQuiz.css
index 79157ff..299e4ac 100644
--- a/client/src/pages/Teacher/EditorQuiz/editorQuiz.css
+++ b/client/src/pages/Teacher/EditorQuiz/editorQuiz.css
@@ -80,6 +80,24 @@ input[type="file"] {
.quizEditor .editSection .preview {
flex: 50%;
padding: 5px;
-
overflow: auto;
+ height: 100%;
+ position: relative;
}
+
+.quizEditor {
+ margin: 0 -2rem; /* Counteract the padding */
+ width: calc(100% + 4rem); /* Expand to fill padded area */
+}
+
+.sticky-buttons {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 8px;
+ position: sticky;
+ top: 0;
+ background: #fff;
+ z-index: 1000;
+ padding: 8px 0;
+}
\ No newline at end of file