Class_generator/admin-ultra-modular.html
StillHammer fe7153d28b Fix compatibility system and improve UX
- 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>
2025-09-18 19:29:21 +08:00

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>