mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
parent
851d04dfac
commit
fefe278d79
10 changed files with 182 additions and 234 deletions
|
|
@ -7,6 +7,7 @@ describe('Editor Component', () => {
|
||||||
const mockOnEditorChange = jest.fn();
|
const mockOnEditorChange = jest.fn();
|
||||||
|
|
||||||
const sampleProps = {
|
const sampleProps = {
|
||||||
|
label: 'Sample Label',
|
||||||
initialValue: 'Sample Initial Value',
|
initialValue: 'Sample Initial Value',
|
||||||
onEditorChange: mockOnEditorChange
|
onEditorChange: mockOnEditorChange
|
||||||
};
|
};
|
||||||
|
|
@ -29,6 +30,7 @@ describe('Editor Component', () => {
|
||||||
|
|
||||||
it('updates editor value when initialValue prop changes', () => {
|
it('updates editor value when initialValue prop changes', () => {
|
||||||
const updatedProps = {
|
const updatedProps = {
|
||||||
|
label: 'Updated Label',
|
||||||
initialValue: 'Updated Initial Value',
|
initialValue: 'Updated Initial Value',
|
||||||
onEditorChange: mockOnEditorChange
|
onEditorChange: mockOnEditorChange
|
||||||
};
|
};
|
||||||
|
|
@ -43,6 +45,7 @@ describe('Editor Component', () => {
|
||||||
|
|
||||||
test('should call change text with the correct value on textarea change', () => {
|
test('should call change text with the correct value on textarea change', () => {
|
||||||
const updatedProps = {
|
const updatedProps = {
|
||||||
|
label: 'Updated Label',
|
||||||
initialValue: 'Updated Initial Value',
|
initialValue: 'Updated Initial Value',
|
||||||
onEditorChange: mockOnEditorChange
|
onEditorChange: mockOnEditorChange
|
||||||
};
|
};
|
||||||
|
|
@ -58,6 +61,7 @@ describe('Editor Component', () => {
|
||||||
|
|
||||||
test('should call onEditorChange with an empty string if textarea value is falsy', () => {
|
test('should call onEditorChange with an empty string if textarea value is falsy', () => {
|
||||||
const updatedProps = {
|
const updatedProps = {
|
||||||
|
label: 'Updated Label',
|
||||||
initialValue: 'Updated Initial Value',
|
initialValue: 'Updated Initial Value',
|
||||||
onEditorChange: mockOnEditorChange
|
onEditorChange: mockOnEditorChange
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ describe('QuizForm Component', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Éditeur de quiz')).toBeInTheDocument();
|
expect(screen.queryByText('Éditeur de quiz')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Éditeur')).toBeInTheDocument();
|
// expect(screen.queryByText('Éditeur')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Prévisualisation')).toBeInTheDocument();
|
expect(screen.queryByText('Prévisualisation')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
// Editor.tsx
|
// Editor.tsx
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import './editor.css';
|
import './editor.css';
|
||||||
|
import { TextareaAutosize } from '@mui/material';
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
|
label: string;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
onEditorChange: (value: string) => void;
|
onEditorChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Editor: React.FC<EditorProps> = ({ initialValue, onEditorChange }) => {
|
const Editor: React.FC<EditorProps> = ({ initialValue, onEditorChange, label }) => {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
const editorRef = useRef<HTMLTextAreaElement | null>(null);
|
const editorRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
|
|
@ -18,14 +20,17 @@ const Editor: React.FC<EditorProps> = ({ initialValue, onEditorChange }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<label>
|
||||||
<textarea
|
<h4>{label}</h4>
|
||||||
|
<TextareaAutosize
|
||||||
|
id="editor-textarea"
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
onChange={handleEditorChange}
|
onChange={handleEditorChange}
|
||||||
value={value}
|
value={value}
|
||||||
className="editor"
|
className="editor"
|
||||||
></textarea>
|
minRows={5}
|
||||||
</div>
|
/>
|
||||||
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,18 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const QuestionVraiFaux = "2+2 \\= 4 ? {T\n}// Vous pouvez utiliser les valeurs {T}, {F}, {TRUE} et {FALSE}";
|
const QuestionVraiFaux = "2+2 \\= 4 ? {T\n}// Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}";
|
||||||
const QuestionChoixMul = "Quelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Bonne réponse!\n}// La bonne réponse est Ottawa";
|
const QuestionChoixMul = "Quelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Bonne réponse!\n}// La bonne réponse est Ottawa";
|
||||||
const QuestionChoixMulMany = "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#### La bonne réponse est Montréal, Ottawa et Vancouver \n} //On utilise le signe ~ pour toutes les réponses. On doit indiquer le pourcentage de chaque réponse";
|
const QuestionChoixMulMany = "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#### La bonne réponse est Montréal, Ottawa et Vancouver \n}\n// Utilisez le signe ~ pour toutes les réponses.\n// On doit indiquer le pourcentage de chaque réponse.";
|
||||||
const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}// Permet de fournir plusieurs bonnes réponses. Note: Les majuscules ne sont pas prises en compte.";
|
const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}\n// Permet de fournir plusieurs bonnes réponses.\n// Note: La casse n'est pas prise en compte.";
|
||||||
const QuestionNum ="Question {#=Nombre\n} //OU \nQuestion {#=Nombre:Tolérance\n} //OU \nQuestion {#=PetitNombre..GrandNombre\n} // La tolérance est un pourcentage. La réponse doit être comprise entre PetitNombre et GrandNombre";
|
const QuestionNum ="Question {#=Nombre\n} //OU \nQuestion {#=Nombre:Tolérance\n} // OU \nQuestion {#=PetitNombre..GrandNombre\n}\n// La tolérance est un pourcentage.\n// La réponse doit être comprise entre PetitNombre et GrandNombre";
|
||||||
return (
|
return (
|
||||||
<div className="gift-cheat-sheet">
|
<div className="gift-cheat-sheet">
|
||||||
<h2 className="subtitle">Informations pratiques sur l'éditeur</h2>
|
<h2 className="subtitle">Informations pratiques sur l'éditeur</h2>
|
||||||
<span>
|
<span>
|
||||||
L'éditeur utilise le format GIFT (General Import Format Template) créé pour la
|
L'éditeur utilise le format GIFT (General Import Format Template) créé pour la
|
||||||
plateforme Moodle afin de générer les quizs. Ci-dessous vous pouvez retrouver la
|
plateforme Moodle afin de générer les mini-tests. Ci-dessous vous pouvez retrouver la
|
||||||
syntaxe pour chaque type de question ainsi que les champs optionnels :
|
syntaxe pour chaque type de question :
|
||||||
</span>
|
</span>
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4>1. Questions Vrai/Faux</h4>
|
<h4>1. Questions Vrai/Faux</h4>
|
||||||
|
|
@ -69,7 +69,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4>4. Questions à reponse courte</h4>
|
<h4>4. Questions à réponse courte</h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code className="question-code-block selectable-text">
|
<code className="question-code-block selectable-text">
|
||||||
{QuestionCourte}
|
{QuestionCourte}
|
||||||
|
|
@ -79,7 +79,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4> 5. Questions numériques </h4>
|
<h4> 5. Question numérique </h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code className="question-code-block selectable-text">
|
<code className="question-code-block selectable-text">
|
||||||
{
|
{
|
||||||
|
|
@ -139,7 +139,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4> 8. LaTeX et Markdown</h4>
|
<h4> 8. LaTeX et Markdown</h4>
|
||||||
<p>
|
<p>
|
||||||
Les format LaTeX et markdown sont supportés dans cette application. Vous devez cependant penser
|
Les formats LaTeX et Markdown sont supportés dans cette application. Vous devez cependant penser
|
||||||
à 'échapper' les caractères spéciaux mentionnés plus haut.
|
à 'échapper' les caractères spéciaux mentionnés plus haut.
|
||||||
</p>
|
</p>
|
||||||
<p>Exemple d'équation:</p>
|
<p>Exemple d'équation:</p>
|
||||||
|
|
@ -154,18 +154,30 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4> 9. inserer une image </h4>
|
<h4> 9. Images </h4>
|
||||||
<p>Pour insérer une image, vous devez utiliser la syntaxe suivante:</p>
|
<p>Pour insérer une image dans une question ou dans une réponse, vous devez utiliser la syntaxe suivante:</p>
|
||||||
<pre>
|
<pre>
|
||||||
<code className="question-code-block">
|
<code className="question-code-block">
|
||||||
{'<img '}
|
{''}
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
<p>Exemple d'une question Vrai/Faux avec l'image d'un chat:</p>
|
||||||
|
<pre>
|
||||||
|
<code className="question-code-block">
|
||||||
|
{'[markdown]Ceci est un chat: \n\n{T}'}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
<p>Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.</p>
|
||||||
|
<p>Note : On ne peut utiliser les images dans les messages de rétroaction (GIFT), car les rétroactions ne supportent pas le texte avec formatage (Markdown).</p>
|
||||||
<p style={{ color: 'red' }}>
|
<p style={{ color: 'red' }}>
|
||||||
Attention nous ne supportons pas encore les images en tant que réponses à une
|
Attention: l'ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n'est plus
|
||||||
question
|
supportée.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -184,5 +196,4 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default GiftCheatSheet;
|
export default GiftCheatSheet;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.gift-cheat-sheet {
|
.gift-cheat-sheet {
|
||||||
width: 30vw;
|
/* width: 30vw; */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,39 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 1000px;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media query for phones in portrait mode (max-width: 767px) */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.content {
|
||||||
|
max-width: 100%; /* Full width for small screens */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media query for tablets (min-width: 768px) */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.content {
|
||||||
|
max-width: 750px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media query for small desktops (min-width: 992px) */
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.content {
|
||||||
|
max-width: 970px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media query for large desktops (min-width: 1200px) */
|
||||||
|
@media (min-width: 1800px) {
|
||||||
|
.content {
|
||||||
|
max-width: 1770px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
|
|
@ -55,159 +82,3 @@ main {
|
||||||
padding: 2rem 2rem;
|
padding: 2rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
main {
|
|
||||||
height: 85%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 5rem;
|
|
||||||
}
|
|
||||||
.wrapper {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app {
|
|
||||||
height: 100%;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
left: 0.5rem;
|
|
||||||
top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: xx-large;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-sm {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
.text-base {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
.text-lg {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
.text-xl {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-2xl {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-v-align {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-v-align > * {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-h-align {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-h-align > * {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.end-h-align {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-text {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 36pt;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.quit-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 1rem;
|
|
||||||
top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-auto {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-1\/2 {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
.mb-1 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-2 {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-3 {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-4 {
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-5 {
|
|
||||||
margin-bottom: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blue {
|
|
||||||
color: #5271ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-12 {
|
|
||||||
width: 13rem;
|
|
||||||
} */
|
|
||||||
|
|
@ -370,7 +370,7 @@ const Dashboard: React.FC = () => {
|
||||||
<TextField
|
<TextField
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
placeholder="Rechercher un quiz"
|
placeholder="Rechercher un quiz par son titre"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// EditorQuiz.tsx
|
// EditorQuiz.tsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { FolderType } from '../../../Types/FolderType';
|
import { FolderType } from '../../../Types/FolderType';
|
||||||
|
|
@ -11,11 +11,12 @@ import GIFTTemplatePreview from '../../../components/GiftTemplate/GIFTTemplatePr
|
||||||
import { QuizType } from '../../../Types/QuizType';
|
import { QuizType } from '../../../Types/QuizType';
|
||||||
|
|
||||||
import './editorQuiz.css';
|
import './editorQuiz.css';
|
||||||
import { Button, TextField, NativeSelect, IconButton } from '@mui/material';
|
import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent } from '@mui/material';
|
||||||
import { Send } from '@mui/icons-material';
|
|
||||||
import ReturnButton from '../../../components/ReturnButton/ReturnButton';
|
import ReturnButton from '../../../components/ReturnButton/ReturnButton';
|
||||||
|
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
|
import { escapeForGIFT } from '../../../utils/giftUtils';
|
||||||
|
import { Upload } from '@mui/icons-material';
|
||||||
|
|
||||||
interface EditQuizParams {
|
interface EditQuizParams {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -37,6 +38,8 @@ const QuizForm: React.FC = () => {
|
||||||
const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
setSelectedFolder(event.target.value);
|
setSelectedFolder(event.target.value);
|
||||||
};
|
};
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
|
@ -134,9 +137,14 @@ const QuizForm: React.FC = () => {
|
||||||
const handleSaveImage = async () => {
|
const handleSaveImage = async () => {
|
||||||
try {
|
try {
|
||||||
const inputElement = document.getElementById('file-input') as HTMLInputElement;
|
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) {
|
if (!inputElement.files || inputElement.files.length === 0) {
|
||||||
window.alert("Veuillez d'abord choisir un fichier à télécharger")
|
window.alert("Veuillez d'abord choisir une image à téléverser.")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,6 +157,11 @@ const QuizForm: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageLinks(prevLinks => [...prevLinks, imageUrl]);
|
setImageLinks(prevLinks => [...prevLinks, imageUrl]);
|
||||||
|
|
||||||
|
// Reset the file input element
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
|
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
|
||||||
}
|
}
|
||||||
|
|
@ -172,73 +185,103 @@ const QuizForm: React.FC = () => {
|
||||||
<div className='dumb'></div>
|
<div className='dumb'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* <h2 className="subtitle">Éditeur</h2> */}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
onChange={handleQuizTitleChange}
|
||||||
|
value={quizTitle}
|
||||||
|
placeholder="Titre du quiz"
|
||||||
|
label="Titre du quiz"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<label>Choisir un dossier:
|
||||||
|
<NativeSelect
|
||||||
|
id="select-folder"
|
||||||
|
color="primary"
|
||||||
|
value={selectedFolder}
|
||||||
|
onChange={handleSelectFolder}
|
||||||
|
disabled={!isNewQuiz}
|
||||||
|
style={{ marginBottom: '16px' }} // Ajout de marge en bas
|
||||||
|
>
|
||||||
|
<option disabled value=""> Choisir un dossier... </option>
|
||||||
|
|
||||||
|
{folders.map((folder: FolderType) => (
|
||||||
|
<option value={folder._id} key={folder._id}> {folder.title} </option>
|
||||||
|
))}
|
||||||
|
</NativeSelect></label>
|
||||||
|
|
||||||
|
<Button variant="contained" onClick={handleQuizSave}>
|
||||||
|
Enregistrer
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
|
|
||||||
<div className='editSection'>
|
<div className='editSection'>
|
||||||
|
|
||||||
<div className='edit'>
|
<div className='edit'>
|
||||||
<h2 className="subtitle">Éditeur</h2>
|
<Editor
|
||||||
<TextField
|
label="Contenu GIFT du quiz:"
|
||||||
onChange={handleQuizTitleChange}
|
initialValue={value}
|
||||||
value={quizTitle}
|
onEditorChange={handleUpdatePreview} />
|
||||||
placeholder="Titre du quiz"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NativeSelect
|
|
||||||
id="select-folder"
|
|
||||||
color="primary"
|
|
||||||
value={selectedFolder}
|
|
||||||
onChange={handleSelectFolder}
|
|
||||||
disabled={!isNewQuiz}
|
|
||||||
>
|
|
||||||
<option disabled value=""> Choisir un dossier... </option>
|
|
||||||
|
|
||||||
{folders.map((folder: FolderType) => (
|
|
||||||
<option value={folder._id} key={folder._id}> {folder.title} </option>
|
|
||||||
))}
|
|
||||||
</NativeSelect>
|
|
||||||
|
|
||||||
<Editor initialValue={value} onEditorChange={handleUpdatePreview} />
|
|
||||||
|
|
||||||
<div className='images'>
|
<div className='images'>
|
||||||
<div className='upload'>
|
<div className='upload'>
|
||||||
<label className="dropArea">
|
<label className="dropArea">
|
||||||
<input type="file" id="file-input" className="file-input" accept="image/jpeg" multiple />
|
<input type="file" id="file-input" className="file-input"
|
||||||
|
accept="image/jpeg, image/png"
|
||||||
|
multiple
|
||||||
|
ref={fileInputRef} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
aria-label='Téléverser'
|
||||||
|
onClick={handleSaveImage}>
|
||||||
|
Téléverser <Upload />
|
||||||
|
</Button>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
<Dialog
|
||||||
<IconButton
|
open={dialogOpen}
|
||||||
color="primary"
|
onClose={() => setDialogOpen(false)} >
|
||||||
onClick={handleSaveImage}
|
<DialogTitle>Erreur</DialogTitle>
|
||||||
> <Send /> </IconButton>
|
<DialogContent>
|
||||||
|
Veuillez d'abord choisir une image à téléverser.
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setDialogOpen(false)} color="primary">
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="subtitle">Mes images :</h2>
|
<h4>Mes images :</h4>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<div>(Cliquez sur un lien pour le copier)</div>
|
||||||
<ul>
|
<ul>
|
||||||
{imageLinks.map((link, index) => (
|
{imageLinks.map((link, index) => {
|
||||||
<li key={index}>
|
const imgTag = `} "texte de l'infobulle")`;
|
||||||
<code
|
return (
|
||||||
onClick={() => handleCopyToClipboard(`<img ${link} >`)}>
|
<li key={index}>
|
||||||
{`<img ${link} >`}
|
<code
|
||||||
</code>
|
onClick={() => handleCopyToClipboard(imgTag)}>
|
||||||
</li>
|
{imgTag}
|
||||||
))}
|
</code>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button variant="contained" onClick={handleQuizSave}>
|
|
||||||
Enregistrer
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<GiftCheatSheet />
|
<GiftCheatSheet />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='preview'>
|
<div className='preview'>
|
||||||
<div className="preview-column">
|
<div className="preview-column">
|
||||||
<h2 className="subtitle">Prévisualisation</h2>
|
<h4>Prévisualisation</h4>
|
||||||
<div>
|
<div>
|
||||||
<GIFTTemplatePreview questions={filteredValue} />
|
<GIFTTemplatePreview questions={filteredValue} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,20 @@
|
||||||
.quizEditor .editSection .edit .upload {
|
.quizEditor .editSection .edit .upload {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: right;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.upload .dropArea {
|
||||||
|
flex-direction: column;
|
||||||
|
/* align-items: stretch; */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
4
client/src/utils/giftUtils.ts
Normal file
4
client/src/utils/giftUtils.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export function escapeForGIFT(link: string): string {
|
||||||
|
const specialChars = /[{}#~=<>\:]/g;
|
||||||
|
return link.replace(specialChars, (match) => `\\${match}`);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue