Class_generator/js/core/navigation.js
StillHammer b004382cee Initial commit: Interactive English Learning Platform
- Complete SPA architecture with dynamic module loading
- 9 different educational games (whack-a-mole, memory, quiz, etc.)
- Rich content system supporting multimedia (audio, images, video)
- Chinese study mode with character recognition
- Adaptive game system based on available content
- Content types: vocabulary, grammar, poems, fill-blanks, corrections
- AI-powered text evaluation for open-ended answers
- Flexible content schema with backward compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 14:25:13 +08:00

495 lines
17 KiB
JavaScript

// === SYSTÈME DE NAVIGATION ===
const AppNavigation = {
currentPage: 'home',
navigationHistory: ['home'],
gamesConfig: null,
contentScanner: new ContentScanner(),
scannedContent: null,
init() {
this.loadGamesConfig();
this.initContentScanner();
this.setupEventListeners();
this.handleInitialRoute();
},
async loadGamesConfig() {
// Utilisation directe de la config par défaut (pas de fetch)
console.log('📁 Utilisation de la configuration par défaut');
this.gamesConfig = this.getDefaultConfig();
},
async initContentScanner() {
try {
console.log('🔍 Initialisation du scanner de contenu...');
this.scannedContent = await this.contentScanner.scanAllContent();
console.log(`${this.scannedContent.found.length} modules de contenu détectés automatiquement`);
} catch (error) {
console.error('Erreur scan contenu:', error);
}
},
getDefaultConfig() {
return {
games: {
'whack-a-mole': {
enabled: true,
name: 'Whack-a-Mole',
icon: '🔨',
description: 'Tape sur les bonnes réponses !'
},
'whack-a-mole-hard': {
enabled: true,
name: 'Whack-a-Mole Hard',
icon: '💥',
description: '3 moles at once, 5x3 grid, harder!'
},
'memory-match': {
enabled: true,
name: 'Memory Match',
icon: '🧠',
description: 'Find matching English-French pairs!'
},
'quiz-game': {
enabled: true,
name: 'Quiz Game',
icon: '❓',
description: 'Answer vocabulary questions!'
},
'temp-games': {
enabled: true,
name: 'Jeux Temporaires',
icon: '🎯',
description: 'Mini-jeux en développement'
},
'fill-the-blank': {
enabled: true,
name: 'Fill the Blank',
icon: '📝',
description: 'Complète les phrases en remplissant les blancs !'
},
'text-reader': {
enabled: true,
name: 'Text Reader',
icon: '📖',
description: 'Read texts sentence by sentence'
},
'adventure-reader': {
enabled: true,
name: 'Adventure Reader',
icon: '⚔️',
description: 'Zelda-style adventure with vocabulary!'
}
},
content: {
'sbs-level-8': {
enabled: true,
name: 'SBS Level 8',
icon: '📚',
description: 'Vocabulaire manuel SBS'
},
'animals': {
enabled: false,
name: 'Animals',
icon: '🐱',
description: 'Vocabulaire des animaux'
},
'colors': {
enabled: false,
name: 'Colors & Numbers',
icon: '🌈',
description: 'Couleurs et nombres'
}
}
};
},
setupEventListeners() {
// Navigation par URL
window.addEventListener('popstate', () => {
this.handleInitialRoute();
});
// Raccourcis clavier
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.goBack();
}
});
// Scroll pour masquer/afficher la breadcrumb
this.setupScrollBehavior();
},
handleInitialRoute() {
const params = Utils.getUrlParams();
if (params.page === 'play' && params.game && params.content) {
this.showGamePage(params.game, params.content);
} else if (params.page === 'levels' && params.game) {
this.showLevelsPage(params.game);
} else if (params.page === 'games') {
this.showGamesPage();
} else {
this.showHomePage();
}
},
setupScrollBehavior() {
let lastScrollY = window.scrollY;
let breadcrumb = null;
const handleScroll = () => {
if (!breadcrumb) {
breadcrumb = document.querySelector('.breadcrumb');
if (!breadcrumb) return;
}
const currentScrollY = window.scrollY;
// Si on scroll vers le bas et qu'on a scrollé plus de 50px
if (currentScrollY > lastScrollY && currentScrollY > 50) {
breadcrumb.classList.add('hidden');
breadcrumb.classList.remove('visible');
}
// Si on scroll vers le haut ou qu'on est près du top
else if (currentScrollY < lastScrollY || currentScrollY <= 50) {
breadcrumb.classList.remove('hidden');
breadcrumb.classList.add('visible');
}
lastScrollY = currentScrollY;
};
// Throttle scroll event pour les performances
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
handleScroll();
ticking = false;
});
ticking = true;
}
});
},
// Navigation vers une page
navigateTo(page, game = null, content = null) {
const params = { page };
if (game) params.game = game;
if (content) params.content = content;
Utils.setUrlParams(params);
// Mise à jour historique
if (this.currentPage !== page) {
this.navigationHistory.push(page);
}
this.currentPage = page;
// Affichage de la page appropriée
switch(page) {
case 'games':
this.showGamesPage();
break;
case 'levels':
this.showLevelsPage(game);
break;
case 'play':
this.showGamePage(game, content);
break;
default:
this.showHomePage();
}
this.updateBreadcrumb();
},
// Retour en arrière
goBack() {
if (this.navigationHistory.length > 1) {
this.navigationHistory.pop(); // Retirer la page actuelle
const previousPage = this.navigationHistory[this.navigationHistory.length - 1];
const params = Utils.getUrlParams();
if (previousPage === 'levels') {
this.navigateTo('levels', params.game);
} else if (previousPage === 'games') {
this.navigateTo('games');
} else {
this.navigateTo('home');
}
}
},
// Affichage page d'accueil
showHomePage() {
this.hideAllPages();
document.getElementById('home-page').classList.add('active');
this.currentPage = 'home';
},
// Affichage page sélection jeux
showGamesPage() {
this.hideAllPages();
document.getElementById('games-page').classList.add('active');
this.renderGamesGrid();
this.currentPage = 'games';
},
// Affichage page sélection niveaux
showLevelsPage(gameType) {
this.hideAllPages();
document.getElementById('levels-page').classList.add('active');
this.renderLevelsGrid(gameType);
this.currentPage = 'levels';
// Mise à jour de la description
const gameInfo = this.gamesConfig?.games[gameType];
if (gameInfo) {
document.getElementById('level-description').textContent =
`Sélectionne le contenu pour jouer à ${gameInfo.name}`;
}
},
// Affichage page de jeu
async showGamePage(gameType, contentType) {
this.hideAllPages();
document.getElementById('game-page').classList.add('active');
this.currentPage = 'play';
Utils.showLoading();
try {
await GameLoader.loadGame(gameType, contentType);
} catch (error) {
console.error('Erreur chargement jeu:', error);
Utils.showToast('Erreur lors du chargement du jeu', 'error');
this.goBack();
} finally {
Utils.hideLoading();
}
},
// Masquer toutes les pages
hideAllPages() {
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
},
// Rendu grille des jeux
renderGamesGrid() {
const grid = document.getElementById('games-grid');
grid.innerHTML = '';
if (!this.gamesConfig) return;
Object.entries(this.gamesConfig.games).forEach(([key, game]) => {
if (game.enabled) {
const card = this.createGameCard(key, game);
grid.appendChild(card);
}
});
},
// Création d'une carte de jeu
createGameCard(gameKey, gameInfo) {
const card = document.createElement('div');
card.className = 'game-card';
card.innerHTML = `
<div class="icon">${gameInfo.icon}</div>
<div class="title">${gameInfo.name}</div>
<div class="description">${gameInfo.description}</div>
`;
card.addEventListener('click', () => {
Utils.animateElement(card, 'pulse');
this.navigateTo('levels', gameKey);
});
return card;
},
// Rendu grille des niveaux
async renderLevelsGrid(gameType) {
const grid = document.getElementById('levels-grid');
grid.innerHTML = '<div class="loading-content">🔍 Recherche du contenu disponible...</div>';
try {
// Obtenir tout le contenu disponible automatiquement
const availableContent = await this.contentScanner.getAvailableContent();
if (availableContent.length === 0) {
grid.innerHTML = '<div class="no-content">Aucun contenu trouvé</div>';
return;
}
// Effacer le loading
grid.innerHTML = '';
// Filtrer par compatibilité avec le jeu si possible
const compatibleContent = await this.contentScanner.getContentByGame(gameType);
const contentToShow = compatibleContent.length > 0 ? compatibleContent : availableContent;
console.log(`📋 Affichage de ${contentToShow.length} modules pour ${gameType}`);
// Créer les cartes pour chaque contenu trouvé
contentToShow.forEach(content => {
const card = this.createLevelCard(content.id, content, gameType);
grid.appendChild(card);
});
// Ajouter info de compatibilité si filtré
if (compatibleContent.length > 0 && compatibleContent.length < availableContent.length) {
const infoDiv = document.createElement('div');
infoDiv.className = 'content-info';
infoDiv.innerHTML = `
<p><em>Affichage des contenus les plus compatibles avec ${gameType}</em></p>
<button onclick="AppNavigation.showAllContent('${gameType}')" class="show-all-btn">
Voir tous les contenus (${availableContent.length})
</button>
`;
grid.appendChild(infoDiv);
}
} catch (error) {
console.error('Erreur rendu levels:', error);
grid.innerHTML = '<div class="error-content">❌ Erreur lors du chargement du contenu</div>';
}
},
// Méthode pour afficher tout le contenu
async showAllContent(gameType) {
const grid = document.getElementById('levels-grid');
grid.innerHTML = '';
const availableContent = await this.contentScanner.getAvailableContent();
availableContent.forEach(content => {
const card = this.createLevelCard(content.id, content, gameType);
grid.appendChild(card);
});
},
// Création d'une carte de niveau
createLevelCard(contentKey, contentInfo, gameType) {
const card = document.createElement('div');
card.className = 'level-card';
// Calculer les statistiques à afficher
const stats = [];
if (contentInfo.stats) {
if (contentInfo.stats.vocabularyCount > 0) {
stats.push(`📚 ${contentInfo.stats.vocabularyCount} mots`);
}
if (contentInfo.stats.sentenceCount > 0) {
stats.push(`💬 ${contentInfo.stats.sentenceCount} phrases`);
}
if (contentInfo.stats.dialogueCount > 0) {
stats.push(`🎭 ${contentInfo.stats.dialogueCount} dialogues`);
}
}
// Indicateur de compatibilité
const compatibility = contentInfo.gameCompatibility?.[gameType];
const compatScore = compatibility?.score || 0;
const compatClass = compatScore > 70 ? 'high-compat' : compatScore > 40 ? 'medium-compat' : 'low-compat';
card.innerHTML = `
<div class="card-header">
<div class="icon">${contentInfo.icon}</div>
${compatibility ? `<div class="compatibility ${compatClass}" title="Compatibilité: ${compatScore}%">
${compatScore > 70 ? '🟢' : compatScore > 40 ? '🟡' : '🟠'}
</div>` : ''}
</div>
<div class="title">${contentInfo.name}</div>
<div class="description">${contentInfo.description}</div>
<div class="content-stats">
<span class="difficulty-badge difficulty-${contentInfo.difficulty}">${contentInfo.difficulty}</span>
<span class="items-count">${contentInfo.metadata.totalItems} éléments</span>
<span class="time-estimate">~${contentInfo.metadata.estimatedTime}min</span>
</div>
${stats.length > 0 ? `<div class="detailed-stats">${stats.join(' • ')}</div>` : ''}
`;
card.addEventListener('click', () => {
Utils.animateElement(card, 'pulse');
this.navigateTo('play', gameType, contentKey);
});
return card;
},
// Mise à jour du breadcrumb
updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
breadcrumb.innerHTML = '';
const params = Utils.getUrlParams();
// Accueil
const homeItem = this.createBreadcrumbItem('🏠 Accueil', 'home',
this.currentPage === 'home');
breadcrumb.appendChild(homeItem);
// Jeux
if (['games', 'levels', 'play'].includes(this.currentPage)) {
const gamesItem = this.createBreadcrumbItem('🎮 Jeux', 'games',
this.currentPage === 'games');
breadcrumb.appendChild(gamesItem);
}
// Niveaux
if (['levels', 'play'].includes(this.currentPage) && params.game) {
const gameInfo = this.gamesConfig?.games[params.game];
const levelText = gameInfo ? `${gameInfo.icon} ${gameInfo.name}` : 'Niveaux';
const levelsItem = this.createBreadcrumbItem(levelText, 'levels',
this.currentPage === 'levels');
breadcrumb.appendChild(levelsItem);
}
// Jeu en cours
if (this.currentPage === 'play' && params.content) {
const contentInfo = this.gamesConfig?.content[params.content];
const playText = contentInfo ? `🎯 ${contentInfo.name}` : 'Jeu';
const playItem = this.createBreadcrumbItem(playText, 'play', true);
breadcrumb.appendChild(playItem);
}
},
// Création d'un élément breadcrumb
createBreadcrumbItem(text, page, isActive) {
const item = document.createElement('button');
item.className = `breadcrumb-item ${isActive ? 'active' : ''}`;
item.textContent = text;
item.dataset.page = page;
if (!isActive) {
item.addEventListener('click', () => {
const params = Utils.getUrlParams();
if (page === 'home') {
this.navigateTo('home');
} else if (page === 'games') {
this.navigateTo('games');
} else if (page === 'levels') {
this.navigateTo('levels', params.game);
}
});
}
return item;
}
};
// Fonctions globales pour l'HTML
window.navigateTo = (page, game, content) => AppNavigation.navigateTo(page, game, content);
window.goBack = () => AppNavigation.goBack();
// Export
window.AppNavigation = AppNavigation;