Class_generator/js/core/json-content-loader.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

391 lines
12 KiB
JavaScript

// === JSON CONTENT LOADER ===
// Transforme les contenus JSON en format compatible avec les jeux existants
class JSONContentLoader {
constructor() {
this.loadedContent = new Map();
}
/**
* Charge et adapte un contenu JSON pour les jeux
* @param {Object} jsonContent - Le contenu JSON brut
* @returns {Object} - Contenu adapté au format legacy
*/
adapt(jsonContent) {
if (!jsonContent) {
logSh('⚠️ JSONContentLoader - Contenu vide reçu', 'WARN');
return this.createEmptyContent();
}
logSh(`🔄 JSONContentLoader - Adaptation du contenu: ${jsonContent.name || 'Sans nom'}`, 'INFO');
// Créer l'objet de base au format legacy
const adaptedContent = {
// Métadonnées (préservées mais adaptées)
name: jsonContent.name || 'Contenu Sans Nom',
description: jsonContent.description || '',
difficulty: jsonContent.difficulty || 'medium',
language: jsonContent.language || 'english',
icon: jsonContent.icon || '📝',
// === VOCABULAIRE ===
vocabulary: this.adaptVocabulary(jsonContent.vocabulary),
// === PHRASES ET SENTENCES ===
sentences: this.adaptSentences(jsonContent.sentences),
// === TEXTES ===
texts: this.adaptTexts(jsonContent.texts),
// === DIALOGUES ===
dialogues: this.adaptDialogues(jsonContent.dialogues),
// === GRAMMAIRE ===
grammar: this.adaptGrammar(jsonContent.grammar),
// === AUDIO ===
audio: this.adaptAudio(jsonContent.audio),
// === POÈMES ET CULTURE ===
poems: this.adaptPoems(jsonContent.poems),
// === EXERCICES AVANCÉS ===
fillInBlanks: this.adaptFillInBlanks(jsonContent.fillInBlanks),
corrections: this.adaptCorrections(jsonContent.corrections),
comprehension: this.adaptComprehension(jsonContent.comprehension),
matching: this.adaptMatching(jsonContent.matching),
// === EXERCICES GÉNÉRIQUES ===
exercises: this.adaptExercises(jsonContent.exercises),
// === LISTENING (format SBS) ===
listening: this.adaptListening(jsonContent.listening),
// === MÉTADONNÉES DE COMPATIBILITÉ ===
_adapted: true,
_source: 'json',
_adaptedAt: new Date().toISOString()
};
// Nettoyage des valeurs undefined
this.cleanUndefinedValues(adaptedContent);
logSh(`✅ JSONContentLoader - Contenu adapté avec succès`, 'INFO');
logSh(`📊 Stats: ${Object.keys(adaptedContent.vocabulary || {}).length} mots, ${(adaptedContent.sentences || []).length} phrases`, 'INFO');
return adaptedContent;
}
/**
* Adapte le vocabulaire (format JSON → Legacy)
*/
adaptVocabulary(vocabulary) {
if (!vocabulary || typeof vocabulary !== 'object') {
return {};
}
const adapted = {};
for (const [word, definition] of Object.entries(vocabulary)) {
if (typeof definition === 'string') {
// Format simple: "cat": "chat"
adapted[word] = definition;
} else if (typeof definition === 'object') {
// Format enrichi: "cat": { translation: "chat", type: "noun", ... }
adapted[word] = {
translation: definition.translation || definition.french || definition.chinese || '',
english: word,
type: definition.type || 'word',
pronunciation: definition.pronunciation || definition.prononciation,
difficulty: definition.difficulty,
examples: definition.examples,
grammarNotes: definition.grammarNotes,
// Compatibilité avec anciens formats
french: definition.french || definition.translation,
chinese: definition.chinese || definition.translation,
image: definition.image,
audio: definition.audio || definition.pronunciation
};
}
}
return adapted;
}
/**
* Adapte les phrases
*/
adaptSentences(sentences) {
if (!Array.isArray(sentences)) {
return [];
}
return sentences.map(sentence => {
if (typeof sentence === 'string') {
return { english: sentence, translation: '' };
}
return {
english: sentence.english || '',
french: sentence.french || sentence.translation,
chinese: sentence.chinese || sentence.translation,
translation: sentence.translation || sentence.french || sentence.chinese,
prononciation: sentence.prononciation || sentence.pinyin,
audio: sentence.audio,
difficulty: sentence.difficulty
};
});
}
/**
* Adapte les textes
*/
adaptTexts(texts) {
if (!Array.isArray(texts)) {
return [];
}
return texts.map(text => ({
title: text.title || 'Sans titre',
content: text.content || '',
translation: text.translation || '',
french: text.french || text.translation,
chinese: text.chinese || text.translation,
audio: text.audio,
difficulty: text.difficulty
}));
}
/**
* Adapte les dialogues
*/
adaptDialogues(dialogues) {
if (!Array.isArray(dialogues)) {
return [];
}
return dialogues.map(dialogue => ({
title: dialogue.title || 'Dialogue',
conversation: Array.isArray(dialogue.conversation)
? dialogue.conversation.map(line => ({
speaker: line.speaker || 'Speaker',
english: line.english || '',
french: line.french || line.translation,
chinese: line.chinese || line.translation,
translation: line.translation || line.french || line.chinese,
prononciation: line.prononciation || line.pinyin,
audio: line.audio
}))
: []
}));
}
/**
* Adapte la grammaire
*/
adaptGrammar(grammar) {
if (!grammar || typeof grammar !== 'object') {
return {};
}
// La grammaire reste en format objet (compatible)
return grammar;
}
/**
* Adapte le contenu audio
*/
adaptAudio(audio) {
if (!audio || typeof audio !== 'object') {
return {};
}
return {
withText: Array.isArray(audio.withText) ? audio.withText : [],
withoutText: Array.isArray(audio.withoutText) ? audio.withoutText : []
};
}
/**
* Adapte les poèmes
*/
adaptPoems(poems) {
if (!Array.isArray(poems)) {
return [];
}
return poems.map(poem => ({
title: poem.title || 'Poème',
content: poem.content || '',
translation: poem.translation || '',
audioFile: poem.audioFile,
culturalContext: poem.culturalContext
}));
}
/**
* Adapte les exercices Fill-in-the-Blanks
*/
adaptFillInBlanks(fillInBlanks) {
if (!Array.isArray(fillInBlanks)) {
return [];
}
return fillInBlanks;
}
/**
* Adapte les exercices de correction
*/
adaptCorrections(corrections) {
if (!Array.isArray(corrections)) {
return [];
}
return corrections;
}
/**
* Adapte les exercices de compréhension
*/
adaptComprehension(comprehension) {
if (!Array.isArray(comprehension)) {
return [];
}
return comprehension;
}
/**
* Adapte les exercices de matching
*/
adaptMatching(matching) {
if (!Array.isArray(matching)) {
return [];
}
return matching;
}
/**
* Adapte les exercices génériques
*/
adaptExercises(exercises) {
if (!exercises || typeof exercises !== 'object') {
return {};
}
return exercises;
}
/**
* Adapte le contenu listening (format SBS)
*/
adaptListening(listening) {
if (!listening || typeof listening !== 'object') {
return {};
}
return listening;
}
/**
* Nettoie les valeurs undefined de l'objet
*/
cleanUndefinedValues(obj) {
Object.keys(obj).forEach(key => {
if (obj[key] === undefined) {
delete obj[key];
} else if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
this.cleanUndefinedValues(obj[key]);
}
});
}
/**
* Crée un contenu vide par défaut
*/
createEmptyContent() {
return {
name: 'Contenu Vide',
description: 'Aucun contenu disponible',
difficulty: 'easy',
vocabulary: {},
sentences: [],
texts: [],
dialogues: [],
grammar: {},
exercises: {},
_adapted: true,
_source: 'empty',
_adaptedAt: new Date().toISOString()
};
}
/**
* Analyse la richesse du contenu et génère des statistiques
*/
analyzeContent(adaptedContent) {
const stats = {
vocabularyCount: Object.keys(adaptedContent.vocabulary || {}).length,
sentenceCount: (adaptedContent.sentences || []).length,
textCount: (adaptedContent.texts || []).length,
dialogueCount: (adaptedContent.dialogues || []).length,
grammarTopics: Object.keys(adaptedContent.grammar || {}).length,
audioContent: !!(adaptedContent.audio && (adaptedContent.audio.withText || adaptedContent.audio.withoutText)),
poemCount: (adaptedContent.poems || []).length,
exerciseTypes: Object.keys(adaptedContent.exercises || {}).length,
totalItems: 0
};
stats.totalItems = stats.vocabularyCount + stats.sentenceCount +
stats.textCount + stats.dialogueCount + stats.poemCount;
stats.richness = stats.totalItems > 50 ? 'rich' :
stats.totalItems > 20 ? 'medium' : 'basic';
return stats;
}
/**
* Teste si un objet est au format JSON (vs legacy JS)
*/
isJSONFormat(content) {
return content && (
content.hasOwnProperty('name') ||
content.hasOwnProperty('description') ||
content.hasOwnProperty('language') ||
content._adapted === true
);
}
/**
* Point d'entrée principal - décide s'il faut adapter ou pas
*/
loadContent(content) {
if (!content) {
return this.createEmptyContent();
}
// Si déjà adapté, retourner tel quel
if (content._adapted === true) {
return content;
}
// Si format JSON, adapter
if (this.isJSONFormat(content)) {
return this.adapt(content);
}
// Sinon, c'est du format legacy, retourner tel quel
return content;
}
}
// Export global
window.JSONContentLoader = JSONContentLoader;
// Export Node.js (optionnel)
if (typeof module !== 'undefined' && module.exports) {
module.exports = JSONContentLoader;
}