Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
441 lines
18 KiB
HTML
441 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Interactive English Class</title>
|
|
<link rel="stylesheet" href="css/main.css">
|
|
<link rel="stylesheet" href="css/navigation.css">
|
|
<link rel="stylesheet" href="css/games.css">
|
|
<link rel="stylesheet" href="css/settings.css">
|
|
</head>
|
|
<body>
|
|
<!-- Top Bar with Network Status -->
|
|
<div class="top-bar">
|
|
<div class="top-bar-left">
|
|
<div class="top-bar-title">🎓 Interactive English Class</div>
|
|
<nav class="breadcrumb" id="breadcrumb">
|
|
<button class="breadcrumb-item active" data-page="home">🏠 Home</button>
|
|
</nav>
|
|
</div>
|
|
<div class="top-bar-right">
|
|
<div class="network-status" id="network-status">
|
|
<div class="network-indicator connecting" id="network-indicator"></div>
|
|
<span class="network-status-text" id="network-status-text">Connecting...</span>
|
|
</div>
|
|
<button class="logger-toggle" onclick="openLogsInterface()" title="Open logs interface">📋</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Container -->
|
|
<main class="container" id="main-container">
|
|
|
|
<!-- Home Page -->
|
|
<div class="page active" id="home-page">
|
|
|
|
<div class="hero">
|
|
<h1>🎓 Interactive English Class</h1>
|
|
<p>Learn English while having fun!</p>
|
|
</div>
|
|
|
|
<div class="main-options">
|
|
<button class="option-card primary" onclick="navigateTo('levels')">
|
|
📚 <span>Create a custom lesson</span>
|
|
</button>
|
|
<button class="option-card secondary" onclick="showComingSoon()">
|
|
📊 <span>Statistics</span>
|
|
<small>Coming soon</small>
|
|
</button>
|
|
<button class="option-card secondary" onclick="navigateTo('settings')">
|
|
⚙️ <span>Settings</span>
|
|
<small>Audio & Debug Tools</small>
|
|
</button>
|
|
<button class="option-card primary" onclick="showContentCreator()">
|
|
🏭 <span>Content Creator</span>
|
|
<small>Create your own exercises</small>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Level/Content Selection -->
|
|
<div class="page" id="levels-page">
|
|
<div class="page-header">
|
|
<button class="back-btn" onclick="AppNavigation.goBack()">← Back</button>
|
|
<h2>📚 Choose your level</h2>
|
|
<p>Select the content you want to learn</p>
|
|
</div>
|
|
|
|
<div class="cards-grid" id="levels-grid">
|
|
<!-- Cards will be generated dynamically -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Game Type Selection -->
|
|
<div class="page" id="games-page">
|
|
<div class="page-header">
|
|
<button class="back-btn" onclick="AppNavigation.goBack()">← Back</button>
|
|
<h2>🎮 Choose your game</h2>
|
|
<p id="game-description">Select the type of activity for this content</p>
|
|
</div>
|
|
|
|
<div class="cards-grid" id="games-grid">
|
|
<!-- Cards will be generated dynamically -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Game Page -->
|
|
<div class="page" id="game-page">
|
|
<div class="game-header">
|
|
<button class="back-btn" onclick="goBack()">← Back</button>
|
|
<h3 id="game-title">Game in progress...</h3>
|
|
<div class="score-display">
|
|
Score: <span id="current-score">0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="game-container" id="game-container">
|
|
<!-- The game will be loaded here dynamically -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Page -->
|
|
<div class="page" id="settings-page">
|
|
<div class="page-header">
|
|
<button class="back-btn" onclick="goBack()">← Back</button>
|
|
<h2>⚙️ Settings & Debug Tools</h2>
|
|
</div>
|
|
|
|
<div class="settings-container">
|
|
<!-- Audio Settings Section -->
|
|
<div class="settings-section">
|
|
<h3>🔊 Audio Settings</h3>
|
|
|
|
<div class="setting-group">
|
|
<label>TTS Voice Speed:</label>
|
|
<input type="range" id="tts-rate" min="0.5" max="2" step="0.1" value="0.8">
|
|
<span id="tts-rate-value">0.8</span>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>TTS Volume:</label>
|
|
<input type="range" id="tts-volume" min="0" max="1" step="0.1" value="1">
|
|
<span id="tts-volume-value">1.0</span>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Preferred Voice:</label>
|
|
<select id="tts-voice">
|
|
<option value="">Auto (System Default)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TTS Debug Section -->
|
|
<div class="settings-section">
|
|
<h3>🔧 TTS Debug Tools</h3>
|
|
|
|
<div class="debug-info" id="tts-status">
|
|
<div class="info-item">
|
|
<span class="label">Browser Support:</span>
|
|
<span class="value" id="browser-support">Checking...</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Available Voices:</span>
|
|
<span class="value" id="voice-count">Loading...</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">English Voices:</span>
|
|
<span class="value" id="english-voice-count">Loading...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="debug-controls">
|
|
<button class="debug-btn" onclick="testBasicTTS()">🔊 Test Basic TTS</button>
|
|
<button class="debug-btn" onclick="testWithCallbacks()">📞 Test with Callbacks</button>
|
|
<button class="debug-btn" onclick="testGameWords()">🎮 Test Game Words</button>
|
|
<button class="debug-btn" onclick="refreshVoices()">🔄 Refresh Voices</button>
|
|
</div>
|
|
|
|
<div class="debug-output" id="debug-output">
|
|
<h4>Debug Output:</h4>
|
|
<div class="debug-log" id="debug-log"></div>
|
|
<button class="clear-btn" onclick="clearDebugLog()">Clear Log</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Voice List Section -->
|
|
<div class="settings-section">
|
|
<h3>🎤 Available Voices</h3>
|
|
<div class="voice-list" id="voice-list">
|
|
Loading voices...
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Browser Info Section -->
|
|
<div class="settings-section">
|
|
<h3>🌐 Browser Information</h3>
|
|
<div class="browser-info">
|
|
<div class="info-item">
|
|
<span class="label">User Agent:</span>
|
|
<span class="value small" id="user-agent"></span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Platform:</span>
|
|
<span class="value" id="platform"></span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Language:</span>
|
|
<span class="value" id="browser-language"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
|
|
<!-- Modal Coming Soon -->
|
|
<div class="modal" id="coming-soon-modal">
|
|
<div class="modal-content">
|
|
<h3>🚧 Coming Soon!</h3>
|
|
<p>This feature will be available in an upcoming version.</p>
|
|
<button onclick="closeModal()">OK</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Indicator -->
|
|
<div class="loading" id="loading">
|
|
<div class="spinner"></div>
|
|
<p>Loading...</p>
|
|
</div>
|
|
|
|
<!-- Scripts -->
|
|
<script src="js/core/websocket-logger.js"></script>
|
|
<script src="js/core/env-config.js"></script>
|
|
<script src="js/core/utils.js"></script>
|
|
<script src="js/core/content-engine.js"></script>
|
|
<script src="js/core/content-factory.js"></script>
|
|
<script src="js/core/content-parsers.js"></script>
|
|
<script src="js/core/content-generators.js"></script>
|
|
<script src="js/core/content-scanner.js"></script>
|
|
<script src="js/core/json-content-loader.js"></script>
|
|
<script src="js/core/content-game-compatibility.js"></script>
|
|
<script src="js/tools/content-creator.js"></script>
|
|
<script src="js/core/settings-manager.js"></script>
|
|
<script src="js/core/navigation.js"></script>
|
|
<script src="js/core/game-loader.js"></script>
|
|
<script>
|
|
// Fonction de debug pour le logger
|
|
function toggleLoggerDebug() {
|
|
console.log('🔧 Bouton toggle cliqué!');
|
|
|
|
if (typeof window.logger === 'undefined') {
|
|
console.error('❌ window.logger n\'existe pas!');
|
|
alert('Erreur: Logger non initialisé!');
|
|
return;
|
|
}
|
|
|
|
console.log('✅ Logger existe, toggle...');
|
|
try {
|
|
window.logger.toggle();
|
|
console.log('✅ Toggle réussi');
|
|
} catch (error) {
|
|
console.error('❌ Erreur toggle:', error);
|
|
alert('Erreur toggle: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Initialize app when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🔧 DOM loaded, initializing...');
|
|
logSh('🎯 DOM loaded, initializing application...', 'INFO');
|
|
|
|
// Vérifier que le logger existe
|
|
if (typeof window.logger === 'undefined') {
|
|
console.error('❌ Logger not found on load!');
|
|
} else {
|
|
console.log('✅ Logger found:', window.logger);
|
|
}
|
|
|
|
// Test du logger
|
|
if (typeof logSh !== 'undefined') {
|
|
logSh('🚀 Application démarrée', 'INFO');
|
|
logSh('Logger système intégré avec succès', 'DEBUG');
|
|
} else {
|
|
console.log('🚀 Application démarrée (pas de logger)');
|
|
}
|
|
|
|
// Initialize connection status listener
|
|
initConnectionStatus();
|
|
|
|
// Initialize navigation system
|
|
AppNavigation.init();
|
|
|
|
// Test initial network connection
|
|
setTimeout(testNetworkConnection, 1000);
|
|
});
|
|
|
|
// Network Status Manager
|
|
function initConnectionStatus() {
|
|
// Listen for content connection status events
|
|
window.addEventListener('contentConnectionStatus', function(event) {
|
|
updateNetworkStatus(event.detail);
|
|
});
|
|
|
|
// Test connection périodiquement
|
|
setInterval(testNetworkConnection, 30000); // Test toutes les 30 secondes
|
|
}
|
|
|
|
function updateNetworkStatus(details) {
|
|
const indicator = document.getElementById('network-indicator');
|
|
const text = document.getElementById('network-status-text');
|
|
|
|
// Remove all status classes
|
|
indicator.classList.remove('connecting', 'online', 'offline');
|
|
|
|
// Update based on status
|
|
switch(details.status) {
|
|
case 'loading':
|
|
indicator.classList.add('connecting');
|
|
text.textContent = 'Connexion...';
|
|
break;
|
|
|
|
case 'online':
|
|
indicator.classList.add('online');
|
|
text.textContent = 'Online';
|
|
break;
|
|
|
|
case 'offline':
|
|
indicator.classList.add('offline');
|
|
text.textContent = 'Local';
|
|
break;
|
|
|
|
case 'error':
|
|
indicator.classList.add('offline');
|
|
text.textContent = 'Hors ligne';
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function testNetworkConnection() {
|
|
const indicator = document.getElementById('network-indicator');
|
|
const text = document.getElementById('network-status-text');
|
|
|
|
// Désactiver les tests réseau en mode file://
|
|
if (window.location.protocol === 'file:') {
|
|
indicator.classList.remove('connecting', 'online');
|
|
indicator.classList.add('offline');
|
|
text.textContent = 'Local Mode';
|
|
logSh('📁 Mode file:// - Test réseau ignoré', 'INFO');
|
|
return;
|
|
}
|
|
|
|
logSh('🔍 Test de connexion réseau démarré...', 'INFO');
|
|
|
|
try {
|
|
// Test avec l'endpoint DigitalOcean avec timeout approprié
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 5000); // Plus de temps
|
|
|
|
// Utiliser le proxy local au lieu de DigitalOcean directement
|
|
const testUrl = 'http://localhost:8083/do-proxy/english-class-demo.json';
|
|
logSh(`🌐 Test URL (proxy): ${testUrl}`, 'INFO');
|
|
logSh(`🕐 Timeout configuré: 5000ms`, 'DEBUG');
|
|
logSh(`🔧 Mode: GET sans headers spéciaux`, 'DEBUG');
|
|
|
|
logSh('📤 Envoi de la requête fetch...', 'INFO');
|
|
const fetchStart = Date.now();
|
|
|
|
const response = await fetch(testUrl, {
|
|
method: 'GET',
|
|
signal: controller.signal,
|
|
mode: 'cors',
|
|
cache: 'no-cache'
|
|
});
|
|
|
|
const fetchDuration = Date.now() - fetchStart;
|
|
clearTimeout(timeoutId);
|
|
|
|
logSh(`⏱️ Durée de la requête: ${fetchDuration}ms`, 'INFO');
|
|
logSh(`📡 Réponse reçue: ${response.status} ${response.statusText}`, 'INFO');
|
|
logSh(`📋 Headers response: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'DEBUG');
|
|
|
|
if (response.ok) {
|
|
indicator.classList.remove('connecting', 'offline');
|
|
indicator.classList.add('online');
|
|
text.textContent = 'Online';
|
|
logSh('✅ Connection successful!', 'INFO');
|
|
} else {
|
|
// Pour les buckets privés, on considère 403 comme "connexion OK mais accès privé"
|
|
if (response.status === 403) {
|
|
indicator.classList.remove('connecting', 'offline');
|
|
indicator.classList.add('online');
|
|
text.textContent = 'Privé';
|
|
logSh('🔒 Connexion OK mais accès privé (403)', 'WARN');
|
|
} else {
|
|
logSh(`❌ Erreur HTTP: ${response.status}`, 'ERROR');
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logSh(`💥 ERREUR DÉTAILLÉE: Type=${error.constructor.name}, Name=${error.name}, Message=${error.message}`, 'ERROR');
|
|
logSh(`🔍 Stack trace: ${error.stack}`, 'DEBUG');
|
|
logSh(`🌐 URL qui a échoué: http://localhost:8083/do-proxy/english-class-demo.json`, 'ERROR');
|
|
logSh(`🔧 Type d'erreur: ${typeof error}`, 'DEBUG');
|
|
|
|
// Vérifier si le serveur proxy est accessible
|
|
logSh('🔍 Test de base du serveur proxy...', 'INFO');
|
|
try {
|
|
const basicTest = await fetch('http://localhost:8083/', { method: 'GET' });
|
|
logSh(`📡 Test serveur proxy: ${basicTest.status}`, 'INFO');
|
|
} catch (proxyError) {
|
|
logSh(`❌ Serveur proxy inaccessible: ${proxyError.message}`, 'ERROR');
|
|
}
|
|
|
|
indicator.classList.remove('connecting', 'online');
|
|
indicator.classList.add('offline');
|
|
if (error.name === 'AbortError') {
|
|
text.textContent = 'Timeout';
|
|
logSh('⏰ Timeout de connexion après 5000ms', 'WARN');
|
|
} else if (error.message.includes('Failed to fetch')) {
|
|
text.textContent = 'Serveur inaccessible';
|
|
logSh(`🚫 Le serveur proxy sur port 8083 n'est pas accessible`, 'ERROR');
|
|
} else {
|
|
text.textContent = 'Hors ligne';
|
|
logSh(`🚫 Hors ligne: ${error.message}`, 'ERROR');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Coming soon functionality
|
|
function showComingSoon() {
|
|
logSh('🚧 Ouverture modal "Bientôt disponible"', 'INFO');
|
|
document.getElementById('coming-soon-modal').classList.add('show');
|
|
}
|
|
|
|
function closeModal() {
|
|
logSh('❌ Fermeture modal', 'DEBUG');
|
|
document.getElementById('coming-soon-modal').classList.remove('show');
|
|
}
|
|
|
|
function showContentCreator() {
|
|
logSh('🏭 Ouverture du créateur de contenu', 'INFO');
|
|
// Masquer la page d'accueil
|
|
document.getElementById('home-page').classList.remove('active');
|
|
|
|
// Créer et afficher le créateur de contenu
|
|
const creator = new ContentCreator();
|
|
creator.init();
|
|
}
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
logSh('⌨️ Touche Escape pressée', 'DEBUG');
|
|
closeModal();
|
|
AppNavigation.goBack();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |