diff --git a/client/src/__tests__/components/Editor/Editor.test.tsx b/client/src/__tests__/components/Editor/Editor.test.tsx index d09d5a0..fd0e642 100644 --- a/client/src/__tests__/components/Editor/Editor.test.tsx +++ b/client/src/__tests__/components/Editor/Editor.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import Editor from '../../../components/Editor/Editor'; +import Editor from '../../../components/Editor/newEditor'; describe('Editor Component', () => { const mockOnValuesChange = jest.fn(); diff --git a/client/src/components/Editor/Editor.tsx b/client/src/components/Editor/Editor.tsx index 1ee403b..04ff8f8 100644 --- a/client/src/components/Editor/Editor.tsx +++ b/client/src/components/Editor/Editor.tsx @@ -1,202 +1,33 @@ -import React, { useState } from 'react'; -import { TextField, Typography, IconButton, Box, Collapse, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material'; -import DeleteIcon from '@mui/icons-material/Delete'; -import VisibilityIcon from '@mui/icons-material/Visibility'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import React, { useRef } from 'react'; +import './editor.css'; +import { TextareaAutosize } from '@mui/material'; interface EditorProps { label: string; values: string[]; - onValuesChange: (values: string[]) => void; - onFocusQuestion?: (index: number) => void; + onEditorChange: (value: string) => void; } -const Editor: React.FC = ({ label, values, onValuesChange, onFocusQuestion }) => { - const [collapsed, setCollapsed] = useState(Array(values.length).fill(false)); - const [dialogOpen, setDialogOpen] = useState(false); - const [deleteIndex, setDeleteIndex] = useState(null); +const Editor: React.FC = ({ label, values, onEditorChange }) => { + const editorRef = useRef(null); - const handleChange = (index: number) => (event: React.ChangeEvent) => { - const newValues = [...values]; - newValues[index] = event.target.value; - onValuesChange(newValues); - }; - - const handleDeleteQuestion = (index: number) => () => { - if (values[index].trim() === '') { - const newValues = values.filter((_, i) => i !== index); - onValuesChange(newValues); - setCollapsed((prev) => prev.filter((_, i) => i !== index)); - } else { - setDeleteIndex(index); - setDialogOpen(true); - } - }; - - const handleConfirmDelete = () => { - if (deleteIndex !== null) { - const newValues = values.filter((_, i) => i !== deleteIndex); - onValuesChange(newValues); - setCollapsed((prev) => prev.filter((_, i) => i !== deleteIndex)); - } - setDialogOpen(false); - setDeleteIndex(null); - }; - - const handleCancelDelete = () => { - setDialogOpen(false); - setDeleteIndex(null); - }; - - const handleFocusQuestion = (index: number) => () => { - if (onFocusQuestion) { - onFocusQuestion(index); - } - }; - - const handleToggleCollapse = (index: number) => () => { - setCollapsed((prev) => { - const newCollapsed = [...prev]; - newCollapsed[index] = !newCollapsed[index]; - return newCollapsed; - }); - }; - - const onDragEnd = (result: any) => { - if (!result.destination) return; - - const newValues = [...values]; - const [reorderedItem] = newValues.splice(result.source.index, 1); - newValues.splice(result.destination.index, 0, reorderedItem); - onValuesChange(newValues); - - const newCollapsed = [...collapsed]; - const [reorderedCollapsed] = newCollapsed.splice(result.source.index, 1); - newCollapsed.splice(result.destination.index, 0, reorderedCollapsed); - setCollapsed(newCollapsed); - }; - - if (collapsed.length !== values.length) { - setCollapsed((prev) => { - const newCollapsed = [...prev]; - while (newCollapsed.length < values.length) newCollapsed.push(false); - while (newCollapsed.length > values.length) newCollapsed.pop(); - return newCollapsed; - }); + function handleEditorChange(event: React.ChangeEvent) { + const text = event.target.value; + onEditorChange(text || ''); } return ( -
- - {label} - - - - - {(provided) => ( -
- {values.map((value, index) => ( - - {(provided) => ( - - - - Question {index + 1} - - - - {collapsed[index] ? : } - - - - - - - - - - - - - - )} - - ))} - {provided.placeholder} -
- )} -
-
- - {/* Confirmation Dialog */} - - Suppression - - - Confirmez vous la suppression de Question {deleteIndex !== null ? deleteIndex + 1 : ''} ? - - - - - - - -
+ ); }; diff --git a/client/src/components/Editor/editor.css b/client/src/components/Editor/editor.css index d47c8c8..64e7c69 100644 --- a/client/src/components/Editor/editor.css +++ b/client/src/components/Editor/editor.css @@ -6,4 +6,4 @@ padding-top: 10px; font-size: medium; resize: none; -} +} \ No newline at end of file diff --git a/client/src/components/Editor/newEditor.css b/client/src/components/Editor/newEditor.css new file mode 100644 index 0000000..d47c8c8 --- /dev/null +++ b/client/src/components/Editor/newEditor.css @@ -0,0 +1,9 @@ +.editor { + width: 100%; + height: 50vh; + background-color: #f8f9ff; + padding-left: 10px; + padding-top: 10px; + font-size: medium; + resize: none; +} diff --git a/client/src/components/Editor/newEditor.tsx b/client/src/components/Editor/newEditor.tsx new file mode 100644 index 0000000..1ee403b --- /dev/null +++ b/client/src/components/Editor/newEditor.tsx @@ -0,0 +1,203 @@ +import React, { useState } from 'react'; +import { TextField, Typography, IconButton, Box, Collapse, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; + +interface EditorProps { + label: string; + values: string[]; + onValuesChange: (values: string[]) => void; + onFocusQuestion?: (index: number) => void; +} + +const Editor: React.FC = ({ label, values, onValuesChange, onFocusQuestion }) => { + const [collapsed, setCollapsed] = useState(Array(values.length).fill(false)); + const [dialogOpen, setDialogOpen] = useState(false); + const [deleteIndex, setDeleteIndex] = useState(null); + + const handleChange = (index: number) => (event: React.ChangeEvent) => { + const newValues = [...values]; + newValues[index] = event.target.value; + onValuesChange(newValues); + }; + + const handleDeleteQuestion = (index: number) => () => { + if (values[index].trim() === '') { + const newValues = values.filter((_, i) => i !== index); + onValuesChange(newValues); + setCollapsed((prev) => prev.filter((_, i) => i !== index)); + } else { + setDeleteIndex(index); + setDialogOpen(true); + } + }; + + const handleConfirmDelete = () => { + if (deleteIndex !== null) { + const newValues = values.filter((_, i) => i !== deleteIndex); + onValuesChange(newValues); + setCollapsed((prev) => prev.filter((_, i) => i !== deleteIndex)); + } + setDialogOpen(false); + setDeleteIndex(null); + }; + + const handleCancelDelete = () => { + setDialogOpen(false); + setDeleteIndex(null); + }; + + const handleFocusQuestion = (index: number) => () => { + if (onFocusQuestion) { + onFocusQuestion(index); + } + }; + + const handleToggleCollapse = (index: number) => () => { + setCollapsed((prev) => { + const newCollapsed = [...prev]; + newCollapsed[index] = !newCollapsed[index]; + return newCollapsed; + }); + }; + + const onDragEnd = (result: any) => { + if (!result.destination) return; + + const newValues = [...values]; + const [reorderedItem] = newValues.splice(result.source.index, 1); + newValues.splice(result.destination.index, 0, reorderedItem); + onValuesChange(newValues); + + const newCollapsed = [...collapsed]; + const [reorderedCollapsed] = newCollapsed.splice(result.source.index, 1); + newCollapsed.splice(result.destination.index, 0, reorderedCollapsed); + setCollapsed(newCollapsed); + }; + + if (collapsed.length !== values.length) { + setCollapsed((prev) => { + const newCollapsed = [...prev]; + while (newCollapsed.length < values.length) newCollapsed.push(false); + while (newCollapsed.length > values.length) newCollapsed.pop(); + return newCollapsed; + }); + } + + return ( +
+ + {label} + + + + + {(provided) => ( +
+ {values.map((value, index) => ( + + {(provided) => ( + + + + Question {index + 1} + + + + {collapsed[index] ? : } + + + + + + + + + + + + + + )} + + ))} + {provided.placeholder} +
+ )} +
+
+ + {/* Confirmation Dialog */} + + Suppression + + + Confirmez vous la suppression de Question {deleteIndex !== null ? deleteIndex + 1 : ''} ? + + + + + + + +
+ ); +}; + +export default Editor; \ No newline at end of file diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx index f6060ca..e49a872 100644 --- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx +++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx @@ -1,16 +1,17 @@ -// EditorQuiz.tsx import React, { useState, useEffect, useRef, CSSProperties } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { FolderType } from '../../../Types/FolderType'; +import NewEditor from 'src/components/Editor/newEditor'; 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 './EditorQuiz.css'; import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent, MenuItem, Select, Snackbar } from '@mui/material'; import ReturnButton from 'src/components/ReturnButton/ReturnButton'; @@ -50,6 +51,8 @@ const QuizForm: React.FC = () => { const [snackbarMessage, setSnackbarMessage] = useState(''); const [copySuccess, setCopySuccess] = useState(null); + const [useNewEditor, setUserNewEditor] = useState(false); + 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."; @@ -142,11 +145,27 @@ const QuizForm: React.FC = () => { console.log("Updated values:", [...values, '']); // Log new state }; - const handleUpdatePreview = (newValues: string[]) => { + const newHandleUpdatePreview = (newValues: string[]) => { setValues(newValues); setFilteredValue(newValues.filter(value => value.trim() !== '')); }; + const handleUpdatePreview = (value: string) => { + if (value !== '') { + setValues([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); }; @@ -205,7 +224,7 @@ const QuizForm: React.FC = () => { const imageUrl = await ApiService.uploadImage(inputElement.files[0]); // Check for errors - if(imageUrl.indexOf("ERROR") >= 0) { + if (imageUrl.indexOf("ERROR") >= 0) { window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) return; } @@ -248,157 +267,103 @@ const QuizForm: React.FC = () => { copyToClipboard(value, label); }; + const toggleEditor = () => { + setUserNewEditor(!useNewEditor); + } + return (
- -
- - - - +
+
Éditeur de Quiz
+ -
- {/*

Éditeur

*/} - - + - {folders.map((folder: FolderType) => ( - - ))} - - - -
+
-
- setCopySuccess(null)} - message={copySuccess} - anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} - key={copySuccess ? 'open' : 'close'} - /> - + setCopySuccess(null)} message={copySuccess} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} key={copySuccess ? 'open' : 'close'} /> +
-
- - - -
- {/* Collapsible Upload Section */} + {useNewEditor ? ( + + ) : ( + + )} + {useNewEditor && ( + + )} +
- {!isUploadCollapsed && (
setDialogOpen(false)}> Erreur - - Veuillez d'abord choisir une image à téléverser. - + Veuillez d'abord choisir une image à téléverser. - +
)}
- {/* Collapsible Images Section */}
- {!isImagesCollapsed && ( @@ -407,16 +372,8 @@ const QuizForm: React.FC = () => {
(Voir section
- - - -

9. Images

-
-
+
+

9. Images

ci-dessous
)
@@ -428,9 +385,7 @@ const QuizForm: React.FC = () => { const imgTag = `![alt_text](${escapeForGIFT(link)} "texte de l'infobulle")`; return (
  • - handleCopyToClipboard(imgTag)}> - {imgTag} - + handleCopyToClipboard(imgTag)}>{imgTag}
  • ); })} @@ -440,13 +395,8 @@ const QuizForm: React.FC = () => { )}
    - {/* Collapsible CheatSheet Section */}
    - {!isCheatSheetCollapsed && } @@ -465,27 +415,15 @@ const QuizForm: React.FC = () => {
    {showScrollButton && ( - )} - +
    ); -}; +}; const scrollToTopButtonStyle: CSSProperties = { position: 'fixed',