// Desktop File Manager for Task Challenge Game class DesktopFileManager { constructor(dataManager) { this.dataManager = dataManager; this.appPath = null; this.photoDirectory = null; // Directory for captured photos this.videoRecordingDirectory = null; // Directory for recorded videos // External video directories (linked, not copied) this.externalVideoDirectories = []; // Array of linked directory objects this.allLinkedVideos = []; // Cached array of all videos from all directories // Don't auto-init, let caller await it explicitly this.isInitialized = false; } async init() { // Prevent double initialization if (this.isInitialized) { console.log('â„šī¸ Desktop file manager already initialized, skipping'); return; } // Check if we're running in Electron this.isElectron = window.electronAPI !== undefined; if (this.isElectron) { try { this.appPath = await window.electronAPI.getAppPath(); if (this.appPath) { this.photoDirectory = await window.electronAPI.pathJoin(this.appPath, 'photos', 'captured'); this.videoRecordingDirectory = await window.electronAPI.pathJoin(this.appPath, 'videos', 'recorded'); // Ensure directories exist await window.electronAPI.createDirectory(this.photoDirectory); await window.electronAPI.createDirectory(this.videoRecordingDirectory); // Note: No longer creating/using local video directories // All videos come from external linked directories only console.log('Desktop file manager initialized'); console.log('App path:', this.appPath); console.log('Photo directory:', this.photoDirectory); console.log('Video recording directory:', this.videoRecordingDirectory); // Load any previously linked external directories await this.loadLinkedDirectories(); // Mark as initialized this.isInitialized = true; } else { console.error('Failed to get app path'); } } catch (error) { console.error('Error initializing desktop file manager:', error); this.isElectron = false; } } else { console.log('Running in browser mode - file manager disabled'); } } async selectAndImportImages(category = 'task') { // Legacy method - no longer supported (use linked directories instead) console.warn('selectAndImportImages is deprecated - images should come from linked directories'); this.showNotification('Image import not supported - use linked directories', 'warning'); return []; } async selectAndImportAudio(category = 'background') { // Legacy method - no longer supported console.warn('selectAndImportAudio is deprecated'); this.showNotification('Audio import not supported', 'warning'); return []; } async selectAndImportVideos(category = 'background') { if (!this.isElectron) { this.showNotification('Video import only available in desktop version', 'warning'); return []; } try { // Open file dialog for video files const filePaths = await window.electronAPI.selectVideos(); if (filePaths.length === 0) { return []; } const importedVideos = []; let targetDir; switch(category) { case 'background': targetDir = this.videoDirectories.background; break; case 'task': case 'tasks': targetDir = this.videoDirectories.tasks; break; case 'reward': case 'rewards': targetDir = this.videoDirectories.rewards; break; case 'punishment': case 'punishments': targetDir = this.videoDirectories.punishments; break; default: targetDir = this.videoDirectories.background; } if (!targetDir) { console.error('Target video directory not initialized'); this.showNotification('Video directory not initialized', 'error'); return []; } for (const filePath of filePaths) { const fileName = filePath.split(/[\\/]/).pop(); const targetPath = await window.electronAPI.pathJoin(targetDir, fileName); // Copy file to app directory const success = await window.electronAPI.copyVideo(filePath, targetPath); if (success) { importedVideos.push({ name: fileName, path: targetPath, category: category, title: this.getVideoTitle(fileName), url: `file:///${targetPath.replace(/\\/g, '/')}` }); console.log(`Imported video: ${fileName} to ${category}`); } else { console.error(`Failed to import video: ${fileName}`); } } if (importedVideos.length > 0) { // Update the game's video storage await this.updateVideoStorage(importedVideos); this.showNotification(`Imported ${importedVideos.length} video file(s) to ${category}!`, 'success'); } return importedVideos; } catch (error) { console.error('Error importing videos:', error); this.showNotification('Failed to import video files', 'error'); return []; } } async addVideoDirectory(customName = null) { if (!this.isElectron) { this.showNotification('Directory linking only available in desktop version', 'warning'); return null; } try { // Open directory dialog const directoryPath = await window.electronAPI.selectDirectory(); if (!directoryPath) { return null; } // Check if directory is already linked const existingDir = this.externalVideoDirectories.find(dir => dir.path === directoryPath); if (existingDir) { this.showNotification('Directory is already linked!', 'warning'); return existingDir; } // Show scanning notification this.showNotification('🔍 Scanning directory for videos... This may take a moment for large collections.', 'info'); // Scan the directory recursively for video files console.log(`🔍 Scanning directory recursively: ${directoryPath}`); const videoFiles = await window.electronAPI.readVideoDirectoryRecursive(directoryPath); console.log(`Found ${videoFiles.length} video files in directory:`, directoryPath); if (videoFiles.length === 0) { this.showNotification('No video files found in selected directory', 'warning'); return null; } // Create directory object const directoryName = customName || this.getDirectoryName(directoryPath); const directoryObj = { id: Date.now(), // Unique ID name: directoryName, path: directoryPath, videoCount: videoFiles.length, dateAdded: new Date().toISOString(), isRecursive: true }; // Process videos in chunks to avoid blocking UI const linkedVideos = await this.processVideosInChunks(videoFiles, directoryObj); // Add to linked directories this.externalVideoDirectories.push(directoryObj); // Update cached video list this.allLinkedVideos.push(...linkedVideos); // Save to persistent storage await this.saveLinkedDirectories(); // Update video storage await this.updateUnifiedVideoStorage(); this.showNotification( `✅ Linked directory "${directoryName}"!\nFound ${videoFiles.length} videos`, 'success' ); console.log(`🔗 Linked directory: ${directoryName} (${videoFiles.length} videos)`); return { directory: directoryObj, videoCount: videoFiles.length, videos: linkedVideos }; } catch (error) { console.error('Error linking video directory:', error); this.showNotification('Failed to link video directory', 'error'); return null; } } async processVideosInChunks(videoFiles, directoryObj) { const chunkSize = 100; // Process 100 videos at a time const linkedVideos = []; for (let i = 0; i < videoFiles.length; i += chunkSize) { const chunk = videoFiles.slice(i, i + chunkSize); const progress = Math.round(((i + chunk.length) / videoFiles.length) * 100); console.log(`Processing videos ${i + 1}-${i + chunk.length} of ${videoFiles.length} (${progress}%)`); const chunkProcessed = chunk.map(video => ({ id: `${directoryObj.id}_${video.name}`, name: video.name, path: video.path, url: `file:///${video.path.replace(/\\/g, '/')}`, title: this.getVideoTitle(video.name), size: video.size || 0, directoryId: directoryObj.id, directoryName: directoryObj.name, isExternal: true, relativePath: video.path.replace(directoryObj.path, '').replace(/^[\\/]/, '') })); linkedVideos.push(...chunkProcessed); // Small delay to keep UI responsive if (i + chunkSize < videoFiles.length) { await new Promise(resolve => setTimeout(resolve, 10)); } } return linkedVideos; } async removeVideoDirectory(directoryId) { if (!this.isElectron) { return false; } try { // Convert to number if string const id = typeof directoryId === 'string' ? parseInt(directoryId, 10) : directoryId; // Find directory - check both string and number formats const dirIndex = this.externalVideoDirectories.findIndex(dir => dir.id === id || dir.id === directoryId || String(dir.id) === String(directoryId) ); if (dirIndex === -1) { console.error('Directory not found with ID:', directoryId, 'Available IDs:', this.externalVideoDirectories.map(d => d.id)); this.showNotification('Directory not found', 'error'); return false; } const directory = this.externalVideoDirectories[dirIndex]; // Remove from arrays - match by index instead of ID to be safe this.externalVideoDirectories.splice(dirIndex, 1); this.allLinkedVideos = this.allLinkedVideos.filter(video => video.directoryId !== id && video.directoryId !== directoryId && String(video.directoryId) !== String(directoryId) ); // Save to persistent storage await this.saveLinkedDirectories(); // Update video storage await this.updateUnifiedVideoStorage(); this.showNotification(`Unlinked directory: ${directory.name}`, 'success'); return true; } catch (error) { console.error('Error unlinking directory:', error); this.showNotification('Failed to unlink directory', 'error'); return false; } } async refreshAllDirectories() { if (!this.isElectron) { return; } console.log('🔄 Refreshing all linked directories...'); this.allLinkedVideos = []; let hasAccessErrors = false; for (const directory of this.externalVideoDirectories) { try { const videoFiles = await window.electronAPI.readVideoDirectoryRecursive(directory.path); const linkedVideos = videoFiles.map(video => ({ id: `${directory.id}_${video.name}`, name: video.name, path: video.path, url: `file:///${video.path.replace(/\\/g, '/')}`, title: this.getVideoTitle(video.name), size: video.size || 0, directoryId: directory.id, directoryName: directory.name, isExternal: true, relativePath: video.path.replace(directory.path, '').replace(/^[\\/]/, '') })); this.allLinkedVideos.push(...linkedVideos); // Update directory video count directory.videoCount = videoFiles.length; // Clear any previous errors delete directory.lastError; delete directory.lastErrorTime; } catch (error) { console.warn(`Could not access directory ${directory.name}:`, error); hasAccessErrors = true; // Directory might be unavailable (external drive, network, etc.) // Don't remove it from the list, just mark it as having 0 videos directory.videoCount = 0; directory.lastError = error.message; directory.lastErrorTime = new Date().toISOString(); } } // Only save directories if we didn't have access errors // This prevents clearing valid directories due to temporary access issues if (!hasAccessErrors || this.externalVideoDirectories.length === 0) { await this.saveLinkedDirectories(); } await this.updateUnifiedVideoStorage(); const totalVideos = this.allLinkedVideos.length; // Removed notification to avoid startup message clutter } async saveLinkedDirectories() { const data = { directories: this.externalVideoDirectories, lastUpdated: new Date().toISOString() }; console.log('💾 Saving linked directories:', this.externalVideoDirectories.length, 'directories'); this.dataManager.set('linkedVideoDirectories', data); // Verify save const saved = this.dataManager.get('linkedVideoDirectories'); console.log('✅ Verified save - directories in storage:', saved?.directories?.length || 0); } async loadLinkedDirectories() { if (!this.isElectron) { return; } if (!this.dataManager) { console.warn('âš ī¸ DataManager not available, skipping linked directories load'); return; } try { console.log('📂 Loading linked directories from storage...'); const data = this.dataManager.get('linkedVideoDirectories'); console.log('📂 Raw data from storage:', data); if (data && data.directories) { this.externalVideoDirectories = data.directories; console.log(`📁 Loaded ${this.externalVideoDirectories.length} linked directories from storage`); console.log('📁 Directory paths:', this.externalVideoDirectories.map(d => d.path)); // Refresh all directories to get current video lists await this.refreshAllDirectories(); } else { console.log('📂 No linked directories found in storage'); } } catch (error) { console.error('Error loading linked directories:', error); } } async updateUnifiedVideoStorage() { // Store all videos in a single unified list (no categories) // Check if we should clear stale data - if we have current video directories/files // but the unified library count doesn't match, clear it const currentVideoCount = this.allLinkedVideos.length; const currentDirectoryCount = this.externalVideoDirectories.length; try { const existingData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}'); const existingVideoCount = existingData.allVideos ? existingData.allVideos.length : 0; // If we have a significant mismatch or the user has video directories but // unified library shows different count, force refresh if (currentDirectoryCount > 0 || currentVideoCount !== existingVideoCount) { console.log(`📹 Forcing refresh - Current: ${currentVideoCount} videos, ${currentDirectoryCount} directories. Stored: ${existingVideoCount} videos`); } else if (this.externalVideoDirectories.length === 0 && this.allLinkedVideos.length === 0 && existingVideoCount > 0) { console.log(`📹 Preserving existing unified video library: ${existingVideoCount} videos`); // Don't overwrite - preserve existing data return; } } catch (error) { console.warn('Error checking existing unified video library:', error); } const videoData = { allVideos: this.allLinkedVideos, directories: this.externalVideoDirectories, lastUpdated: new Date().toISOString() }; localStorage.setItem('unifiedVideoLibrary', JSON.stringify(videoData)); console.log(`📹 Updated unified video library: ${this.allLinkedVideos.length} videos from ${this.externalVideoDirectories.length} directories`); } /** * Clear stale unified video library data and force refresh */ clearUnifiedVideoLibrary() { console.log('đŸ—‘ī¸ Clearing unified video library...'); localStorage.removeItem('unifiedVideoLibrary'); console.log('✅ Unified video library cleared'); } /** * Force refresh of all video data */ async forceRefreshVideoLibrary() { console.log('🔄 Force refreshing video library...'); this.clearUnifiedVideoLibrary(); await this.refreshAllLinkedDirectories(); // Trigger video manager reload if it exists if (window.videoPlayerManager) { window.videoPlayerManager.loadVideoFiles(); } console.log('✅ Video library force refresh complete'); } getDirectoryName(directoryPath) { // Extract just the folder name from the full path return directoryPath.split(/[\\/]/).pop() || 'Unknown Directory'; } getAllVideos() { return this.allLinkedVideos; } async scanVideoDirectory(directoryPath) { if (!this.isElectron) { console.warn('Not in Electron environment, cannot scan directory'); return []; } try { console.log(`đŸŽŦ Scanning video directory: ${directoryPath}`); const files = await window.electronAPI.readDirectory(directoryPath); if (!Array.isArray(files)) { console.warn('Directory scan returned non-array result:', files); return []; } console.log(`đŸŽŦ Raw files found in ${directoryPath}:`, files.length); // More comprehensive video extensions list const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', '.mpg', '.mpeg', '.3gp', '.asf', '.rm', '.rmvb', '.vob', '.ts', '.mts', '.m2ts']; // Debug: log first few files to see what we're working with if (files.length > 0) { console.log(`đŸŽŦ Sample files:`, files.slice(0, 5).map(f => ({ name: f.name, size: f.size }))); } const videoFiles = files.filter(file => { const ext = file.name.toLowerCase().substring(file.name.lastIndexOf('.')); const isVideo = videoExtensions.includes(ext); if (isVideo) { console.log(`đŸŽŦ Found video file: ${file.name} (${ext})`); } return isVideo; }); console.log(`đŸŽŦ Found ${videoFiles.length} video files in ${directoryPath}`); // Process video files into the expected format const processedVideos = videoFiles.map(file => ({ name: file.name, path: file.path, size: file.size || 0, dateAdded: new Date().toISOString(), directory: directoryPath, source: 'directory' })); return processedVideos; } catch (error) { console.error(`❌ Error scanning video directory ${directoryPath}:`, error); return []; } } getDirectoriesInfo() { return this.externalVideoDirectories.map(dir => ({ id: dir.id, name: dir.name, path: dir.path, videoCount: dir.videoCount, dateAdded: dir.dateAdded })); } async scanDirectoryForImages(category = 'task') { // Legacy method - no longer used (images now come from linked directories) console.warn('scanDirectoryForImages is deprecated - use linked directories instead'); return []; } async updateImageStorage(images) { // Get existing images let customImages = this.dataManager.get('customImages') || { task: [], consequence: [] }; // Convert old format if necessary if (Array.isArray(customImages)) { customImages = { task: customImages, consequence: [] }; } // Add new images (avoid duplicates) for (const image of images) { const category = image.category === 'tasks' ? 'task' : image.category === 'consequences' ? 'consequence' : image.category; if (!customImages[category]) { customImages[category] = []; } // Check for duplicates by path const exists = customImages[category].some(existing => { if (typeof existing === 'string') { return existing === image.path; } else if (typeof existing === 'object') { return existing.path === image.path; } return false; }); if (!exists) { customImages[category].push(image.path); } } // Save updated images this.dataManager.set('customImages', customImages); return customImages; } async deleteImage(imagePath, category) { if (!this.isElectron) { return false; } try { const success = await window.electronAPI.deleteFile(imagePath); if (success) { // Remove from storage let customImages = this.dataManager.get('customImages') || { task: [], consequence: [] }; if (Array.isArray(customImages)) { customImages = { task: customImages, consequence: [] }; } if (customImages[category]) { customImages[category] = customImages[category].filter(img => { if (typeof img === 'string') { return img !== imagePath; } else if (typeof img === 'object') { return img.path !== imagePath; } return true; }); } this.dataManager.set('customImages', customImages); this.showNotification('Image deleted successfully', 'success'); return true; } else { this.showNotification('Failed to delete image', 'error'); return false; } } catch (error) { console.error('Error deleting image:', error); this.showNotification('Error deleting image', 'error'); return false; } } async openImageDirectory(category = 'task') { if (!this.isElectron) { this.showNotification('This feature is only available in desktop version', 'info'); return; } // Note: We would need to add shell integration to open the folder // For now, just show the path const dir = this.imageDirectories[category === 'task' ? 'tasks' : 'consequences']; this.showNotification(`Images stored in: ${dir}`, 'info'); console.log(`${category} images directory:`, dir); } async scanDirectoryForAudio(category = 'background') { // Legacy method - no longer supported console.warn('scanDirectoryForAudio is deprecated'); return []; } async scanAllAudioDirectories() { // Legacy method - no longer supported console.warn('scanAllAudioDirectories is deprecated'); return { background: [], ambient: [] }; } async updateAudioStorage(audioFiles) { if (!Array.isArray(audioFiles) || audioFiles.length === 0) { return; } // Get existing audio let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [] }; // Add new audio files (avoid duplicates) for (const audio of audioFiles) { const category = audio.category || 'background'; if (!customAudio[category]) { customAudio[category] = []; } // Check for duplicates by path const exists = customAudio[category].some(existing => { if (typeof existing === 'string') { return existing === audio.path; } else if (typeof existing === 'object') { return existing.path === audio.path; } return false; }); if (!exists) { customAudio[category].push({ name: audio.name, path: audio.path, title: audio.title, category: audio.category }); } } // Save back to storage this.dataManager.set('customAudio', customAudio); console.log('Audio storage updated:', customAudio); } async deleteAudio(audioPath, category = 'background') { if (!this.isElectron) { this.showNotification('Audio deletion only available in desktop version', 'warning'); return false; } try { const success = await window.electronAPI.deleteFile(audioPath); if (success) { // Remove from storage let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [] }; if (customAudio[category]) { customAudio[category] = customAudio[category].filter(audio => { if (typeof audio === 'string') { return audio !== audioPath; } return audio.path !== audioPath; }); } this.dataManager.set('customAudio', customAudio); console.log(`Deleted audio file: ${audioPath}`); return true; } return false; } catch (error) { console.error('Error deleting audio:', error); return false; } } getAudioTitle(fileName) { // Remove file extension and clean up the name for display return fileName .replace(/\.[^/.]+$/, '') // Remove extension .replace(/[-_]/g, ' ') // Replace dashes and underscores with spaces .replace(/\b\w/g, l => l.toUpperCase()); // Capitalize first letters } getAudioPath(audioName, category = 'background') { if (!this.isElectron) { return `audio/${audioName}`; } let dir; switch(category) { case 'background': dir = this.audioDirectories.background; break; case 'ambient': dir = this.audioDirectories.ambient; break; case 'effects': dir = this.audioDirectories.effects; break; default: dir = this.audioDirectories.background; } return `${dir}/${audioName}`; } showNotification(message, type = 'info') { // Use the game's existing notification system if (window.game && window.game.showNotification) { window.game.showNotification(message, type); } else { console.log(`[${type.toUpperCase()}] ${message}`); } } getImagePath(imageName, category = 'task') { // Legacy method - return web path console.warn('getImagePath is deprecated'); return `images/${category}s/${imageName}`; } getVideoTitle(fileName) { // Remove file extension and clean up the name for display return fileName .replace(/\.[^/.]+$/, '') // Remove extension .replace(/[-_]/g, ' ') // Replace dashes and underscores with spaces .replace(/\b\w/g, l => l.toUpperCase()); // Capitalize first letters } /** * Save a captured photo to the file system * @param {string} dataURL - Base64 data URL of the photo * @param {string} sessionType - Type of session (e.g., 'training-academy', 'dress-up') * @returns {Promise} Photo metadata or null if failed */ async savePhoto(dataURL, sessionType = 'training-academy') { if (!this.isElectron) { console.warn('📸 Photo saving only available in desktop version'); return null; } if (!this.photoDirectory) { console.error('📸 Photo directory not initialized yet'); return null; } try { console.log('📸 Attempting to save photo...'); const timestamp = Date.now(); const filename = `${sessionType}_${timestamp}.jpg`; const filePath = await window.electronAPI.pathJoin(this.photoDirectory, filename); console.log('📸 Photo will be saved to:', filePath); // Convert data URL to base64 string (remove the "data:image/jpeg;base64," prefix) const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, ''); console.log('📸 Base64 data length:', base64Data.length); // Save the file const success = await window.electronAPI.saveBase64Image(filePath, base64Data); console.log('📸 Save result:', success); if (success) { const photoData = { filename: filename, path: filePath, fullPath: filePath, isWebcamCapture: true, timestamp: timestamp, sessionType: sessionType, url: `file:///${filePath.replace(/\\/g, '/')}` }; console.log(`📸 Photo saved successfully: ${filename}`); return photoData; } else { console.error('📸 Failed to save photo to file system - saveBase64Image returned false'); return null; } } catch (error) { console.error('📸 Error saving photo:', error); return null; } } /** * Load all captured photos from the photo directory * @returns {Promise} Array of photo metadata objects */ async loadCapturedPhotos() { if (!this.isElectron || !this.photoDirectory) { return []; } try { const files = await window.electronAPI.readDirectory(this.photoDirectory); const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif']; const photoFiles = files.filter(file => { const ext = file.name.toLowerCase().substring(file.name.lastIndexOf('.')); return imageExtensions.includes(ext); }); const photos = photoFiles.map(file => { // Extract session type from filename (e.g., "training-academy_1234567890.jpg") const parts = file.name.split('_'); const sessionType = parts.length > 1 ? parts.slice(0, -1).join('_') : 'unknown'; const timestamp = parts.length > 1 ? parseInt(parts[parts.length - 1].split('.')[0]) : Date.now(); return { filename: file.name, path: file.path, fullPath: file.path, isWebcamCapture: true, timestamp: timestamp, sessionType: sessionType, url: `file:///${file.path.replace(/\\/g, '/')}` }; }); console.log(`📸 Loaded ${photos.length} photos from file system`); return photos; } catch (error) { console.error('Error loading captured photos:', error); return []; } } /** * Delete a captured photo from the file system * @param {string} filePath - Full path to the photo file * @returns {Promise} Success status */ async deletePhoto(filePath) { if (!this.isElectron) { return false; } try { const success = await window.electronAPI.deleteFile(filePath); if (success) { console.log(`📸 Photo deleted: ${filePath}`); return true; } return false; } catch (error) { console.error('Error deleting photo:', error); return false; } } /** * Save a recorded video to the file system * @param {Blob} videoBlob - Video blob data * @param {string} sessionType - Type of session (e.g., 'quick-play', 'training-academy') * @param {string} extension - File extension (e.g., 'mp4', 'webm') * @returns {Promise} Video metadata or null if failed */ async saveVideo(videoBlob, sessionType = 'quick-play', extension = 'mp4') { if (!this.isElectron) { console.warn('📹 Video saving only available in desktop version'); return null; } if (!this.videoRecordingDirectory) { console.error('📹 Video directory not initialized yet'); return null; } try { console.log('📹 Attempting to save video...'); const timestamp = Date.now(); const filename = `${sessionType}_${timestamp}.${extension}`; const filePath = await window.electronAPI.pathJoin(this.videoRecordingDirectory, filename); console.log('📹 Video will be saved to:', filePath); // Convert blob to ArrayBuffer then to base64 const arrayBuffer = await videoBlob.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); const binaryString = uint8Array.reduce((data, byte) => data + String.fromCharCode(byte), ''); const base64Data = btoa(binaryString); console.log('📹 Video data size:', videoBlob.size, 'bytes'); // Save the file using the same handler const success = await window.electronAPI.saveBase64Image(filePath, base64Data); console.log('📹 Save result:', success); if (success) { const videoData = { filename: filename, path: filePath, fullPath: filePath, timestamp: timestamp, sessionType: sessionType, size: videoBlob.size, extension: extension, url: `file:///${filePath.replace(/\\/g, '/')}` }; console.log(`📹 Video saved successfully: ${filename}`); return videoData; } else { console.error('📹 Failed to save video to file system'); return null; } } catch (error) { console.error('📹 Error saving video:', error); return null; } } getVideoPath(videoName, category = 'background') { if (!this.isElectron) { return `videos/${videoName}`; } let dir; switch(category) { case 'background': dir = this.videoDirectories.background; break; case 'tasks': dir = this.videoDirectories.tasks; break; case 'rewards': dir = this.videoDirectories.rewards; break; case 'punishments': dir = this.videoDirectories.punishments; break; default: dir = this.videoDirectories.background; } return `${dir}/${videoName}`; } async updateVideoStorage(videoFiles) { // Get existing videos from localStorage const existingVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}'); console.log('Updating video storage with', videoFiles.length, 'videos'); // Add new videos videoFiles.forEach(video => { console.log(`Adding video to category: ${video.category}, name: ${video.name}`); if (!existingVideos[video.category]) { existingVideos[video.category] = []; console.log(`Created new category: ${video.category}`); } // Check if video already exists (prevent duplicates) const exists = existingVideos[video.category].some(existing => existing.name === video.name); if (!exists) { existingVideos[video.category].push(video); console.log(`Added video: ${video.name} to ${video.category}`); } else { console.log(`Video already exists: ${video.name} in ${video.category}`); } }); console.log('Final video storage:', existingVideos); // Save back to localStorage localStorage.setItem('videoFiles', JSON.stringify(existingVideos)); // Trigger video manager reload if it exists if (window.videoPlayerManager) { window.videoPlayerManager.loadVideoFiles(); } } async scanDirectoryForVideos(category = 'background') { if (!this.isElectron) { return []; } try { let targetDir; switch(category) { case 'background': targetDir = this.videoDirectories.background; break; case 'task': case 'tasks': targetDir = this.videoDirectories.tasks; break; case 'reward': case 'rewards': targetDir = this.videoDirectories.rewards; break; case 'punishment': case 'punishments': targetDir = this.videoDirectories.punishments; break; default: targetDir = this.videoDirectories.background; } if (!targetDir) { console.log(`Video directory for ${category} not initialized`); return []; } const videos = await window.electronAPI.readVideoDirectory(targetDir); // Add category information return videos.map(video => ({ ...video, category: category })); } catch (error) { console.error(`Error scanning video directory for ${category}:`, error); return []; } } async loadAllVideos() { if (!this.isElectron) { return; } try { // Scan all video directories const backgroundVideos = await this.scanDirectoryForVideos('background'); const taskVideos = await this.scanDirectoryForVideos('tasks'); const rewardVideos = await this.scanDirectoryForVideos('rewards'); const punishmentVideos = await this.scanDirectoryForVideos('punishments'); // Update storage with all found videos await this.updateVideoStorage([...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos]); } catch (error) { console.error('Error loading videos from directories:', error); } } } // Global file manager instance let desktopFileManager = null;