Class_generator/js/core/navigation.js
StillHammer 1f8688c4aa Fix WebSocket logging system and add comprehensive network features
- Fix WebSocket server to properly broadcast logs to all connected clients
- Integrate professional logging system with real-time WebSocket interface
- Add network status indicator with DigitalOcean Spaces connectivity
- Implement AWS Signature V4 authentication for private bucket access
- Add JSON content loader with backward compatibility to JS modules
- Restore navigation breadcrumb system with comprehensive logging
- Add multiple content formats: JSON + JS with automatic discovery
- Enhance top bar with logger toggle and network status indicator
- Remove deprecated temp-games module and clean up unused files

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

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

511 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === 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)
logSh('📁 Utilisation de la configuration par défaut', 'INFO');
this.gamesConfig = this.getDefaultConfig();
},
async initContentScanner() {
try {
logSh('🔍 Initialisation du scanner de contenu...', 'INFO');
this.scannedContent = await this.contentScanner.scanAllContent();
logSh(`${this.scannedContent.found.length} modules de contenu détectés automatiquement`, 'INFO');
} catch (error) {
logSh('Erreur scan contenu:', error, '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!'
},
'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) {
logSh(`🧭 Navigation vers: ${page} ${game ? `(jeu: ${game})` : ''} ${content ? `(contenu: ${content})` : ''}`, 'INFO');
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() {
logSh(`⬅️ Retour en arrière depuis: ${this.currentPage}`, 'INFO');
if (this.navigationHistory.length > 1) {
this.navigationHistory.pop(); // Retirer la page actuelle
const previousPage = this.navigationHistory[this.navigationHistory.length - 1];
logSh(`📍 Retour vers: ${previousPage}`, 'DEBUG');
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() {
logSh('🏠 Affichage page d\'accueil', 'INFO');
this.hideAllPages();
document.getElementById('home-page').classList.add('active');
this.currentPage = 'home';
this.updateBreadcrumb();
},
// Affichage page sélection jeux
showGamesPage() {
logSh('🎮 Affichage page sélection des jeux', 'INFO');
this.hideAllPages();
document.getElementById('games-page').classList.add('active');
this.renderGamesGrid();
this.currentPage = 'games';
this.updateBreadcrumb();
},
// Affichage page sélection niveaux
showLevelsPage(gameType) {
logSh(`📚 Affichage page sélection des niveaux pour: ${gameType}`, 'INFO');
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) {
logSh(`🎯 Description mise à jour: ${gameInfo.name}`, 'DEBUG');
document.getElementById('level-description').textContent =
`Sélectionne le contenu pour jouer à ${gameInfo.name}`;
}
this.updateBreadcrumb();
},
// 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);
this.updateBreadcrumb();
} catch (error) {
logSh('Erreur chargement jeu:', error, '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() {
logSh('🎲 Génération de la grille des jeux...', 'DEBUG');
const grid = document.getElementById('games-grid');
grid.innerHTML = '';
if (!this.gamesConfig) {
logSh('❌ Pas de configuration de jeux disponible', 'ERROR');
return;
}
const enabledGames = Object.entries(this.gamesConfig.games).filter(([key, game]) => game.enabled);
logSh(`🎯 ${enabledGames.length} jeux activés trouvés`, 'INFO');
enabledGames.forEach(([key, game]) => {
logSh(` Ajout de la carte: ${game.name}`, 'DEBUG');
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', () => {
logSh(`🎮 Clic sur la carte du jeu: ${gameInfo.name} (${gameKey})`, 'INFO');
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;
logSh(`📋 Affichage de ${contentToShow.length} modules pour ${gameType}`, 'INFO');
// 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) {
logSh('Erreur rendu levels:', error, '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', () => {
logSh(`📚 Clic sur la carte du contenu: ${contentInfo.name} (${contentKey}) pour le jeu ${gameType}`, 'INFO');
Utils.animateElement(card, 'pulse');
this.navigateTo('play', gameType, contentKey);
});
return card;
},
// Mise à jour du breadcrumb
updateBreadcrumb() {
logSh(`🍞 Mise à jour du breadcrumb pour page: ${this.currentPage}`, 'DEBUG');
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', () => {
logSh(`🍞 Clic sur breadcrumb: ${text}${page}`, 'INFO');
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;