Implement Phase 2: Complete Audio Management UI
Major audio management interface implementation: HTML Structure (index.html): - Added complete audio-management-screen after image management - Audio import buttons for background/ambient/effects categories - Audio gallery with tabbed interface (background, ambient, effects) - Audio preview section with HTML5 audio player - Gallery controls: select all, deselect all, delete, preview - Manage Audio button added to main start screen - Professional layout matching image management design CSS Styling (styles.css): - Complete audio management styling system - Audio gallery grid layout with 280px cards - Audio item cards with icons, titles, controls - Tabbed interface styling with active states - Audio preview section with integrated player - Category-specific icons ( background, ambient, effects) - Hover effects, selection states, and responsive design - Consistent with image management visual patterns JavaScript Functionality (game.js): - showAudioManagement() - main screen initialization - setupAudioManagementEventListeners() - comprehensive event handling - loadAudioGallery() - populate all three category galleries - Audio tab switching (background/ambient/effects) - Audio selection/deselection with click handlers - Audio preview system with HTML5 player integration - Delete functionality for selected audio files - Enable/disable audio file toggles - Audio storage info modal with desktop/web mode details - Complete event listener management with duplicate prevention - Audio discovery initialization in constructor Features: - Three-category audio organization (background, ambient, effects) - Click-to-select audio items with visual feedback - Preview selected audio files with integrated player - Bulk operations: select all, deselect all, delete selected - Enable/disable individual audio files - Desktop file management integration - Automatic audio directory scanning - Storage information with category breakdowns - Professional gallery layout with metadata display Ready for Phase 3: Integration with MusicManager for playlist functionality
This commit is contained in:
parent
c67d4dca27
commit
9c8876b89f
447
game.js
447
game.js
|
|
@ -27,6 +27,8 @@ class TaskChallengeGame {
|
|||
this.timerInterval = null;
|
||||
this.imageDiscoveryComplete = false;
|
||||
this.imageManagementListenersAttached = false;
|
||||
this.audioDiscoveryComplete = false;
|
||||
this.audioManagementListenersAttached = false;
|
||||
this.musicManager = new MusicManager(this.dataManager);
|
||||
this.initializeEventListeners();
|
||||
this.setupKeyboardShortcuts();
|
||||
|
|
@ -34,6 +36,7 @@ class TaskChallengeGame {
|
|||
this.discoverImages().then(() => {
|
||||
this.showScreen('start-screen');
|
||||
});
|
||||
this.discoverAudio();
|
||||
|
||||
// Check for auto-resume after initialization
|
||||
this.checkAutoResume();
|
||||
|
|
@ -297,6 +300,29 @@ class TaskChallengeGame {
|
|||
return `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMGYwIiBzdHJva2U9IiNjY2MiIHN0cm9rZS13aWR0aD0iMiIvPjx0ZXh0IHg9IjUwJSIgeT0iNDAlIiBmb250LXNpemU9IjE2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSIgZmlsbD0iIzY2NiI+JHtsYWJlbH08L3RleHQ+PHRleHQgeD0iNTAlIiB5PSI2MCUiIGZvbnQtc2l6ZT0iMTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmaWxsPSIjOTk5Ij5BZGQgaW1hZ2VzIHRvIGZvbGRlcjwvdGV4dD48L3N2Zz4=`;
|
||||
}
|
||||
|
||||
// Audio Discovery Functions
|
||||
async discoverAudio() {
|
||||
try {
|
||||
console.log('Discovering audio files...');
|
||||
|
||||
// Initialize audio discovery - scan directories if desktop mode
|
||||
if (this.fileManager) {
|
||||
await this.fileManager.scanDirectoryForAudio('background');
|
||||
await this.fileManager.scanDirectoryForAudio('ambient');
|
||||
await this.fileManager.scanDirectoryForAudio('effects');
|
||||
console.log('Desktop audio discovery completed');
|
||||
} else {
|
||||
console.log('Web mode - audio discovery skipped');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log('Audio discovery failed:', error);
|
||||
}
|
||||
|
||||
this.audioDiscoveryComplete = true;
|
||||
console.log('Audio discovery completed');
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
// Screen navigation
|
||||
document.getElementById('start-btn').addEventListener('click', () => this.startGame());
|
||||
|
|
@ -346,6 +372,9 @@ class TaskChallengeGame {
|
|||
// Image management - only the main button, others will be attached when screen is shown
|
||||
document.getElementById('manage-images-btn').addEventListener('click', () => this.showImageManagement());
|
||||
|
||||
// Audio management - only the main button, others will be attached when screen is shown
|
||||
document.getElementById('manage-audio-btn').addEventListener('click', () => this.showAudioManagement());
|
||||
|
||||
// Load saved theme
|
||||
this.loadSavedTheme();
|
||||
}
|
||||
|
|
@ -1765,6 +1794,424 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
return `${baseMessage}: ${specificMessage}`;
|
||||
}
|
||||
|
||||
// Audio Management Functions
|
||||
showAudioManagement() {
|
||||
// Reset listener flag to allow fresh attachment
|
||||
this.audioManagementListenersAttached = false;
|
||||
|
||||
this.showScreen('audio-management-screen');
|
||||
this.setupAudioManagementEventListeners();
|
||||
|
||||
// Wait for audio discovery to complete before loading gallery
|
||||
if (!this.audioDiscoveryComplete) {
|
||||
const galleries = document.querySelectorAll('.audio-gallery');
|
||||
galleries.forEach(gallery => {
|
||||
gallery.innerHTML = '<div class="loading">Discovering audio files...</div>';
|
||||
});
|
||||
|
||||
// Wait and try again
|
||||
setTimeout(() => {
|
||||
if (this.audioDiscoveryComplete) {
|
||||
this.loadAudioGallery();
|
||||
} else {
|
||||
galleries.forEach(gallery => {
|
||||
gallery.innerHTML = '<div class="loading">Still discovering audio... Please wait</div>';
|
||||
});
|
||||
setTimeout(() => this.loadAudioGallery(), 1000);
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
this.loadAudioGallery();
|
||||
}
|
||||
}
|
||||
|
||||
switchAudioTab(tabType) {
|
||||
// Update tab buttons
|
||||
const backgroundTab = document.getElementById('background-audio-tab');
|
||||
const ambientTab = document.getElementById('ambient-audio-tab');
|
||||
const effectsTab = document.getElementById('effects-audio-tab');
|
||||
const backgroundGallery = document.getElementById('background-audio-gallery');
|
||||
const ambientGallery = document.getElementById('ambient-audio-gallery');
|
||||
const effectsGallery = document.getElementById('effects-audio-gallery');
|
||||
|
||||
// Remove active class from all tabs and galleries
|
||||
[backgroundTab, ambientTab, effectsTab].forEach(tab => tab && tab.classList.remove('active'));
|
||||
[backgroundGallery, ambientGallery, effectsGallery].forEach(gallery => gallery && gallery.classList.remove('active'));
|
||||
|
||||
// Add active class to selected tab and gallery
|
||||
if (tabType === 'background') {
|
||||
backgroundTab && backgroundTab.classList.add('active');
|
||||
backgroundGallery && backgroundGallery.classList.add('active');
|
||||
} else if (tabType === 'ambient') {
|
||||
ambientTab && ambientTab.classList.add('active');
|
||||
ambientGallery && ambientGallery.classList.add('active');
|
||||
} else if (tabType === 'effects') {
|
||||
effectsTab && effectsTab.classList.add('active');
|
||||
effectsGallery && effectsGallery.classList.add('active');
|
||||
}
|
||||
|
||||
// Update gallery controls to work with current tab
|
||||
this.updateAudioGalleryControls(tabType);
|
||||
}
|
||||
|
||||
setupAudioManagementEventListeners() {
|
||||
// Check if we already have listeners attached to prevent duplicates
|
||||
if (this.audioManagementListenersAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Back button
|
||||
const backBtn = document.getElementById('back-to-start-from-audio-btn');
|
||||
if (backBtn) {
|
||||
backBtn.onclick = () => this.showScreen('start-screen');
|
||||
}
|
||||
|
||||
// Desktop import buttons
|
||||
const importBackgroundBtn = document.getElementById('import-background-audio-btn');
|
||||
if (importBackgroundBtn) {
|
||||
importBackgroundBtn.onclick = async () => {
|
||||
if (this.fileManager) {
|
||||
await this.fileManager.selectAndImportAudio('background');
|
||||
this.loadAudioGallery(); // Refresh the gallery to show new audio
|
||||
} else {
|
||||
this.showNotification('Desktop file manager not available', 'warning');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const importAmbientBtn = document.getElementById('import-ambient-audio-btn');
|
||||
if (importAmbientBtn) {
|
||||
importAmbientBtn.onclick = async () => {
|
||||
if (this.fileManager) {
|
||||
await this.fileManager.selectAndImportAudio('ambient');
|
||||
this.loadAudioGallery(); // Refresh the gallery to show new audio
|
||||
} else {
|
||||
this.showNotification('Desktop file manager not available', 'warning');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const importEffectsBtn = document.getElementById('import-effects-audio-btn');
|
||||
if (importEffectsBtn) {
|
||||
importEffectsBtn.onclick = async () => {
|
||||
if (this.fileManager) {
|
||||
await this.fileManager.selectAndImportAudio('effects');
|
||||
this.loadAudioGallery(); // Refresh the gallery to show new audio
|
||||
} else {
|
||||
this.showNotification('Desktop file manager not available', 'warning');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Audio storage info button
|
||||
const audioStorageInfoBtn = document.getElementById('audio-storage-info-btn');
|
||||
if (audioStorageInfoBtn) {
|
||||
audioStorageInfoBtn.onclick = () => this.showAudioStorageInfo();
|
||||
}
|
||||
|
||||
// Tab buttons
|
||||
const backgroundAudioTab = document.getElementById('background-audio-tab');
|
||||
if (backgroundAudioTab) {
|
||||
backgroundAudioTab.onclick = () => this.switchAudioTab('background');
|
||||
}
|
||||
|
||||
const ambientAudioTab = document.getElementById('ambient-audio-tab');
|
||||
if (ambientAudioTab) {
|
||||
ambientAudioTab.onclick = () => this.switchAudioTab('ambient');
|
||||
}
|
||||
|
||||
const effectsAudioTab = document.getElementById('effects-audio-tab');
|
||||
if (effectsAudioTab) {
|
||||
effectsAudioTab.onclick = () => this.switchAudioTab('effects');
|
||||
}
|
||||
|
||||
// Gallery control buttons - assign onclick directly to avoid expensive DOM operations
|
||||
const selectAllAudioBtn = document.getElementById('select-all-audio-btn');
|
||||
if (selectAllAudioBtn) {
|
||||
selectAllAudioBtn.onclick = () => this.selectAllAudio();
|
||||
}
|
||||
|
||||
const deselectAllAudioBtn = document.getElementById('deselect-all-audio-btn');
|
||||
if (deselectAllAudioBtn) {
|
||||
deselectAllAudioBtn.onclick = () => this.deselectAllAudio();
|
||||
}
|
||||
|
||||
const deleteSelectedAudioBtn = document.getElementById('delete-selected-audio-btn');
|
||||
if (deleteSelectedAudioBtn) {
|
||||
deleteSelectedAudioBtn.onclick = () => this.deleteSelectedAudio();
|
||||
}
|
||||
|
||||
const previewSelectedAudioBtn = document.getElementById('preview-selected-audio-btn');
|
||||
if (previewSelectedAudioBtn) {
|
||||
previewSelectedAudioBtn.onclick = () => this.previewSelectedAudio();
|
||||
}
|
||||
|
||||
// Close preview button
|
||||
const closePreviewBtn = document.getElementById('close-preview-btn');
|
||||
if (closePreviewBtn) {
|
||||
closePreviewBtn.onclick = () => this.closeAudioPreview();
|
||||
}
|
||||
|
||||
// Mark listeners as attached
|
||||
this.audioManagementListenersAttached = true;
|
||||
}
|
||||
|
||||
loadAudioGallery() {
|
||||
const backgroundGallery = document.getElementById('background-audio-gallery');
|
||||
const ambientGallery = document.getElementById('ambient-audio-gallery');
|
||||
const effectsGallery = document.getElementById('effects-audio-gallery');
|
||||
|
||||
if (!backgroundGallery || !ambientGallery || !effectsGallery) {
|
||||
console.error('Audio gallery elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get custom audio from storage
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
|
||||
// Load each category
|
||||
this.loadAudioCategory('background', backgroundGallery, customAudio.background);
|
||||
this.loadAudioCategory('ambient', ambientGallery, customAudio.ambient);
|
||||
this.loadAudioCategory('effects', effectsGallery, customAudio.effects);
|
||||
|
||||
// Update audio count
|
||||
this.updateAudioCount();
|
||||
|
||||
// Setup initial gallery controls for the active tab
|
||||
this.updateAudioGalleryControls('background');
|
||||
}
|
||||
|
||||
loadAudioCategory(category, gallery, audioFiles) {
|
||||
if (!audioFiles || audioFiles.length === 0) {
|
||||
gallery.innerHTML = `<div class="no-audio">No ${category} audio files found. Use the import button to add some!</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const audioItems = audioFiles.map(audio => {
|
||||
return `
|
||||
<div class="audio-item" data-category="${category}" data-filename="${audio.filename}" onclick="game.toggleAudioSelection(this)">
|
||||
<div class="audio-icon" data-category="${category}"></div>
|
||||
<div class="audio-title">${audio.title}</div>
|
||||
<div class="audio-filename">${audio.filename}</div>
|
||||
<div class="audio-controls">
|
||||
<button class="audio-preview-btn" onclick="event.stopPropagation(); game.previewAudio('${audio.path}', '${audio.title}')">🎧 Preview</button>
|
||||
<label class="audio-status" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" class="audio-checkbox" ${audio.enabled !== false ? 'checked' : ''}
|
||||
onchange="game.toggleAudioEnabled('${category}', '${audio.filename}', this.checked)">
|
||||
<span class="${audio.enabled !== false ? 'audio-enabled' : 'audio-disabled'}">
|
||||
${audio.enabled !== false ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
gallery.innerHTML = audioItems;
|
||||
}
|
||||
|
||||
updateAudioGalleryControls(activeCategory = 'background') {
|
||||
// This will be called when the active tab changes to update controls
|
||||
// for the current category
|
||||
console.log(`Audio gallery controls updated for ${activeCategory} category`);
|
||||
}
|
||||
|
||||
updateAudioCount() {
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
const backgroundCount = customAudio.background ? customAudio.background.length : 0;
|
||||
const ambientCount = customAudio.ambient ? customAudio.ambient.length : 0;
|
||||
const effectsCount = customAudio.effects ? customAudio.effects.length : 0;
|
||||
const total = backgroundCount + ambientCount + effectsCount;
|
||||
|
||||
const audioCountElement = document.querySelector('.audio-count');
|
||||
if (audioCountElement) {
|
||||
audioCountElement.textContent = `${total} total audio files (${backgroundCount} background, ${ambientCount} ambient, ${effectsCount} effects)`;
|
||||
}
|
||||
}
|
||||
|
||||
selectAllAudio() {
|
||||
const activeGallery = document.querySelector('.audio-gallery.active');
|
||||
if (activeGallery) {
|
||||
const audioItems = activeGallery.querySelectorAll('.audio-item');
|
||||
audioItems.forEach(item => item.classList.add('selected'));
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllAudio() {
|
||||
const activeGallery = document.querySelector('.audio-gallery.active');
|
||||
if (activeGallery) {
|
||||
const audioItems = activeGallery.querySelectorAll('.audio-item');
|
||||
audioItems.forEach(item => item.classList.remove('selected'));
|
||||
}
|
||||
}
|
||||
|
||||
deleteSelectedAudio() {
|
||||
const activeGallery = document.querySelector('.audio-gallery.active');
|
||||
if (!activeGallery) return;
|
||||
|
||||
const selectedItems = activeGallery.querySelectorAll('.audio-item.selected');
|
||||
if (selectedItems.length === 0) {
|
||||
this.showNotification('No audio files selected', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Are you sure you want to delete ${selectedItems.length} selected audio file(s)?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let deletedCount = 0;
|
||||
const isDesktop = window.electronAPI !== undefined;
|
||||
|
||||
selectedItems.forEach(async (item) => {
|
||||
const category = item.dataset.category;
|
||||
const filename = item.dataset.filename;
|
||||
|
||||
if (isDesktop && this.fileManager) {
|
||||
// Desktop mode - delete actual file
|
||||
const success = await this.fileManager.deleteAudio(category, filename);
|
||||
if (success) {
|
||||
deletedCount++;
|
||||
} else {
|
||||
console.error(`Failed to delete audio file: ${filename}`);
|
||||
}
|
||||
} else {
|
||||
// Web mode - remove from storage only
|
||||
this.removeAudioFromStorage(category, filename);
|
||||
deletedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (deletedCount > 0) {
|
||||
const modeText = isDesktop ? 'file(s) deleted from disk' : 'reference(s) removed from storage';
|
||||
this.showNotification(`${deletedCount} audio ${modeText}`, 'success');
|
||||
this.loadAudioGallery(); // Refresh the gallery
|
||||
}
|
||||
}
|
||||
|
||||
removeAudioFromStorage(category, filename) {
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
if (customAudio[category]) {
|
||||
customAudio[category] = customAudio[category].filter(audio => audio.filename !== filename);
|
||||
this.dataManager.set('customAudio', customAudio);
|
||||
}
|
||||
}
|
||||
|
||||
previewSelectedAudio() {
|
||||
const activeGallery = document.querySelector('.audio-gallery.active');
|
||||
if (!activeGallery) return;
|
||||
|
||||
const selectedItems = activeGallery.querySelectorAll('.audio-item.selected');
|
||||
if (selectedItems.length === 0) {
|
||||
this.showNotification('No audio file selected for preview', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedItems.length > 1) {
|
||||
this.showNotification('Please select only one audio file for preview', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedItem = selectedItems[0];
|
||||
const category = selectedItem.dataset.category;
|
||||
const filename = selectedItem.dataset.filename;
|
||||
|
||||
// Find the audio file data
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
const audioFile = customAudio[category].find(audio => audio.filename === filename);
|
||||
|
||||
if (audioFile) {
|
||||
this.previewAudio(audioFile.path, audioFile.title);
|
||||
}
|
||||
}
|
||||
|
||||
previewAudio(audioPath, title) {
|
||||
const previewSection = document.getElementById('audio-preview-section');
|
||||
const audioPlayer = document.getElementById('audio-preview-player');
|
||||
const previewFileName = document.getElementById('preview-file-name');
|
||||
|
||||
if (previewSection && audioPlayer && previewFileName) {
|
||||
previewSection.style.display = 'block';
|
||||
audioPlayer.src = audioPath;
|
||||
previewFileName.textContent = title || 'Unknown';
|
||||
|
||||
// Scroll to preview section
|
||||
previewSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
closeAudioPreview() {
|
||||
const previewSection = document.getElementById('audio-preview-section');
|
||||
const audioPlayer = document.getElementById('audio-preview-player');
|
||||
|
||||
if (previewSection && audioPlayer) {
|
||||
previewSection.style.display = 'none';
|
||||
audioPlayer.pause();
|
||||
audioPlayer.src = '';
|
||||
}
|
||||
}
|
||||
|
||||
toggleAudioEnabled(category, filename, enabled) {
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
|
||||
if (customAudio[category]) {
|
||||
const audioFile = customAudio[category].find(audio => audio.filename === filename);
|
||||
if (audioFile) {
|
||||
audioFile.enabled = enabled;
|
||||
this.dataManager.set('customAudio', customAudio);
|
||||
|
||||
// Update the visual status
|
||||
this.loadAudioGallery();
|
||||
this.showNotification(`Audio ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleAudioSelection(audioItem) {
|
||||
audioItem.classList.toggle('selected');
|
||||
}
|
||||
|
||||
showAudioStorageInfo() {
|
||||
try {
|
||||
const isDesktop = window.electronAPI !== undefined;
|
||||
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
|
||||
const backgroundCount = customAudio.background ? customAudio.background.length : 0;
|
||||
const ambientCount = customAudio.ambient ? customAudio.ambient.length : 0;
|
||||
const effectsCount = customAudio.effects ? customAudio.effects.length : 0;
|
||||
const totalCustomAudio = backgroundCount + ambientCount + effectsCount;
|
||||
|
||||
let message = `📊 Audio Storage Information\n\n`;
|
||||
|
||||
if (isDesktop) {
|
||||
message += `🖥️ Desktop Mode - Unlimited Storage\n`;
|
||||
message += `Files stored in native file system\n\n`;
|
||||
message += `📁 Audio Categories:\n`;
|
||||
message += `🎵 Background Music: ${backgroundCount} files\n`;
|
||||
message += `🌿 Ambient Sounds: ${ambientCount} files\n`;
|
||||
message += `🔊 Sound Effects: ${effectsCount} files\n`;
|
||||
message += `📊 Total Custom Audio: ${totalCustomAudio} files\n\n`;
|
||||
message += `💾 Storage: Uses native file system\n`;
|
||||
message += `📂 Location: audio/ folder in app directory\n`;
|
||||
message += `🔄 Auto-scanned on startup`;
|
||||
} else {
|
||||
message += `🌐 Web Mode - Browser Storage\n`;
|
||||
message += `Limited by browser storage quotas\n\n`;
|
||||
message += `📁 Audio References:\n`;
|
||||
message += `🎵 Background Music: ${backgroundCount} files\n`;
|
||||
message += `🌿 Ambient Sounds: ${ambientCount} files\n`;
|
||||
message += `🔊 Sound Effects: ${effectsCount} files\n`;
|
||||
message += `📊 Total Custom Audio: ${totalCustomAudio} files\n\n`;
|
||||
message += `💾 Storage: Browser localStorage\n`;
|
||||
message += `⚠️ Subject to browser storage limits`;
|
||||
}
|
||||
|
||||
alert(message);
|
||||
} catch (error) {
|
||||
console.error('Error showing audio storage info:', error);
|
||||
alert('Error retrieving audio storage information.');
|
||||
}
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
// Create notification element if it doesn't exist
|
||||
let notification = document.getElementById('notification');
|
||||
|
|
|
|||
74
index.html
74
index.html
|
|
@ -48,6 +48,7 @@
|
|||
<button id="start-btn" class="btn btn-primary">Start Game</button>
|
||||
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
|
||||
<button id="manage-images-btn" class="btn btn-secondary">Manage Images</button>
|
||||
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
|
||||
</div>
|
||||
|
||||
<!-- Options Menu -->
|
||||
|
|
@ -195,6 +196,79 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Management Screen -->
|
||||
<div id="audio-management-screen" class="screen">
|
||||
<h2>Manage Your Audio</h2>
|
||||
|
||||
<!-- Upload Section -->
|
||||
<div class="upload-section">
|
||||
<h3>🎵 Import Audio Files</h3>
|
||||
<div class="upload-controls">
|
||||
<button id="import-background-audio-btn" class="btn btn-primary">🎶 Import Background Music</button>
|
||||
<button id="import-ambient-audio-btn" class="btn btn-success">🌿 Import Ambient Sounds</button>
|
||||
<button id="import-effects-audio-btn" class="btn btn-warning">🔊 Import Sound Effects</button>
|
||||
<input type="file" id="audio-upload-input" accept="audio/*" multiple style="display: none;">
|
||||
<span class="upload-info desktop-feature">Desktop: Native file dialogs • MP3, WAV, OGG, M4A, AAC, FLAC</span>
|
||||
<span class="upload-info web-feature" style="display: none;">Web: Limited browser upload</span>
|
||||
</div>
|
||||
<div class="directory-controls">
|
||||
<button id="audio-storage-info-btn" class="btn btn-outline">📊 Audio Storage Info</button>
|
||||
<span class="scan-info">Audio files automatically scanned on startup</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Gallery Section -->
|
||||
<div class="gallery-section">
|
||||
<div class="gallery-header">
|
||||
<h3>Current Audio Files</h3>
|
||||
<div class="gallery-controls">
|
||||
<button id="select-all-audio-btn" class="btn btn-small">Select All</button>
|
||||
<button id="deselect-all-audio-btn" class="btn btn-small">Deselect All</button>
|
||||
<button id="delete-selected-audio-btn" class="btn btn-danger btn-small">Delete Selected</button>
|
||||
<button id="preview-selected-audio-btn" class="btn btn-info btn-small">🎧 Preview</button>
|
||||
<span class="audio-count">Loading audio files...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Tabs -->
|
||||
<div class="audio-tabs">
|
||||
<button id="background-audio-tab" class="tab-btn active">Background Music</button>
|
||||
<button id="ambient-audio-tab" class="tab-btn">Ambient Sounds</button>
|
||||
<button id="effects-audio-tab" class="tab-btn">Sound Effects</button>
|
||||
</div>
|
||||
|
||||
<div id="background-audio-gallery" class="audio-gallery active">
|
||||
<!-- Background audio files will be populated here -->
|
||||
</div>
|
||||
|
||||
<div id="ambient-audio-gallery" class="audio-gallery">
|
||||
<!-- Ambient audio files will be populated here -->
|
||||
</div>
|
||||
|
||||
<div id="effects-audio-gallery" class="audio-gallery">
|
||||
<!-- Effects audio files will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Preview Section -->
|
||||
<div class="audio-preview-section" id="audio-preview-section" style="display: none;">
|
||||
<h4>🎧 Audio Preview</h4>
|
||||
<div class="preview-controls">
|
||||
<audio id="audio-preview-player" controls style="width: 100%; max-width: 500px;">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
<div class="preview-info">
|
||||
<span id="preview-file-name">No file selected</span>
|
||||
<button id="close-preview-btn" class="btn btn-small btn-outline">Close Preview</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="management-buttons">
|
||||
<button id="back-to-start-from-audio-btn" class="btn btn-secondary">Back to Start</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Game Screen -->
|
||||
<div id="game-screen" class="screen">
|
||||
<div class="task-container">
|
||||
|
|
|
|||
194
styles.css
194
styles.css
|
|
@ -1454,4 +1454,198 @@ body.theme-monochrome {
|
|||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 2px dashed #dee2e6;
|
||||
}
|
||||
|
||||
/* Audio Management Styles */
|
||||
.audio-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.audio-tabs .tab-btn {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
background: #f8f9fa;
|
||||
color: #6c757d;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.audio-tabs .tab-btn:hover {
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.audio-tabs .tab-btn.active {
|
||||
background: white;
|
||||
color: #007bff;
|
||||
border-bottom-color: #007bff;
|
||||
}
|
||||
|
||||
.audio-count {
|
||||
font-size: 0.9em;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.audio-gallery {
|
||||
display: none; /* Hide galleries by default */
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 15px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.audio-gallery.active {
|
||||
display: grid; /* Show active gallery as grid */
|
||||
}
|
||||
|
||||
.audio-item {
|
||||
position: relative;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.audio-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.audio-item.selected {
|
||||
border: 3px solid #007bff;
|
||||
transform: scale(0.98);
|
||||
background: #f8f9ff;
|
||||
}
|
||||
|
||||
.audio-icon {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.audio-title {
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-filename {
|
||||
font-size: 0.7em;
|
||||
color: #6c757d;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.audio-preview-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.audio-preview-btn:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.audio-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.audio-status {
|
||||
font-size: 0.7em;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.audio-enabled {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.audio-disabled {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.audio-preview-section {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.audio-preview-section h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.preview-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.preview-info span {
|
||||
font-size: 0.9em;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-audio {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 2px dashed #dee2e6;
|
||||
}
|
||||
|
||||
/* Audio category icons */
|
||||
.audio-item[data-category="background"] .audio-icon::before {
|
||||
content: "🎵";
|
||||
}
|
||||
|
||||
.audio-item[data-category="ambient"] .audio-icon::before {
|
||||
content: "🌿";
|
||||
}
|
||||
|
||||
.audio-item[data-category="effects"] .audio-icon::before {
|
||||
content: "🔊";
|
||||
}
|
||||
Loading…
Reference in New Issue