- Add intelligent content-game compatibility system with visual badges - Fix Adventure Reader to work with Dragon's Pearl content structure - Implement multi-column games grid for faster navigation - Add pronunciation display for Chinese vocabulary and sentences - Fix navigation breadcrumb to show proper hierarchy (Home > Levels > Content) - Add back buttons to all navigation pages - Improve JSONContentLoader to preserve story structure - Add comprehensive debugging and diagnostic tools 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
643 lines
21 KiB
HTML
643 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>🚀 Admin: Ultra-Modular JSON System</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
color: #333;
|
|
}
|
|
|
|
.admin-header {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.admin-header h1 {
|
|
color: #4338ca;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.admin-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 0 20px;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 20px;
|
|
}
|
|
|
|
.admin-panel {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
color: #4338ca;
|
|
border-bottom: 2px solid #e5e7eb;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.file-selector {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.file-card {
|
|
background: #f8fafc;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
text-align: center;
|
|
}
|
|
|
|
.file-card:hover {
|
|
border-color: #3b82f6;
|
|
background: #eff6ff;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.file-card.active {
|
|
border-color: #10b981;
|
|
background: #ecfdf5;
|
|
}
|
|
|
|
.file-card h4 {
|
|
color: #1e293b;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.file-card p {
|
|
color: #64748b;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.validation-results {
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.validation-score {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.score-circle {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: bold;
|
|
color: white;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.score-excellent { background: #10b981; }
|
|
.score-good { background: #3b82f6; }
|
|
.score-fair { background: #f59e0b; }
|
|
.score-poor { background: #ef4444; }
|
|
|
|
.validation-section {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.validation-section h4 {
|
|
color: #374151;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.validation-list {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.validation-item {
|
|
background: white;
|
|
padding: 8px 12px;
|
|
margin-bottom: 5px;
|
|
border-radius: 6px;
|
|
border-left: 3px solid #d1d5db;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.validation-item.error {
|
|
border-left-color: #ef4444;
|
|
background: #fef2f2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.validation-item.warning {
|
|
border-left-color: #f59e0b;
|
|
background: #fffbeb;
|
|
color: #d97706;
|
|
}
|
|
|
|
.validation-item.suggestion {
|
|
border-left-color: #3b82f6;
|
|
background: #eff6ff;
|
|
color: #1d4ed8;
|
|
}
|
|
|
|
.capabilities-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 8px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.capability-badge {
|
|
background: #f1f5f9;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 6px;
|
|
padding: 6px 10px;
|
|
font-size: 0.8rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.capability-badge.active {
|
|
background: #dcfce7;
|
|
border-color: #16a34a;
|
|
color: #166534;
|
|
}
|
|
|
|
.compatibility-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.game-compatibility {
|
|
background: white;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.game-compatibility.compatible {
|
|
border-color: #16a34a;
|
|
background: #f0fdf4;
|
|
}
|
|
|
|
.game-compatibility.incompatible {
|
|
border-color: #dc2626;
|
|
background: #fef2f2;
|
|
}
|
|
|
|
.game-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.game-score {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.game-reason {
|
|
font-size: 0.8rem;
|
|
color: #6b7280;
|
|
font-style: italic;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #3b82f6;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #2563eb;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #10b981;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #059669;
|
|
}
|
|
|
|
.stats-display {
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 10px;
|
|
padding: 5px 0;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.stats-row:last-child {
|
|
border-bottom: none;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.loading {
|
|
display: none;
|
|
text-align: center;
|
|
color: #6b7280;
|
|
font-style: italic;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.admin-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="admin-header">
|
|
<h1>🚀 Admin: Ultra-Modular JSON System</h1>
|
|
<p>Validation et gestion des spécifications de contenu ultra-modulaires</p>
|
|
</div>
|
|
|
|
<div class="admin-container">
|
|
<!-- Panel de sélection et validation -->
|
|
<div class="admin-panel">
|
|
<div class="panel-header">
|
|
<span>🔍</span>
|
|
<h3>Validation de Spécifications</h3>
|
|
</div>
|
|
|
|
<h4>Fichiers JSON Disponibles:</h4>
|
|
<div class="file-selector" id="file-selector">
|
|
<div class="loading">Chargement des fichiers...</div>
|
|
</div>
|
|
|
|
<div id="validation-results" style="display: none;">
|
|
<div class="validation-results">
|
|
<div class="validation-score">
|
|
<div>
|
|
<h4>Score de Qualité</h4>
|
|
<p id="selected-file-name">Aucun fichier sélectionné</p>
|
|
</div>
|
|
<div class="score-circle" id="quality-score">-</div>
|
|
</div>
|
|
|
|
<div class="validation-section">
|
|
<h4>❌ Erreurs</h4>
|
|
<div class="validation-list" id="errors-list">
|
|
<div class="validation-item">Aucune erreur détectée</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="validation-section">
|
|
<h4>⚠️ Avertissements</h4>
|
|
<div class="validation-list" id="warnings-list">
|
|
<div class="validation-item">Aucun avertissement</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="validation-section">
|
|
<h4>💡 Suggestions</h4>
|
|
<div class="validation-list" id="suggestions-list">
|
|
<div class="validation-item">Aucune suggestion</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-buttons">
|
|
<button class="btn btn-primary" onclick="validateSelected()">🔄 Revalider</button>
|
|
<button class="btn btn-success" onclick="convertToLegacy()">🔄 Convertir vers Legacy</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Panel d'analyse des capacités -->
|
|
<div class="admin-panel">
|
|
<div class="panel-header">
|
|
<span>📊</span>
|
|
<h3>Analyse des Capacités</h3>
|
|
</div>
|
|
|
|
<div id="capabilities-section" style="display: none;">
|
|
<div class="stats-display" id="content-stats">
|
|
<h4>📈 Statistiques du Contenu</h4>
|
|
<div id="stats-content"></div>
|
|
</div>
|
|
|
|
<h4>🎯 Capacités Détectées</h4>
|
|
<div class="capabilities-grid" id="capabilities-grid"></div>
|
|
|
|
<h4>🎮 Compatibilité des Jeux</h4>
|
|
<div class="compatibility-grid" id="compatibility-grid"></div>
|
|
</div>
|
|
</div>
|
|
</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/json-content-loader.js"></script>
|
|
<script src="js/core/content-scanner.js"></script>
|
|
<script src="js/tools/ultra-modular-validator.js"></script>
|
|
|
|
<script>
|
|
class UltraModularAdmin {
|
|
constructor() {
|
|
this.validator = new UltraModularValidator();
|
|
this.selectedFile = null;
|
|
this.validationResults = null;
|
|
this.availableFiles = [
|
|
{
|
|
filename: 'english_exemple.json',
|
|
name: 'English Example (Basic)',
|
|
description: 'Format JSON de base'
|
|
},
|
|
{
|
|
filename: 'english_exemple_fixed.json',
|
|
name: 'English Example (Modular)',
|
|
description: 'Format modulaire amélioré'
|
|
},
|
|
{
|
|
filename: 'english_exemple_ultra_commented.json',
|
|
name: 'English Example (Ultra-Modular)',
|
|
description: 'Spécification ultra-modulaire complète'
|
|
}
|
|
];
|
|
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
this.renderFileSelector();
|
|
}
|
|
|
|
renderFileSelector() {
|
|
const fileSelector = document.getElementById('file-selector');
|
|
fileSelector.innerHTML = '';
|
|
|
|
this.availableFiles.forEach(file => {
|
|
const card = document.createElement('div');
|
|
card.className = 'file-card';
|
|
card.onclick = () => this.selectFile(file.filename);
|
|
card.innerHTML = `
|
|
<h4>${file.name}</h4>
|
|
<p>${file.description}</p>
|
|
`;
|
|
card.dataset.filename = file.filename;
|
|
fileSelector.appendChild(card);
|
|
});
|
|
}
|
|
|
|
async selectFile(filename) {
|
|
// Mise à jour visuelle
|
|
document.querySelectorAll('.file-card').forEach(card => {
|
|
card.classList.remove('active');
|
|
});
|
|
document.querySelector(`[data-filename="${filename}"]`).classList.add('active');
|
|
|
|
this.selectedFile = filename;
|
|
document.getElementById('selected-file-name').textContent =
|
|
this.availableFiles.find(f => f.filename === filename)?.name || filename;
|
|
|
|
// Validation automatique
|
|
await this.validateFile(filename);
|
|
}
|
|
|
|
async validateFile(filename) {
|
|
try {
|
|
document.querySelector('.loading').style.display = 'block';
|
|
|
|
const response = await fetch(filename);
|
|
const jsonContent = await response.json();
|
|
|
|
this.validationResults = await this.validator.validateSpecification(jsonContent);
|
|
|
|
this.renderValidationResults();
|
|
this.renderCapabilities();
|
|
|
|
document.getElementById('validation-results').style.display = 'block';
|
|
document.getElementById('capabilities-section').style.display = 'block';
|
|
|
|
} catch (error) {
|
|
alert(`Erreur lors du chargement: ${error.message}`);
|
|
} finally {
|
|
document.querySelector('.loading').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
renderValidationResults() {
|
|
const results = this.validationResults;
|
|
|
|
// Score de qualité
|
|
const scoreCircle = document.getElementById('quality-score');
|
|
scoreCircle.textContent = results.score;
|
|
scoreCircle.className = 'score-circle ' + this.getScoreClass(results.score);
|
|
|
|
// Erreurs
|
|
this.renderValidationList('errors-list', results.errors, 'error');
|
|
|
|
// Avertissements
|
|
this.renderValidationList('warnings-list', results.warnings, 'warning');
|
|
|
|
// Suggestions
|
|
this.renderValidationList('suggestions-list', results.suggestions, 'suggestion');
|
|
}
|
|
|
|
renderValidationList(containerId, items, type) {
|
|
const container = document.getElementById(containerId);
|
|
container.innerHTML = '';
|
|
|
|
if (items.length === 0) {
|
|
const item = document.createElement('div');
|
|
item.className = 'validation-item';
|
|
item.textContent = type === 'error' ? 'Aucune erreur détectée' :
|
|
type === 'warning' ? 'Aucun avertissement' :
|
|
'Aucune suggestion';
|
|
container.appendChild(item);
|
|
return;
|
|
}
|
|
|
|
items.forEach(text => {
|
|
const item = document.createElement('div');
|
|
item.className = `validation-item ${type}`;
|
|
item.textContent = text;
|
|
container.appendChild(item);
|
|
});
|
|
}
|
|
|
|
renderCapabilities() {
|
|
const capabilities = this.validationResults.capabilities;
|
|
const compatibility = this.validationResults.compatibility;
|
|
|
|
// Statistiques
|
|
this.renderContentStats();
|
|
|
|
// Capacités
|
|
const capabilitiesGrid = document.getElementById('capabilities-grid');
|
|
capabilitiesGrid.innerHTML = '';
|
|
|
|
Object.entries(capabilities).forEach(([key, value]) => {
|
|
const badge = document.createElement('div');
|
|
badge.className = `capability-badge ${value ? 'active' : ''}`;
|
|
|
|
let displayText = key.replace(/([A-Z])/g, ' $1').toLowerCase();
|
|
if (typeof value === 'number') {
|
|
displayText += `: ${value}`;
|
|
}
|
|
|
|
badge.textContent = displayText;
|
|
capabilitiesGrid.appendChild(badge);
|
|
});
|
|
|
|
// Compatibilité des jeux
|
|
const compatibilityGrid = document.getElementById('compatibility-grid');
|
|
compatibilityGrid.innerHTML = '';
|
|
|
|
Object.entries(compatibility).forEach(([game, compat]) => {
|
|
const gameCard = document.createElement('div');
|
|
gameCard.className = `game-compatibility ${compat.compatible ? 'compatible' : 'incompatible'}`;
|
|
gameCard.innerHTML = `
|
|
<div class="game-header">
|
|
<strong>${game}</strong>
|
|
<span class="game-score">${compat.score}%</span>
|
|
</div>
|
|
<div class="game-reason">${compat.reason}</div>
|
|
`;
|
|
compatibilityGrid.appendChild(gameCard);
|
|
});
|
|
}
|
|
|
|
renderContentStats() {
|
|
// Simuler des statistiques basées sur les capacités
|
|
const caps = this.validationResults.capabilities;
|
|
const statsContent = document.getElementById('stats-content');
|
|
|
|
const stats = [
|
|
['Profondeur vocabulaire', `${caps.vocabularyDepth}/6`],
|
|
['Richesse contenu', `${caps.contentRichness.toFixed(1)}/10`],
|
|
['Audio présent', caps.hasAudioFiles ? '✅ Oui' : '❌ Non'],
|
|
['Exercices présents', caps.hasExercises ? '✅ Oui' : '❌ Non'],
|
|
['Contenu culturel', caps.hasCulture ? '✅ Oui' : '❌ Non'],
|
|
['Format multi-langue', caps.hasMultipleLanguages ? '✅ Oui' : '❌ Non']
|
|
];
|
|
|
|
statsContent.innerHTML = stats.map(([label, value]) => `
|
|
<div class="stats-row">
|
|
<span>${label}:</span>
|
|
<strong>${value}</strong>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
getScoreClass(score) {
|
|
if (score >= 90) return 'score-excellent';
|
|
if (score >= 70) return 'score-good';
|
|
if (score >= 50) return 'score-fair';
|
|
return 'score-poor';
|
|
}
|
|
|
|
// Actions
|
|
async validateSelected() {
|
|
if (this.selectedFile) {
|
|
await this.validateFile(this.selectedFile);
|
|
}
|
|
}
|
|
|
|
async convertToLegacy() {
|
|
if (!this.selectedFile || !this.validationResults) {
|
|
alert('Veuillez d\'abord sélectionner et valider un fichier');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(this.selectedFile);
|
|
const jsonContent = await response.json();
|
|
|
|
const conversion = await this.validator.convertToLegacy(jsonContent);
|
|
|
|
// Créer et télécharger le fichier JS
|
|
const blob = new Blob([conversion.jsModule], { type: 'text/javascript' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `${jsonContent.id || 'content'}.js`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
|
|
alert('Module JavaScript généré et téléchargé avec succès !');
|
|
|
|
} catch (error) {
|
|
alert(`Erreur lors de la conversion: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialiser l'admin au chargement
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.adminApp = new UltraModularAdmin();
|
|
});
|
|
|
|
// Fonctions globales pour les boutons
|
|
function validateSelected() {
|
|
window.adminApp.validateSelected();
|
|
}
|
|
|
|
function convertToLegacy() {
|
|
window.adminApp.convertToLegacy();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |