training-academy/src/features/academy/academyUI.js

628 lines
24 KiB
JavaScript

/**
* Academy UI Manager
* Handles level selection, checkpoint modals, and integration with campaign/preference/library systems
*/
class AcademyUI {
constructor() {
this.currentModal = null;
this.init();
}
init() {
console.log('🎓 Academy UI Manager initialized');
}
/**
* Show level selection screen
* Displays all 30 levels with locked/unlocked states
*/
showLevelSelect() {
const unlockedLevels = window.campaignManager.getUnlockedLevels();
const completedLevels = window.gameData.academyProgress.completedLevels;
const currentLevel = window.gameData.academyProgress.currentLevel;
let html = `
<div class="level-select-screen">
<div class="level-select-header">
<h2>🎓 The Academy - Campaign</h2>
<p class="progress-subtitle">Select your next level</p>
<div class="campaign-progress">
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: ${(completedLevels.length / 30 * 100).toFixed(1)}%"></div>
</div>
<p class="progress-text">${completedLevels.length}/30 Levels Complete</p>
</div>
</div>
<div class="level-grid">
`;
// Generate 30 level buttons
for (let i = 1; i <= 30; i++) {
const isUnlocked = unlockedLevels.includes(i);
const isCompleted = completedLevels.includes(i);
const isCurrent = i === currentLevel;
const isCheckpoint = window.campaignManager.isCheckpointLevel(i);
const arc = window.campaignManager.getArcForLevel(i);
const statusClass = isCompleted ? 'completed' :
isUnlocked ? 'unlocked' : 'locked';
const icon = isCompleted ? '✓' :
isCheckpoint ? '⚡' :
isUnlocked ? '▶' : '🔒';
html += `
<button
class="level-button ${statusClass} ${isCurrent ? 'current' : ''}"
onclick="window.academyUI.selectLevel(${i})"
${!isUnlocked ? 'disabled' : ''}
title="${arc} Arc${isCheckpoint ? ' - Checkpoint' : ''}${isCompleted ? ' - Completed' : ''}">
<div class="level-number">${i}</div>
<div class="level-icon">${icon}</div>
${isCheckpoint ? '<div class="checkpoint-badge">⚡</div>' : ''}
</button>
`;
}
html += `
</div>
<div class="level-info-panel" id="level-info-panel">
<p class="info-hint">Select a level to view details</p>
</div>
<div class="level-select-footer">
<button onclick="window.location.href='index.html'" class="btn-back">🏠 Back to Menu</button>
</div>
</div>
`;
// Show in the academy setup container
const setupContainer = document.getElementById('academy-setup');
if (setupContainer) {
setupContainer.innerHTML = html;
setupContainer.style.display = 'block';
}
}
/**
* Select a level and show its details
* @param {number} levelNum - Level to select
*/
selectLevel(levelNum) {
const arc = window.campaignManager.getArcForLevel(levelNum);
const isCheckpoint = window.campaignManager.isCheckpointLevel(levelNum);
const isCompleted = window.campaignManager.isLevelCompleted(levelNum);
// Show level details in info panel
const infoPanelContent = this.getLevelInfoHTML(levelNum, arc, isCheckpoint, isCompleted);
const infoPanel = document.getElementById('level-info-panel');
if (infoPanel) {
infoPanel.innerHTML = infoPanelContent;
}
}
/**
* Get level info HTML
* @param {number} levelNum - Level number
* @param {string} arc - Arc name
* @param {boolean} isCheckpoint - Is checkpoint level
* @param {boolean} isCompleted - Is completed
* @returns {string} HTML content
*/
getLevelInfoHTML(levelNum, arc, isCheckpoint, isCompleted) {
return `
<div class="level-details">
<h3>Level ${levelNum}: ${this.getLevelTitle(levelNum)}</h3>
<div class="level-meta">
<span class="arc-badge">${arc} Arc</span>
${isCheckpoint ? '<span class="checkpoint-badge-large">⚡ Checkpoint</span>' : ''}
${isCompleted ? '<span class="completed-badge">✓ Completed</span>' : ''}
</div>
<p class="level-description">${this.getLevelDescription(levelNum)}</p>
${isCheckpoint ? '<p class="checkpoint-notice">💡 You will be asked to update your preferences before starting this level.</p>' : ''}
<button onclick="window.academyUI.startLevel(${levelNum})" class="btn-start-level">
${isCompleted ? '🔄 Replay Level' : '▶️ Start Level'}
</button>
</div>
`;
}
/**
* Get level title based on number
* @param {number} levelNum - Level number
* @returns {string} Level title
*/
getLevelTitle(levelNum) {
const titles = {
1: "First Edge",
5: "Foundation Complete",
10: "Feature Discovery Complete",
15: "Deepening",
20: "Mastery",
25: "Final Challenge",
30: "Graduation"
};
return titles[levelNum] || `Training Session ${levelNum}`;
}
/**
* Get level description
* @param {number} levelNum - Level number
* @returns {string} Level description
*/
getLevelDescription(levelNum) {
if (levelNum === 1) return "Welcome to The Academy. Begin your journey with basic edging training.";
if (levelNum === 5) return "Complete the Foundation arc. Test your progress with a checkpoint session.";
if (levelNum === 10) return "Feature Discovery complete. Time to optimize your preferences.";
if (levelNum === 15) return "Halfway through. Deepen your training with advanced techniques.";
if (levelNum === 20) return "Enter the Mastery arc. Push your limits further.";
if (levelNum === 25) return "Final checkpoint before graduation. Prepare for the ultimate test.";
if (levelNum === 30) return "The final test. Graduate from The Academy.";
return "Continue your training journey with this session.";
}
/**
* Start a level (with checkpoint modal if needed)
* @param {number} levelNum - Level to start
*/
startLevel(levelNum) {
console.log(`🎯 Starting Level ${levelNum}...`);
// Check if this is a checkpoint level
if (window.campaignManager.isCheckpointLevel(levelNum)) {
this.showCheckpointModal(levelNum);
} else {
this.beginLevel(levelNum);
}
}
/**
* Show checkpoint preference modal
* @param {number} levelNum - Checkpoint level number
*/
showCheckpointModal(levelNum) {
const config = window.preferenceManager.getCheckpointModalConfig(levelNum);
if (!config) {
console.error('No checkpoint config for level', levelNum);
this.beginLevel(levelNum);
return;
}
const modalHTML = this.buildCheckpointModalHTML(levelNum, config);
// Create modal overlay
const overlay = document.createElement('div');
overlay.className = 'checkpoint-modal-overlay';
overlay.innerHTML = modalHTML;
document.body.appendChild(overlay);
this.currentModal = overlay;
// Initialize tabs
this.initCheckpointModalTabs(levelNum, config);
}
/**
* Build checkpoint modal HTML
* @param {number} levelNum - Level number
* @param {Object} config - Modal configuration
* @returns {string} Modal HTML
*/
buildCheckpointModalHTML(levelNum, config) {
return `
<div class="checkpoint-modal">
<div class="checkpoint-header">
<h2>${config.title}</h2>
<p>${config.description}</p>
</div>
<div class="checkpoint-tabs">
<button class="tab-btn active" data-tab="preferences">🎯 Preferences</button>
<button class="tab-btn" data-tab="library">📚 Library</button>
</div>
<div class="checkpoint-content">
<div class="tab-panel active" id="preferences-panel">
<div id="preferences-editor"></div>
</div>
<div class="tab-panel" id="library-panel">
<div id="library-manager"></div>
</div>
</div>
<div class="checkpoint-footer">
<button onclick="window.academyUI.closeCheckpointModal()" class="btn-secondary">
Skip for Now
</button>
<button onclick="window.academyUI.saveCheckpointAndStart(${levelNum})" class="btn-primary">
Save & Start Level
</button>
</div>
</div>
`;
}
/**
* Initialize checkpoint modal tabs
* @param {number} levelNum - Level number
* @param {Object} config - Modal configuration
*/
initCheckpointModalTabs(levelNum, config) {
// Tab switching
const tabBtns = document.querySelectorAll('.tab-btn');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const tabName = btn.dataset.tab;
// Update active states
tabBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.tab-panel').forEach(panel => {
panel.classList.remove('active');
});
document.getElementById(`${tabName}-panel`).classList.add('active');
});
});
// Populate preferences editor
this.populatePreferencesEditor(config.categories);
// Populate library manager
this.populateLibraryManager();
}
/**
* Populate preferences editor
* @param {Array<string>} categories - Categories to show
*/
populatePreferencesEditor(categories) {
const container = document.getElementById('preferences-editor');
if (!container) return;
let html = '';
categories.forEach(category => {
const prefs = window.preferenceManager.getCategoryPreferences(category);
html += `<div class="pref-category">`;
html += `<h3>${this.getCategoryDisplayName(category)}</h3>`;
html += this.buildCategoryEditor(category, prefs);
html += `</div>`;
});
container.innerHTML = html;
}
/**
* Get category display name
* @param {string} category - Category key
* @returns {string} Display name
*/
getCategoryDisplayName(category) {
const names = {
contentThemes: '🎭 Content Themes',
visualPreferences: '👁️ Visual Preferences',
intensity: '⚡ Intensity Levels',
captionTone: '💬 Caption Tone',
audioPreferences: '🔊 Audio Preferences',
sessionDuration: '⏱️ Session Duration',
featurePreferences: '🎮 Feature Preferences',
boundaries: '🛡️ Boundaries'
};
return names[category] || category;
}
/**
* Build category editor HTML
* @param {string} category - Category name
* @param {Object} prefs - Preferences object
* @returns {string} Editor HTML
*/
buildCategoryEditor(category, prefs) {
if (category === 'intensity') {
return this.buildIntensitySliders(prefs);
} else if (category === 'sessionDuration') {
return this.buildDurationSelector(prefs);
} else {
return this.buildCheckboxes(category, prefs);
}
}
/**
* Build intensity sliders
* @param {Object} prefs - Intensity preferences
* @returns {string} HTML
*/
buildIntensitySliders(prefs) {
let html = '';
for (const [key, value] of Object.entries(prefs)) {
html += `
<div class="slider-group">
<label>${key.replace(/([A-Z])/g, ' $1').trim()}: <span id="${key}-value">${value}</span></label>
<input type="range" min="1" max="5" value="${value}"
data-category="intensity" data-key="${key}"
oninput="document.getElementById('${key}-value').textContent = this.value">
</div>
`;
}
return html;
}
/**
* Build duration selector
* @param {Object} prefs - Duration preferences
* @returns {string} HTML
*/
buildDurationSelector(prefs) {
const options = ['short', 'medium', 'long', 'marathon'];
let html = '<select data-category="sessionDuration" data-key="preferred">';
options.forEach(opt => {
html += `<option value="${opt}" ${prefs.preferred === opt ? 'selected' : ''}>${opt}</option>`;
});
html += '</select>';
return html;
}
/**
* Build checkboxes for category
* @param {string} category - Category name
* @param {Object} prefs - Preferences object
* @returns {string} HTML
*/
buildCheckboxes(category, prefs) {
let html = '<div class="checkbox-grid">';
for (const [key, value] of Object.entries(prefs)) {
if (value !== null) { // Skip undiscovered features
html += `
<label class="checkbox-label">
<input type="checkbox" ${value ? 'checked' : ''}
data-category="${category}" data-key="${key}">
${key}
</label>
`;
}
}
html += '</div>';
return html;
}
/**
* Populate library manager
*/
populateLibraryManager() {
const container = document.getElementById('library-manager');
if (!container) return;
// Sync with existing library first
const syncResult = window.libraryManager.syncWithExistingLibrary();
const stats = window.libraryManager.getLibraryStats();
container.innerHTML = `
<div class="library-stats">
<h3>📊 Library Statistics</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">Total Videos</span>
<span class="stat-value">${stats.totalItems}</span>
</div>
<div class="stat-item">
<span class="stat-label">Tagged</span>
<span class="stat-value">${stats.taggedItems}</span>
</div>
<div class="stat-item">
<span class="stat-label">Avg Rating</span>
<span class="stat-value">${stats.averageRating}⭐</span>
</div>
<div class="stat-item">
<span class="stat-label">Favorites</span>
<span class="stat-value">${stats.totalFavorites}</span>
</div>
</div>
<p class="library-hint">
💡 Your existing video library (${syncResult.total} videos) is automatically imported.
You can add tags and ratings to organize your content.
</p>
<p class="library-hint">
📚 Manage your full library from the main menu Library tab for more detailed controls.
</p>
</div>
`;
}
/**
* Save checkpoint preferences and start level
* @param {number} levelNum - Level to start
*/
saveCheckpointAndStart(levelNum) {
console.log('💾 Saving checkpoint preferences...');
// Collect all preference changes
const updates = {};
// Get all checkboxes
document.querySelectorAll('input[type="checkbox"][data-category]').forEach(checkbox => {
const category = checkbox.dataset.category;
const key = checkbox.dataset.key;
if (!updates[category]) updates[category] = {};
updates[category][key] = checkbox.checked;
});
// Get all sliders
document.querySelectorAll('input[type="range"][data-category]').forEach(slider => {
const category = slider.dataset.category;
const key = slider.dataset.key;
if (!updates[category]) updates[category] = {};
updates[category][key] = parseInt(slider.value);
});
// Get all selects
document.querySelectorAll('select[data-category]').forEach(select => {
const category = select.dataset.category;
const key = select.dataset.key;
if (!updates[category]) updates[category] = {};
updates[category][key] = select.value;
});
// Save to preferenceManager
let saved = true;
if (Object.keys(updates).length > 0) {
saved = window.preferenceManager.updateMultipleCategories(updates, levelNum);
if (saved) {
console.log('✅ Preferences updated:', updates);
} else {
console.error('❌ Failed to save preferences');
alert('⚠️ Unable to save preferences. Storage may be full.\n\nPlease restart the app and try again.');
return;
}
}
// Close modal and begin level
this.closeCheckpointModal();
this.beginLevel(levelNum);
}
/**
* Close checkpoint modal
*/
closeCheckpointModal() {
if (this.currentModal) {
this.currentModal.remove();
this.currentModal = null;
}
}
/**
* Begin level (after checkpoint or directly)
* @param {number} levelNum - Level to start
*/
beginLevel(levelNum) {
console.log(`🚀 Beginning Level ${levelNum}...`);
// Use campaignManager to start level
const levelConfig = window.campaignManager.startLevel(levelNum);
if (!levelConfig) {
alert('Unable to start this level. Please check requirements.');
return;
}
// Hide level select screen
const setupContainer = document.getElementById('academy-setup');
if (setupContainer) {
setupContainer.style.display = 'none';
}
// Start the actual training session
// This would integrate with existing training-academy.html session logic
this.startTrainingSession(levelConfig);
}
/**
* Start training session with level config
* @param {Object} levelConfig - Level configuration
*/
startTrainingSession(levelConfig) {
console.log('🎓 Starting training session with config:', levelConfig);
// Get detailed level configuration from academyLevelData
const detailedConfig = window.academyLevelData.getLevelConfig(levelConfig.levelNumber);
if (!detailedConfig) {
console.error('⚠️ No level data found for level:', levelConfig.levelNumber);
return;
}
console.log('📊 Detailed level config:', detailedConfig);
// Get content filter based on user preferences
const preferenceFilter = window.preferenceManager.getContentFilter();
// Sync with existing library to ensure we have latest videos
window.libraryManager.syncWithExistingLibrary();
// Get media requirements for this level
const mediaRequirements = window.academyLevelData.getMediaForLevel(
levelConfig.levelNumber,
window.preferenceManager.getPreferences()
);
console.log('🎬 Media requirements:', mediaRequirements);
// Get filtered media from library
let mediaItems = window.libraryManager.getMediaForPreferences(
mediaRequirements.videoFilter,
mediaRequirements.videoCount
);
// Fallback to all videos if no matches
if (mediaItems.length === 0) {
console.warn('⚠️ No media items matched preferences. Using all available videos.');
mediaItems = window.libraryManager.getAllVideosWithMetadata();
}
console.log('🎬 Loaded media for session:', mediaItems.length, 'items');
console.log('🎯 Preference filter:', preferenceFilter);
console.log('⏱️ Session duration:', detailedConfig.session.duration);
console.log('📋 Tasks:', detailedConfig.tasks.length);
console.log('🎯 Objectives:', detailedConfig.objectives);
// Sync videos to videoPlayerManager for video playback
if (window.videoPlayerManager && mediaItems.length > 0) {
const videoPaths = mediaItems.map(item => item.path || item.fullPath);
window.videoPlayerManager.videoLibrary.background = videoPaths;
window.videoPlayerManager.videoLibrary.task = videoPaths;
window.videoPlayerManager.videoLibrary.reward = videoPaths;
window.videoPlayerManager.videoLibrary.punishment = videoPaths;
console.log(`🔄 Synced ${videoPaths.length} videos to videoPlayerManager for Level ${levelConfig.levelNumber}`);
}
// Update trainingVideoLibrary global for training-academy.html
if (mediaItems.length > 0) {
window.trainingVideoLibrary = mediaItems;
console.log(`📹 Set trainingVideoLibrary with ${mediaItems.length} videos`);
}
// Enable video if this level requires it (has video media requirements)
if (detailedConfig.media && detailedConfig.media.videos && detailedConfig.media.videos.required > 0) {
// Load and update settings to enable video
const settings = typeof window.loadAcademySettings === 'function'
? window.loadAcademySettings()
: { enableVideo: false };
if (!settings.enableVideo) {
console.log('🎬 Level requires video - enabling video in settings');
settings.enableVideo = true;
if (typeof window.saveAcademySettings === 'function') {
window.saveAcademySettings(settings);
}
}
}
// Set the selected training mode to trigger proper training flow
window.selectedTrainingMode = 'training-academy';
// Store current level info globally for access during session
window.currentAcademyLevel = {
levelNumber: levelConfig.levelNumber,
config: detailedConfig,
mediaRequirements: mediaRequirements
};
// Call the existing training session starter
if (typeof window.startTrainingSession === 'function') {
window.startTrainingSession();
} else {
console.error('⚠️ startTrainingSession() not found in window scope');
}
}
}
// Create global instance
window.academyUI = new AcademyUI();