// EditorQuiz.tsx import React, { useState, useEffect, useRef, CSSProperties } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { FolderType } from '../../../Types/FolderType'; import Editor from 'src/components/Editor/Editor'; import GiftCheatSheet from 'src/components/GIFTCheatSheet/GiftCheatSheet'; import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview'; import { QuizType } from '../../../Types/QuizType'; import './editorQuiz.css'; import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent, MenuItem, Select, Snackbar } from '@mui/material'; import ReturnButton from 'src/components/ReturnButton/ReturnButton'; import ApiService from '../../../services/ApiService'; import { escapeForGIFT } from '../../../utils/giftUtils'; import { Upload } from '@mui/icons-material'; interface EditQuizParams { id: string; [key: string]: string | undefined; } const QuizForm: React.FC = () => { const [quizTitle, setQuizTitle] = useState(''); const [selectedFolder, setSelectedFolder] = useState(''); const [filteredValue, setFilteredValue] = useState([]); const { id } = useParams(); const [value, setValue] = useState(''); const [isNewQuiz, setNewQuiz] = useState(false); const [quiz, setQuiz] = useState(null); const navigate = useNavigate(); const [folders, setFolders] = useState([]); const [imageLinks, setImageLinks] = useState([]); const handleSelectFolder = (event: React.ChangeEvent) => { setSelectedFolder(event.target.value); }; const fileInputRef = useRef(null); const [dialogOpen, setDialogOpen] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false); const [copySuccess, setCopySuccess] = useState(null); const scrollToTop = () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }; 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 }, ]; useEffect(() => { const handleScroll = () => { if (window.scrollY > 300) { setShowScrollButton(true); } else { setShowScrollButton(false); } }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, []); const scrollToImagesSection = (event: { preventDefault: () => void; }) => { event.preventDefault(); const section = document.getElementById('images-section'); if (section) { section.scrollIntoView({ behavior: 'smooth' }); } }; useEffect(() => { const fetchData = async () => { const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); }; fetchData(); }, []); useEffect(() => { const fetchData = async () => { try { if (!id || id === 'new') { setNewQuiz(true); return; } const quiz = await ApiService.getQuiz(id) as QuizType; if (!quiz) { window.alert(`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`) console.error('Quiz not found for id:', id); navigate('/teacher/dashboard'); return; } setQuiz(quiz as QuizType); const { title, content, folderId } = quiz; setQuizTitle(title); setSelectedFolder(folderId); setFilteredValue(content); setValue(quiz.content.join('\n\n')); } catch (error) { window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) console.error('Error fetching quiz:', error); navigate('/teacher/dashboard'); } }; fetchData(); }, [id]); function handleUpdatePreview(value: string) { if (value !== '') { setValue(value); } // 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 handleQuizTitleChange = (event: React.ChangeEvent) => { setQuizTitle(event.target.value); }; const handleQuizSave = async () => { try { // check if everything is there if (quizTitle == '') { alert("Veuillez choisir un titre"); return; } if (selectedFolder == '') { alert("Veuillez choisir un dossier"); return; } if (isNewQuiz) { await ApiService.createQuiz(quizTitle, filteredValue, selectedFolder); } else { if (quiz) { await ApiService.updateQuiz(quiz._id, quizTitle, filteredValue); } } navigate('/teacher/dashboard'); } catch (error) { window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) console.log(error) } }; // I do not know what this does but do not remove if (!isNewQuiz && !quiz) { return
Chargement...
; } 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 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); }; return (
Éditeur de quiz
{/*

Éditeur

*/}
setCopySuccess(null)} message={copySuccess} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} key={copySuccess ? 'open' : 'close'} />
setDialogOpen(false)} > Erreur Veuillez d'abord choisir une image à téléverser.

Mes images :

(Voir section

9. Images

ci-dessous
)

- Cliquez sur un lien pour le copier
    {imageLinks.map((link, index) => { const imgTag = `![alt_text](${escapeForGIFT(link)} "texte de l'infobulle")`; return (
  • handleCopyToClipboard(imgTag)}> {imgTag}
  • ); })}

Prévisualisation

{showScrollButton && ( )}
); }; const scrollToTopButtonStyle: CSSProperties = { position: 'fixed', bottom: '40px', right: '50px', padding: '10px', fontSize: '16px', color: 'white', backgroundColor: '#5271ff', border: 'none', cursor: 'pointer', zIndex: 1000, }; export default QuizForm;