diff --git a/index.html b/index.html index b61a0eb..476d75d 100644 --- a/index.html +++ b/index.html @@ -373,11 +373,99 @@ grid-template-columns: 1fr; } } + + /* Système de notifications */ + .notification { + position: fixed; + top: 20px; + right: 20px; + min-width: 300px; + max-width: 500px; + padding: 20px 25px; + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + color: white; + font-weight: 500; + font-size: 15px; + z-index: 10000; + animation: slideIn 0.3s ease-out; + display: flex; + align-items: flex-start; + gap: 12px; + line-height: 1.5; + } + + .notification.success { + background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); + } + + .notification.error { + background: linear-gradient(135deg, #f44336 0%, #da190b 100%); + } + + .notification.info { + background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%); + } + + .notification-icon { + font-size: 24px; + flex-shrink: 0; + } + + .notification-content { + flex: 1; + white-space: pre-line; + } + + .notification-close { + background: rgba(255,255,255,0.2); + border: none; + color: white; + width: 24px; + height: 24px; + border-radius: 50%; + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 0; + flex-shrink: 0; + transition: background 0.2s; + } + + .notification-close:hover { + background: rgba(255,255,255,0.3); + } + + @keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(400px); + opacity: 0; + } + } + + .notification.hiding { + animation: slideOut 0.3s ease-in forwards; + }
-

📝 Quiz Interactif ESP32

+

📝 Quiz Interactif

@@ -392,7 +480,7 @@ ❌
- +
Aucun module
@@ -452,9 +540,36 @@ const correctScoreInput = document.getElementById('correctScore'); const incorrectScoreInput = document.getElementById('incorrectScore'); + // Fonction pour afficher une notification + function showNotification(message, type = 'info', duration = 5000) { + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + + const icons = { + success: '✅', + error: '❌', + info: 'ℹ️' + }; + + notification.innerHTML = ` +
${icons[type]}
+
${message}
+ + `; + + document.body.appendChild(notification); + + if (duration > 0) { + setTimeout(() => { + notification.classList.add('hiding'); + setTimeout(() => notification.remove(), 300); + }, duration); + } + } + // Vérifier si Web Bluetooth est disponible if (!navigator.bluetooth) { - alert('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.'); + showNotification('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.', 'error', 0); connectBtn.disabled = true; } @@ -481,13 +596,13 @@ async function connectModule() { try { - console.log('Recherche ESP32...'); + console.log('Recherche module...'); const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [SERVICE_UUID] }] }); - console.log('ESP32 trouvé:', device.name); + console.log('Module trouvé:', device.name); const server = await device.gatt.connect(); const service = await server.getPrimaryService(SERVICE_UUID); @@ -539,7 +654,7 @@ } catch (error) { console.error('Erreur connexion:', error); - alert(`Erreur: ${error.message}`); + showNotification(`Erreur de connexion: ${error.message}`, 'error'); } } @@ -676,9 +791,16 @@ const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; - parseQuizFile(content); - if (questions.length > 0) { - startQuiz(); + try { + parseQuizFile(content); + if (questions.length > 0) { + showNotification(`Quiz chargé avec succès!\n\n${questions.length} question(s) trouvée(s).`, 'success', 3000); + startQuiz(); + } + } catch (error) { + console.error('Erreur lors du chargement du quiz:', error); + questions = []; + // L'erreur a déjà été affichée dans parseQuizFile } }; reader.readAsText(file); @@ -689,36 +811,116 @@ questions = []; const lines = content.trim().split('\n'); let currentQuestion = null; + let lineNumber = 0; + const errors = []; for (let line of lines) { + lineNumber++; + const originalLine = line; line = line.trim(); if (!line) continue; if (line.startsWith('* ')) { + // Valider la question précédente avant d'en commencer une nouvelle if (currentQuestion) { - questions.push(currentQuestion); + const questionErrors = validateQuestion(currentQuestion, lineNumber - 1); + if (questionErrors.length > 0) { + errors.push(...questionErrors); + } else { + questions.push(currentQuestion); + } } + + const questionText = line.substring(2).trim(); + if (!questionText) { + errors.push(`Ligne ${lineNumber}: Question vide après '*'`); + } + currentQuestion = { - question: line.substring(2).trim(), + question: questionText, answers: [], - correctAnswer: -1 + correctAnswer: -1, + lineNumber: lineNumber }; } else if (line.startsWith('+ ')) { - if (currentQuestion) { - currentQuestion.correctAnswer = currentQuestion.answers.length; - currentQuestion.answers.push(line.substring(2).trim()); + if (!currentQuestion) { + errors.push(`Ligne ${lineNumber}: Réponse correcte '+' trouvée sans question précédente`); + continue; } + + const answerText = line.substring(2).trim(); + if (!answerText) { + errors.push(`Ligne ${lineNumber}: Réponse correcte vide après '+'`); + } + + if (currentQuestion.correctAnswer !== -1) { + errors.push(`Ligne ${lineNumber}: Question à la ligne ${currentQuestion.lineNumber} a déjà une réponse correcte (ligne avec '+')`); + } + + currentQuestion.correctAnswer = currentQuestion.answers.length; + currentQuestion.answers.push(answerText); } else if (line.startsWith('- ')) { - if (currentQuestion) { - currentQuestion.answers.push(line.substring(2).trim()); + if (!currentQuestion) { + errors.push(`Ligne ${lineNumber}: Réponse '-' trouvée sans question précédente`); + continue; + } + + const answerText = line.substring(2).trim(); + if (!answerText) { + errors.push(`Ligne ${lineNumber}: Réponse vide après '-'`); } + + currentQuestion.answers.push(answerText); + } else { + // Ligne avec un format non reconnu + errors.push(`Ligne ${lineNumber}: Format non reconnu. Utilisez '* ' pour une question, '+ ' pour la bonne réponse, '- ' pour les mauvaises réponses`); } } + // Valider la dernière question if (currentQuestion) { - questions.push(currentQuestion); + const questionErrors = validateQuestion(currentQuestion, lineNumber); + if (questionErrors.length > 0) { + errors.push(...questionErrors); + } else { + questions.push(currentQuestion); + } + } + + // Vérifier s'il y a des erreurs + if (errors.length > 0) { + const errorMessage = `Erreurs détectées dans le fichier de quiz:\n\n${errors.join('\n')}`; + showNotification(errorMessage, 'error', 10000); + throw new Error('Format de quiz invalide'); + } + + // Vérifier qu'il y a au moins une question + if (questions.length === 0) { + showNotification('Erreur: Aucune question valide trouvée dans le fichier.\n\nFormat attendu:\n* Question ?\n+ Bonne réponse\n- Mauvaise réponse\n- Mauvaise réponse', 'error', 8000); + throw new Error('Aucune question trouvée'); } } + + function validateQuestion(question, lineNumber) { + const errors = []; + + // Vérifier qu'il y a au moins 2 réponses + if (question.answers.length < 2) { + errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a que ${question.answers.length} réponse(s). Minimum requis: 2`); + } + + // Vérifier qu'il y a maximum 4 réponses (A, B, C, D) + if (question.answers.length > 4) { + errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" a ${question.answers.length} réponses. Maximum autorisé: 4 (A, B, C, D)`); + } + + // Vérifier qu'il y a une réponse correcte + if (question.correctAnswer === -1) { + errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a pas de réponse correcte (utilisez '+' devant la bonne réponse)`); + } + + return errors; + } function startQuiz() { quizContainer.classList.remove('hidden'); diff --git a/platformio.ini b/platformio.ini index 8dd1b2d..af6ef57 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,15 +1,19 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - [env:esp32dev] platform = espressif32 board = esp32dev framework = arduino -monitor_speed = 115200 \ No newline at end of file +monitor_speed = 115200 + +[env:esp32-s3-supermini] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +monitor_speed = 115200 +board_build.arduino.memory_type = qio_qspi +board_build.flash_mode = qio +board_build.psram_type = qio +board_upload.flash_size = 4MB +board_upload.maximum_size = 4194304 +board_build.partitions = default.csv +board_build.extra_flags = + -DBOARD_HAS_PSRAM \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4ff425b..208a127 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ bool oldDeviceConnected = false; uint8_t gpioStates = 0; // Stocke l'état des GPIOs (bit à bit) // Nom du module (peut être modifié pour chaque ESP32) -String moduleName = "ESP32-Module-2"; +String moduleName = "ESP32S3-Module-1"; class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) {