// === SCANNER AUTOMATIQUE DE CONTENU === class ContentScanner { constructor() { this.discoveredContent = new Map(); this.contentFiles = [ // Liste des fichiers de contenu à scanner automatiquement 'sbs-level-7-8-new.js', 'basic-chinese.js', 'english-class-demo.js' ]; } async scanAllContent() { console.log('🔍 ContentScanner - Scan automatique du contenu...'); const results = { found: [], errors: [], total: 0 }; for (const filename of this.contentFiles) { try { const contentInfo = await this.scanContentFile(filename); if (contentInfo) { this.discoveredContent.set(contentInfo.id, contentInfo); results.found.push(contentInfo); } } catch (error) { console.warn(`⚠️ Erreur scan ${filename}:`, error.message); results.errors.push({ filename, error: error.message }); } } results.total = results.found.length; console.log(`✅ Scan terminé: ${results.total} modules trouvés`); return results; } async scanContentFile(filename) { const contentId = this.extractContentId(filename); const moduleName = this.getModuleName(contentId); try { // Charger le script si pas déjà fait await this.loadScript(`js/content/${filename}`); // Vérifier si le module existe if (!window.ContentModules || !window.ContentModules[moduleName]) { throw new Error(`Module ${moduleName} non trouvé après chargement`); } const module = window.ContentModules[moduleName]; // Extraire les métadonnées const contentInfo = this.extractContentInfo(module, contentId, filename); console.log(`📦 Contenu découvert: ${contentInfo.name}`); return contentInfo; } catch (error) { throw new Error(`Impossible de charger ${filename}: ${error.message}`); } } extractContentInfo(module, contentId, filename) { return { id: contentId, filename: filename, name: module.name || this.beautifyContentId(contentId), description: module.description || 'Contenu automatiquement détecté', icon: this.getContentIcon(module, contentId), difficulty: module.difficulty || 'medium', enabled: true, // Métadonnées détaillées metadata: { version: module.version || '1.0', format: module.format || 'legacy', totalItems: this.countItems(module), categories: this.extractCategories(module), contentTypes: this.extractContentTypes(module), estimatedTime: this.calculateEstimatedTime(module), lastScanned: new Date().toISOString() }, // Statistiques stats: { vocabularyCount: this.countByType(module, 'vocabulary'), sentenceCount: this.countByType(module, 'sentence'), dialogueCount: this.countByType(module, 'dialogue'), grammarCount: this.countByType(module, 'grammar') }, // Configuration pour les jeux gameCompatibility: this.analyzeGameCompatibility(module) }; } extractContentId(filename) { return filename.replace('.js', '').toLowerCase(); } getModuleName(contentId) { const mapping = { 'sbs-level-7-8-new': 'SBSLevel78New' }; return mapping[contentId] || this.toPascalCase(contentId); } toPascalCase(str) { return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(''); } beautifyContentId(contentId) { const beautified = { 'sbs-level-7-8-new': 'SBS Level 7-8 (Simple Format)' }; return beautified[contentId] || contentId.charAt(0).toUpperCase() + contentId.slice(1); } getContentIcon(module, contentId) { // Icône du module si disponible if (module.icon) return module.icon; // Icônes par défaut selon l'ID const defaultIcons = { 'sbs-level-7-8-new': '✨' }; return defaultIcons[contentId] || '📝'; } countItems(module) { let count = 0; // Format moderne (contentItems) if (module.contentItems && Array.isArray(module.contentItems)) { return module.contentItems.length; } // Format simple (vocabulary object + sentences array) if (module.vocabulary && typeof module.vocabulary === 'object' && !Array.isArray(module.vocabulary)) { count += Object.keys(module.vocabulary).length; } // Format legacy (vocabulary array) else if (module.vocabulary && Array.isArray(module.vocabulary)) { count += module.vocabulary.length; } // Autres contenus if (module.sentences && Array.isArray(module.sentences)) count += module.sentences.length; if (module.dialogues && Array.isArray(module.dialogues)) count += module.dialogues.length; if (module.phrases && Array.isArray(module.phrases)) count += module.phrases.length; if (module.texts && Array.isArray(module.texts)) count += module.texts.length; return count; } extractCategories(module) { const categories = new Set(); if (module.categories) { Object.keys(module.categories).forEach(cat => categories.add(cat)); } if (module.metadata && module.metadata.categories) { module.metadata.categories.forEach(cat => categories.add(cat)); } // Extraire des contenus si format moderne if (module.contentItems) { module.contentItems.forEach(item => { if (item.category) categories.add(item.category); }); } // Extraire du vocabulaire selon le format if (module.vocabulary) { // Format simple (vocabulary object) if (typeof module.vocabulary === 'object' && !Array.isArray(module.vocabulary)) { // Pour l'instant, pas de catégories dans le format simple categories.add('vocabulary'); } // Format legacy (vocabulary array) else if (Array.isArray(module.vocabulary)) { module.vocabulary.forEach(word => { if (word.category) categories.add(word.category); }); } } return Array.from(categories); } extractContentTypes(module) { const types = new Set(); if (module.contentItems) { module.contentItems.forEach(item => { if (item.type) types.add(item.type); }); } else { // Format legacy - deviner les types if (module.vocabulary) types.add('vocabulary'); if (module.sentences) types.add('sentence'); if (module.dialogues || module.dialogue) types.add('dialogue'); if (module.phrases) types.add('sentence'); } return Array.from(types); } calculateEstimatedTime(module) { if (module.metadata && module.metadata.estimatedTime) { return module.metadata.estimatedTime; } // Calcul basique : 1 minute par 3 éléments const itemCount = this.countItems(module); return Math.max(5, Math.ceil(itemCount / 3)); } countByType(module, type) { if (module.contentItems) { return module.contentItems.filter(item => item.type === type).length; } // Format simple et legacy switch(type) { case 'vocabulary': // Format simple (vocabulary object) if (module.vocabulary && typeof module.vocabulary === 'object' && !Array.isArray(module.vocabulary)) { return Object.keys(module.vocabulary).length; } // Format legacy (vocabulary array) return module.vocabulary ? module.vocabulary.length : 0; case 'sentence': return (module.sentences ? module.sentences.length : 0) + (module.phrases ? module.phrases.length : 0); case 'dialogue': return module.dialogues ? module.dialogues.length : 0; case 'grammar': // Format simple (grammar object avec sous-propriétés) if (module.grammar && typeof module.grammar === 'object') { return Object.keys(module.grammar).length; } return module.grammar && Array.isArray(module.grammar) ? module.grammar.length : 0; default: return 0; } } analyzeGameCompatibility(module) { const compatibility = { 'whack-a-mole': { compatible: false, score: 0 }, 'memory-game': { compatible: false, score: 0 }, 'story-builder': { compatible: false, score: 0 }, 'temp-games': { compatible: false, score: 0 } }; const vocabCount = this.countByType(module, 'vocabulary'); const sentenceCount = this.countByType(module, 'sentence'); const dialogueCount = this.countByType(module, 'dialogue'); // Whack-a-Mole - aime le vocabulaire et phrases simples if (vocabCount > 5 || sentenceCount > 3) { compatibility['whack-a-mole'].compatible = true; compatibility['whack-a-mole'].score = Math.min(100, vocabCount * 5 + sentenceCount * 3); } // Memory Game - parfait pour vocabulaire avec images if (vocabCount > 4) { compatibility['memory-game'].compatible = true; compatibility['memory-game'].score = Math.min(100, vocabCount * 8); } // Story Builder - aime les dialogues et séquences if (dialogueCount > 0 || sentenceCount > 5) { compatibility['story-builder'].compatible = true; compatibility['story-builder'].score = Math.min(100, dialogueCount * 15 + sentenceCount * 2); } // Temp Games - accepte tout if (this.countItems(module) > 3) { compatibility['temp-games'].compatible = true; compatibility['temp-games'].score = Math.min(100, this.countItems(module) * 2); } return compatibility; } async loadScript(src) { return new Promise((resolve, reject) => { // Vérifier si déjà chargé const existingScript = document.querySelector(`script[src="${src}"]`); if (existingScript) { resolve(); return; } const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = () => reject(new Error(`Impossible de charger ${src}`)); document.head.appendChild(script); }); } // === API PUBLIQUE === async getAvailableContent() { if (this.discoveredContent.size === 0) { await this.scanAllContent(); } return Array.from(this.discoveredContent.values()); } async getContentById(id) { if (this.discoveredContent.size === 0) { await this.scanAllContent(); } return this.discoveredContent.get(id); } async getContentByGame(gameType) { const allContent = await this.getAvailableContent(); return allContent.filter(content => { const compat = content.gameCompatibility[gameType]; return compat && compat.compatible; }).sort((a, b) => { // Trier par score de compatibilité const scoreA = a.gameCompatibility[gameType].score; const scoreB = b.gameCompatibility[gameType].score; return scoreB - scoreA; }); } async refreshContent() { this.discoveredContent.clear(); return await this.scanAllContent(); } getContentStats() { const stats = { totalModules: this.discoveredContent.size, totalItems: 0, categories: new Set(), contentTypes: new Set(), difficulties: new Set() }; for (const content of this.discoveredContent.values()) { stats.totalItems += content.metadata.totalItems; content.metadata.categories.forEach(cat => stats.categories.add(cat)); content.metadata.contentTypes.forEach(type => stats.contentTypes.add(type)); stats.difficulties.add(content.difficulty); } return { ...stats, categories: Array.from(stats.categories), contentTypes: Array.from(stats.contentTypes), difficulties: Array.from(stats.difficulties) }; } } // Export global window.ContentScanner = ContentScanner;