diff --git a/Gooner-Training-Academy-v4.1-Beta.zip b/Gooner-Training-Academy-v4.1-Beta.zip new file mode 100644 index 0000000..68e818d Binary files /dev/null and b/Gooner-Training-Academy-v4.1-Beta.zip differ diff --git a/index.html b/index.html index 66e2823..b3eaa2a 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - +๏ปฟ @@ -192,7 +192,7 @@ ๐ŸŒ€ Hypno Gallery - @@ -883,259 +883,6 @@ - -
-

๐Ÿ“š Media Library

-

Manage all your media content in one place

- - -
- - - - -
- - -
-
-

๐Ÿ–ผ๏ธ Image Library Management

-

Link directories from your computer to access image content

- - -
-

๐Ÿ“ Linked Image Directories

-
- - - - - 0 directories linked -
-
-
-
No image directories linked yet
-
-
-
- - - -
-
- - -
-
-

๐ŸŽต Audio Library Management

-

Organize your background music and ambient sounds

- - -
-

๐ŸŽต Import Audio Files

-
- - - -
-
- ๐Ÿ’ป Desktop: Native file dialogs โ€ข Supports MP3, WAV, OGG, M4A formats -
-
- - - -
-
- - - -
-
- - -
-
-

๐ŸŽฌ Video Library Management

-

Manage your video content for enhanced training sessions

- -
-

๐Ÿ“ Linked Video Directories

-

Link directories from your computer to access video content

-
- - - - - 0 directories linked -
-
-
-
No video directories linked yet
-
-
-
- - - -
-
- - - - - -
- - -
-
@@ -3747,28 +3494,13 @@ // Set up library button (only once) const libraryBtn = document.getElementById('library-btn'); console.log('๐Ÿ” Library button found:', !!libraryBtn); - console.log('๐Ÿ” Library button has handler:', libraryBtn ? libraryBtn.hasAttribute('data-handler-attached') : 'button not found'); if (libraryBtn && !libraryBtn.hasAttribute('data-handler-attached')) { console.log('๐Ÿ”ง Attaching library button handler...'); libraryBtn.setAttribute('data-handler-attached', 'true'); libraryBtn.addEventListener('click', () => { - console.log('๐Ÿ“š Library button clicked'); - console.log('๐ŸŽฎ Game instance available:', !!window.game); - console.log('๐Ÿ”ง showScreen method available:', !!(window.game && typeof window.game.showScreen === 'function')); - - if (window.game && typeof window.game.showScreen === 'function') { - console.log('๐Ÿ“บ Showing library screen...'); - window.game.showScreen('library-screen'); - // Set up library tab handlers when screen is shown - setTimeout(() => { - console.log('โš™๏ธ Setting up library handlers...'); - setupLibraryHandlers(); - }, 100); - } else { - console.error('Game instance not available for library'); - console.error('Available game methods:', window.game ? Object.keys(window.game) : 'No game object'); - } + console.log('๐Ÿ“š Library button clicked - navigating to library.html'); + window.location.href = 'library.html'; }); console.log('โœ… Library button handler attached successfully'); } else if (libraryBtn && libraryBtn.hasAttribute('data-handler-attached')) { @@ -3916,22 +3648,8 @@ if (libraryBtn) { libraryBtn.onclick = function(e) { e.preventDefault(); - - // Show library screen directly - const libraryScreen = document.getElementById('library-screen'); - const startScreen = document.getElementById('start-screen'); - - if (libraryScreen && startScreen) { - startScreen.classList.remove('active'); - libraryScreen.classList.add('active'); - - // Set up library handlers - setTimeout(() => { - setupLibraryHandlers(); - }, 100); - } else { - console.error('Could not find library or start screen'); - } + console.log('๐Ÿ“š Library button clicked - navigating to library.html'); + window.location.href = 'library.html'; }; } } diff --git a/library.html b/library.html new file mode 100644 index 0000000..a0f7410 --- /dev/null +++ b/library.html @@ -0,0 +1,1000 @@ + + + + + + Media Library - Gooner Training + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + +
+ + +
+
+

๐Ÿ–ผ๏ธ Image Library Management

+

Link directories from your computer to access image content

+ + +
+

๐Ÿ“ Linked Image Directories

+
+ + + + + 0 directories linked +
+
+
+
No image directories linked yet
+
+
+
+ + + +
+
+ + +
+
+

๐ŸŽต Audio Library Management

+

Organize your background music and ambient sounds

+ + +
+

๐ŸŽต Import Audio Files

+
+ + + +
+
+ ๐Ÿ’ป Desktop: Native file dialogs โ€ข Supports MP3, WAV, OGG, M4A formats +
+
+ + + +
+
+ + + +
+
+ + +
+
+

๐ŸŽฌ Video Library Management

+

Manage your video content for enhanced training sessions

+ + +
+

๐Ÿ“ Linked Video Directories

+

Link directories from your computer to access video content

+
+ + + + + 0 directories linked +
+
+
+
No video directories linked yet
+
+
+
+ + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/porn-cinema.html b/porn-cinema.html index f2b2cfe..7473767 100644 --- a/porn-cinema.html +++ b/porn-cinema.html @@ -239,91 +239,117 @@ + + diff --git a/scripts/create-distribution.bat b/scripts/create-distribution.bat index 7ab7d0e..a8b9d85 100644 --- a/scripts/create-distribution.bat +++ b/scripts/create-distribution.bat @@ -5,7 +5,7 @@ echo ================================================= echo. :: Set distribution info -set DIST_NAME=Gooner-Training-Academy-v4.0-Beta +set DIST_NAME=Gooner-Training-Academy-v4.1-Beta set BUILD_DATE=%DATE:~-4,4%-%DATE:~-10,2%-%DATE:~-7,2% set OUTPUT_DIR=..\%DIST_NAME% diff --git a/src/core/main.js b/src/core/main.js index 4a1f7aa..c1032f8 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -330,6 +330,24 @@ ipcMain.handle('delete-file', async (event, filePath) => { } }); +// Save base64 image to file +ipcMain.handle('save-base64-image', async (event, filePath, base64Data) => { + try { + // Ensure the directory exists + await fs.mkdir(path.dirname(filePath), { recursive: true }); + + // Convert base64 to buffer and write to file + const buffer = Buffer.from(base64Data, 'base64'); + await fs.writeFile(filePath, buffer); + + console.log(`๐Ÿ“ธ Saved image: ${filePath}`); + return true; + } catch (error) { + console.error('Error saving base64 image:', error); + return false; + } +}); + // Audio-specific IPC handlers ipcMain.handle('select-audio', async () => { const result = await dialog.showOpenDialog(mainWindow, { diff --git a/src/core/preload.js b/src/core/preload.js index c544820..805963e 100644 --- a/src/core/preload.js +++ b/src/core/preload.js @@ -28,6 +28,7 @@ contextBridge.exposeInMainWorld('electronAPI', { fileExists: (filePath) => ipcRenderer.invoke('file-exists', filePath), createDirectory: (dirPath) => ipcRenderer.invoke('create-directory', dirPath), deleteFile: (filePath) => ipcRenderer.invoke('delete-file', filePath), + saveBase64Image: (filePath, base64Data) => ipcRenderer.invoke('save-base64-image', filePath, base64Data), // Platform info platform: process.platform, diff --git a/src/features/media/pornCinema.js b/src/features/media/pornCinema.js index 6cea1e1..d1edaf7 100644 Binary files a/src/features/media/pornCinema.js and b/src/features/media/pornCinema.js differ diff --git a/src/styles/styles.css b/src/styles/styles.css index 578ccd2..ee91cca 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -2854,7 +2854,8 @@ body.theme-monochrome { #lib-video-gallery .video-info { padding: 8px 6px !important; /* Increased padding for more space */ background: rgba(0, 0, 0, 0.9) !important; /* Darker background for better contrast */ - height: 75px !important; /* Increased height for better text display */ + height: auto !important; /* Auto height to fit content */ + min-height: 75px !important; /* Minimum height for consistency */ overflow: hidden !important; display: flex !important; flex-direction: column !important; @@ -2862,7 +2863,15 @@ body.theme-monochrome { border-top: 1px solid rgba(255, 255, 255, 0.2) !important; } -#lib-video-gallery .video-name { +#lib-video-gallery .video-details { + display: flex !important; + flex-direction: column !important; + gap: 4px !important; + width: 100% !important; +} + +#lib-video-gallery .video-name, +#lib-video-gallery .video-title { font-size: 12px !important; /* Slightly larger font for better visibility */ color: #ffffff !important; /* Bright white for visibility */ margin: 0 0 3px 0 !important; /* Increased bottom margin */ @@ -2873,7 +2882,8 @@ body.theme-monochrome { white-space: nowrap !important; } -#lib-video-gallery .video-directory { +#lib-video-gallery .video-directory, +#lib-video-gallery .video-meta { font-size: 10px !important; /* Slightly larger for better readability */ color: #e0e0e0 !important; /* Brighter light gray for better visibility */ opacity: 1 !important; /* Remove opacity to ensure visibility */ @@ -2881,7 +2891,14 @@ body.theme-monochrome { text-overflow: ellipsis !important; white-space: nowrap !important; line-height: 1.1 !important; - margin: 0 !important; + margin: 0 0 4px 0 !important; +} + +#lib-video-gallery .btn-small { + font-size: 10px !important; + padding: 4px 8px !important; + margin-top: 0 !important; +} } /* Override conflicting gallery item styles for video items - COMPACT LAYOUT */ @@ -3098,7 +3115,8 @@ body.theme-monochrome { padding: var(--space-sm); } -.video-name { +.video-name, +.video-title { font-weight: 600; font-size: var(--font-sm); color: var(--text-primary); @@ -6085,19 +6103,42 @@ button#start-mirror-btn:disabled { #lib-video-gallery .video-info { padding: 10px !important; background: var(--bg-card) !important; + display: flex !important; + flex-direction: column !important; } -#lib-video-gallery .video-name { +#lib-video-gallery .video-details { + display: flex !important; + flex-direction: column !important; + gap: 4px !important; + width: 100% !important; +} + +#lib-video-gallery .video-name, +#lib-video-gallery .video-title { font-size: var(--font-sm) !important; color: var(--text-primary) !important; margin-bottom: 4px !important; font-weight: 500 !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; } -#lib-video-gallery .video-directory { +#lib-video-gallery .video-directory, +#lib-video-gallery .video-meta { font-size: var(--font-xs) !important; color: var(--text-secondary) !important; opacity: 0.7 !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; +} + +#lib-video-gallery .btn-small { + font-size: var(--font-xs) !important; + padding: 4px 8px !important; + margin-top: 4px !important; } /* ALSO APPLY ALL VIDEO STYLING TO UNIFIED-VIDEO-GALLERY */ diff --git a/src/utils/desktop-file-manager.js b/src/utils/desktop-file-manager.js index d36cf6a..0e7d5ab 100644 --- a/src/utils/desktop-file-manager.js +++ b/src/utils/desktop-file-manager.js @@ -17,6 +17,8 @@ class DesktopFileManager { rewards: null, punishments: 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 @@ -44,6 +46,9 @@ class DesktopFileManager { this.videoDirectories.rewards = await window.electronAPI.pathJoin(this.appPath, 'videos', 'rewards'); this.videoDirectories.punishments = await window.electronAPI.pathJoin(this.appPath, 'videos', 'punishments'); + 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.imageDirectories.tasks); await window.electronAPI.createDirectory(this.imageDirectories.consequences); @@ -51,6 +56,9 @@ class DesktopFileManager { await window.electronAPI.createDirectory(this.audioDirectories.background); await window.electronAPI.createDirectory(this.audioDirectories.ambient); + 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 @@ -58,6 +66,8 @@ class DesktopFileManager { console.log('App path:', this.appPath); console.log('Image directories:', this.imageDirectories); console.log('Audio directories:', this.audioDirectories); + console.log('Photo directory:', this.photoDirectory); + console.log('Video recording directory:', this.videoRecordingDirectory); // Load any previously linked external directories await this.loadLinkedDirectories(); @@ -968,6 +978,193 @@ class DesktopFileManager { .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}`; diff --git a/src/utils/libraryManager.js b/src/utils/libraryManager.js new file mode 100644 index 0000000..868e650 --- /dev/null +++ b/src/utils/libraryManager.js @@ -0,0 +1,1586 @@ +// Library Manager - All library-related functions extracted from index.html +// This file contains functions for managing Images, Audio, Video, and Gallery tabs + +async function setupLibraryImagesTab(retryCount = 0) { + console.log('Setting up images tab functionality...'); + + // Wait for game to be available (max 10 retries) + if (!window.game && retryCount < 10) { + console.log(`โณ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`); + setTimeout(() => setupLibraryImagesTab(retryCount + 1), 500); + return; + } + + if (!window.game) { + console.error('โŒ Game not available after 10 retries, aborting image tab setup'); + return; + } + + // Get current image stats from multiple possible locations + let taskImages = []; + let consequenceImages = []; + + // Try dataManager first (most likely location based on console output) + if (window.game.dataManager && window.game.dataManager.gameData) { + taskImages = window.game.dataManager.gameData.taskImages || []; + consequenceImages = window.game.dataManager.gameData.consequenceImages || []; + } + + // Try gameData directly + if (taskImages.length === 0 && window.game.gameData) { + taskImages = window.game.gameData.taskImages || []; + consequenceImages = window.game.gameData.consequenceImages || []; + } + + console.log(`๐Ÿ“Š Found ${taskImages.length} task images, ${consequenceImages.length} consequence images`); + + // Update image count display + const imageCountElement = document.getElementById('lib-image-count'); + if (imageCountElement) { + imageCountElement.textContent = `${taskImages.length + consequenceImages.length} images`; + } + + // Populate image gallery + const imageGallery = document.getElementById('lib-image-gallery'); + if (imageGallery && (taskImages.length > 0 || consequenceImages.length > 0)) { + imageGallery.innerHTML = ''; + + // Add task images + taskImages.forEach((image, index) => { + const imgElement = document.createElement('div'); + imgElement.className = 'gallery-item'; + imgElement.innerHTML = ` + Task Image ${index + 1} + + `; + imageGallery.appendChild(imgElement); + }); + + // Add consequence images + consequenceImages.forEach((image, index) => { + const imgElement = document.createElement('div'); + imgElement.className = 'gallery-item'; + imgElement.innerHTML = ` + Consequence Image ${index + 1} + + `; + imageGallery.appendChild(imgElement); + }); + + console.log(`โœ… Created ${taskImages.length + consequenceImages.length} image gallery items`); + } else if (imageGallery) { + imageGallery.innerHTML = ` +
+

๐Ÿ—ƒ๏ธ No images found

+

Import images to get started

+
+ `; + } + + // Set up image directory management buttons + const addImageDirBtn = document.getElementById('lib-add-image-directory-btn'); + const addIndividualImagesBtn = document.getElementById('lib-add-individual-images-btn'); + const refreshImageDirBtn = document.getElementById('lib-refresh-image-directories-btn'); + const clearImageDirBtn = document.getElementById('lib-clear-image-directories-btn'); + + if (addImageDirBtn) { + addImageDirBtn.onclick = () => { + console.log('Adding image directory...'); + handleAddImageDirectory(); + }; + } + + if (addIndividualImagesBtn) { + addIndividualImagesBtn.onclick = () => { + console.log('Adding individual images...'); + handleAddIndividualImages(); + }; + } + + if (refreshImageDirBtn) { + refreshImageDirBtn.onclick = () => { + console.log('Refreshing image directories...'); + handleRefreshImageDirectories(); + }; + } + + if (clearImageDirBtn) { + clearImageDirBtn.onclick = () => { + console.log('Clearing image directories...'); + handleClearImageDirectories(); + }; + } + + // Set up category filter dropdown + const categoryFilter = document.getElementById('lib-image-category-filter'); + if (categoryFilter) { + categoryFilter.onchange = () => { + console.log('Filtering images by category:', categoryFilter.value); + // Use stored images and apply filter + if (window.allLinkedImages) { + const filteredImages = filterImagesByCategory(window.allLinkedImages, categoryFilter.value); + populateImageGallery(filteredImages); + + // Update count display + const countElement = document.getElementById('lib-image-count'); + if (countElement) { + countElement.textContent = `${filteredImages.length} images`; + } + } + }; + } + + // Initialize linked image directories display + updateImageDirectoriesList(); + + // Check if we have linked directories first + const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]'); + + console.log('๐Ÿ“ Checking for linked directories...', linkedDirs.length); + console.log('๐Ÿ“ electronAPI available:', !!window.electronAPI); + + if (linkedDirs.length > 0) { + console.log('๐Ÿ“ Using linked directories instead of built-in directories'); + await loadLinkedImages(); + return; // Skip built-in directory scanning + } else { + console.log('๐Ÿ“ No linked directories found, using built-in directories'); + } +} + +function setupLibraryAudioTab(retryCount = 0) { + console.log('Setting up audio tab functionality...'); + + // Wait for game to be available (max 10 retries) + if (!window.game && retryCount < 10) { + console.log(`โณ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`); + setTimeout(() => setupLibraryAudioTab(retryCount + 1), 500); + return; + } + + if (!window.game) { + console.error('โŒ Game not available after 10 retries, aborting audio tab setup'); + return; + } + + // Get current audio stats from multiple possible locations + let audioLibrary = {}; + let backgroundTracks = []; + let ambientTracks = []; + + // Try audioManager first + if (window.game.audioManager && window.game.audioManager.audioLibrary) { + audioLibrary = window.game.audioManager.audioLibrary; + backgroundTracks = audioLibrary.background || []; + ambientTracks = audioLibrary.ambient || []; + } + + // If audioLibrary is empty, try checking if tracks are stored differently + if (backgroundTracks.length === 0 && window.game.audioManager) { + // Check if background tracks are stored in a different property + backgroundTracks = window.game.audioManager.backgroundTracks || + window.game.audioManager.background || []; + } + + if (ambientTracks.length === 0 && window.game.audioManager) { + // Check if ambient tracks are stored in a different property + ambientTracks = window.game.audioManager.ambientTracks || + window.game.audioManager.ambient || []; + } + + console.log(`๐Ÿ“Š Found ${backgroundTracks.length} background tracks, ${ambientTracks.length} ambient tracks`); + console.log('๐Ÿ“ Audio manager available:', !!window.game.audioManager); + + // Store audio tracks globally for filtering + window.allAudioTracks = { background: backgroundTracks, ambient: ambientTracks }; + + // Apply current filter and populate the gallery + const categoryFilter = document.getElementById('lib-audio-category-filter'); + const selectedCategory = categoryFilter ? categoryFilter.value : 'all'; + const filteredTracks = filterAudioByCategory(backgroundTracks, ambientTracks, selectedCategory); + populateAudioGallery(filteredTracks.background, filteredTracks.ambient); + + // Update count display + const audioCountElement = document.getElementById('lib-audio-count'); + if (audioCountElement) { + const totalCount = filteredTracks.background.length + filteredTracks.ambient.length; + audioCountElement.textContent = `${totalCount} files`; + } + + // Set up category filter dropdown + if (categoryFilter) { + categoryFilter.onchange = () => { + console.log('Filtering audio by category:', categoryFilter.value); + // Use stored audio tracks and apply filter + if (window.allAudioTracks) { + const filtered = filterAudioByCategory( + window.allAudioTracks.background, + window.allAudioTracks.ambient, + categoryFilter.value + ); + populateAudioGallery(filtered.background, filtered.ambient); + + // Update count display + const totalCount = filtered.background.length + filtered.ambient.length; + audioCountElement.textContent = `${totalCount} files`; + } + }; + } +} + +function filterAudioByCategory(backgroundTracks, ambientTracks, category) { + switch (category) { + case 'background': + return { background: backgroundTracks, ambient: [] }; + case 'ambient': + return { background: [], ambient: ambientTracks }; + case 'all': + default: + return { background: backgroundTracks, ambient: ambientTracks }; + } +} + +function populateAudioGallery(backgroundTracks, ambientTracks) { + const audioGallery = document.getElementById('lib-audio-gallery'); + if (!audioGallery) return; + + if (backgroundTracks.length === 0 && ambientTracks.length === 0) { + audioGallery.innerHTML = ` +
+

๐ŸŽต No audio files found

+

Import audio to get started

+
+ `; + return; + } + + audioGallery.innerHTML = ''; + + // Add background tracks + backgroundTracks.forEach((track, index) => { + const audioElement = document.createElement('div'); + audioElement.className = 'gallery-item audio-item'; + audioElement.innerHTML = ` +
+
๐ŸŽต
+
${track.name || `Track ${index + 1}`}
+
Background
+
+ `; + audioGallery.appendChild(audioElement); + }); + + // Add ambient tracks + ambientTracks.forEach((track, index) => { + const audioElement = document.createElement('div'); + audioElement.className = 'gallery-item audio-item'; + audioElement.innerHTML = ` +
+
๐ŸŒŠ
+
${track.name || `Ambient ${index + 1}`}
+
Ambient
+
+ `; + audioGallery.appendChild(audioElement); + }); + + console.log(`โœ… Created ${backgroundTracks.length + ambientTracks.length} audio gallery items`); +} + +async function setupLibraryVideoTab(retryCount = 0) { + console.log('Setting up video tab functionality...'); + + // Wait for game to be available (max 10 retries) + if (!window.game && retryCount < 10) { + console.log(`โณ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`); + setTimeout(() => setupLibraryVideoTab(retryCount + 1), 500); + return; + } + + if (!window.game) { + console.error('โŒ Game not available after 10 retries, aborting video tab setup'); + return; + } + + console.log('๐Ÿ“ Video manager types:', Object.keys(window.game).filter(key => key.toLowerCase().includes('video'))); + + // Update video count display + const videoCountElement = document.getElementById('lib-video-count'); + if (videoCountElement) { + videoCountElement.textContent = `0 files`; + } + + // Populate video gallery + const videoGallery = document.getElementById('lib-video-gallery'); + if (videoGallery) { + videoGallery.innerHTML = ` +
+

๐ŸŽฌ No video files found

+

Import videos to get started

+
+ `; + } + + // Set up video directory management buttons + const addVideoDirBtn = document.getElementById('lib-add-video-directory-btn'); + const addIndividualVideosBtn = document.getElementById('lib-add-individual-videos-btn'); + const refreshVideoDirBtn = document.getElementById('lib-refresh-video-directories-btn'); + const clearVideoDirBtn = document.getElementById('lib-clear-video-directories-btn'); + + if (addVideoDirBtn) { + addVideoDirBtn.onclick = () => { + console.log('Adding video directory...'); + handleAddVideoDirectory(); + }; + } + + if (addIndividualVideosBtn) { + addIndividualVideosBtn.onclick = () => { + console.log('Adding individual videos...'); + handleAddIndividualVideos(); + }; + } + + if (refreshVideoDirBtn) { + refreshVideoDirBtn.onclick = () => { + console.log('Refreshing video directories...'); + handleRefreshVideoDirectories(); + }; + } + + if (clearVideoDirBtn) { + clearVideoDirBtn.onclick = () => { + console.log('Clearing video directories...'); + handleClearVideoDirectories(); + }; + } + + // Set up category filter dropdown + const categoryFilter = document.getElementById('lib-video-category-filter'); + if (categoryFilter) { + categoryFilter.onchange = () => { + console.log('Filtering videos by category:', categoryFilter.value); + // Use stored videos and apply filter + if (window.allLinkedVideos) { + const filteredVideos = filterVideosByCategory(window.allLinkedVideos, categoryFilter.value); + populateVideoGallery(filteredVideos); + + // Update count display + const videoCountElement = document.getElementById('lib-video-count'); + if (videoCountElement) { + videoCountElement.textContent = `${filteredVideos.length} videos`; + } + } + }; + } + + // Initialize linked video directories display + updateVideoDirectoriesList(); + + // Load linked videos if any exist + let linkedVideoDirs; + let individualVideos; + try { + linkedVideoDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]'); + if (!Array.isArray(linkedVideoDirs)) { + linkedVideoDirs = []; + } + individualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]'); + if (!Array.isArray(individualVideos)) { + individualVideos = []; + } + } catch (e) { + console.log('Error parsing video directories, resetting to empty arrays:', e); + linkedVideoDirs = []; + individualVideos = []; + } + + if (linkedVideoDirs.length > 0 || individualVideos.length > 0) { + console.log('๐Ÿ“ Loading linked video directories...'); + loadLinkedVideos(); + } +} + +function setupLibraryGalleryTab() { + console.log('Setting up gallery tab functionality...'); + + // Load captured photos from localStorage + const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); + console.log(`๐Ÿ“ธ Found ${capturedPhotos.length} captured photos`); + + // Load verification photos from localStorage + const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]'); + console.log(`๐Ÿ“ท Found ${verificationPhotos.length} verification photos`); + + const allPhotosGrid = document.getElementById('lib-all-photos-grid'); + const allPhotosCount = document.getElementById('lib-all-photos-count'); + + if (allPhotosGrid) { + if (capturedPhotos.length === 0 && verificationPhotos.length === 0) { + allPhotosGrid.innerHTML = ` +
+

๐Ÿ“ธ No photos found

+

Take some photos during gameplay to see them here

+
+ `; + if (allPhotosCount) allPhotosCount.textContent = '0 photos'; + } else { + // Create photo gallery grid - start with captured photos + let photosHtml = ''; + capturedPhotos.forEach((photo, index) => { + const timestamp = new Date(photo.timestamp || Date.now()).toLocaleDateString(); + const imageData = photo.imageData || photo.dataURL; // Support both formats + + if (imageData) { + photosHtml += ` +
+
+
+ + +
+ Captured Photo ${index + 1} +
+ + +
+
+ ${timestamp} + ${photo.sessionType || 'Training'} +
+
+
+ `; + } + }); + + // Add verification photos to the gallery + verificationPhotos.forEach((photo, index) => { + const timestamp = new Date(photo.timestamp || Date.now()).toLocaleDateString(); + const photoType = photo.phase === 'start' ? '๐ŸŸข START Position' : '๐Ÿ”ด END Position'; + const degradingMessage = photo.message || 'Position verification photo'; + const verificationIndex = capturedPhotos.length + index; // Offset by captured photos length + const imageData = photo.data || photo.dataUrl; // Support both formats + + if (imageData) { + photosHtml += ` +
+
+
+ + +
+ Verification Photo ${index + 1} +
+ + +
+
+ ${timestamp} + ${photoType} + "${degradingMessage}" +
+
+
+ `; + } + }); + + allPhotosGrid.innerHTML = photosHtml; + const totalPhotos = capturedPhotos.length + verificationPhotos.length; + if (allPhotosCount) allPhotosCount.textContent = `${totalPhotos} photos`; + } + } + + // Initialize bulk action event listeners + setTimeout(initializeBulkActions, 100); +} + +// Delete a photo from the gallery +function deletePhoto(index) { + const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); + + if (index < 0 || index >= capturedPhotos.length) { + console.error('Invalid photo index:', index); + return; + } + + const photo = capturedPhotos[index]; + const photoType = photo.sessionType || 'Training'; + const photoDate = new Date(photo.timestamp || Date.now()).toLocaleDateString(); + + // Show confirmation dialog + const confirmed = confirm(`Are you sure you want to delete this photo?\n\nType: ${photoType}\nDate: ${photoDate}\n\nThis action cannot be undone.`); + + if (confirmed) { + // Remove photo from array + capturedPhotos.splice(index, 1); + + // Update localStorage + localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos)); + + // Show success message + if (window.game && window.game.flashMessageManager) { + window.game.flashMessageManager.show(`๐Ÿ“ธ Photo deleted successfully!`, 'info'); + } + + // Refresh the photo galleries + setupLibraryGalleryTab(); + + console.log(`๐Ÿ—‘๏ธ Deleted photo ${index + 1} (${photoType})`); + } +} + +// Update selection count and enable/disable bulk action buttons +function updateSelectionCount() { + const selectedCheckboxes = document.querySelectorAll('.photo-select:checked'); + const count = selectedCheckboxes.length; + + const selectedCountSpan = document.getElementById('selected-count'); + const downloadBtn = document.getElementById('download-selected-photos'); + const deleteBtn = document.getElementById('delete-selected-photos'); + + if (selectedCountSpan) selectedCountSpan.textContent = `${count} selected`; + + if (downloadBtn) downloadBtn.disabled = count === 0; + if (deleteBtn) deleteBtn.disabled = count === 0; +} + +// Select all photos +function selectAllPhotos() { + const checkboxes = document.querySelectorAll('.photo-select'); + checkboxes.forEach(checkbox => { + checkbox.checked = true; + }); + updateSelectionCount(); +} + +// Deselect all photos +function deselectAllPhotos() { + const checkboxes = document.querySelectorAll('.photo-select'); + checkboxes.forEach(checkbox => { + checkbox.checked = false; + }); + updateSelectionCount(); +} + +// Download single photo +function downloadSinglePhoto(index) { + const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); + + if (index < 0 || index >= capturedPhotos.length) { + console.error('Invalid photo index:', index); + return; + } + + const photo = capturedPhotos[index]; + + // Handle different photo data structures + let imageData; + if (photo.data) { + // Verification photos store in 'data' property + imageData = photo.data; + } else if (photo.dataURL) { + // Regular photos store in 'dataURL' property + imageData = photo.dataURL; + } else if (photo.imageData) { + // Fallback for other formats + imageData = photo.imageData; + } else { + console.error('Photo missing image data:', photo); + return; + } + + const timestamp = new Date(photo.timestamp || Date.now()).toISOString().slice(0, 19).replace(/:/g, '-'); + const filename = `photo-${timestamp}.png`; + + // Create download link + const link = document.createElement('a'); + link.href = imageData; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + if (window.game && window.game.flashMessageManager) { + window.game.flashMessageManager.show(`๐Ÿ“ฅ Photo downloaded: ${filename}`, 'info'); + } + console.log(`๐Ÿ“ฅ Downloaded photo: ${filename}`); +} + +// Download single verification photo +function downloadVerificationPhoto(index) { + const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]'); + + if (index < 0 || index >= verificationPhotos.length) { + console.error('Invalid verification photo index:', index); + return; + } + + const photo = verificationPhotos[index]; + const timestamp = new Date(photo.timestamp || Date.now()); + const dateStr = timestamp.toISOString().split('T')[0]; + const timeStr = timestamp.toTimeString().split(' ')[0].replace(/:/g, '-'); + const photoType = photo.phase === 'start' ? 'START' : 'END'; + const imageData = photo.data || photo.dataUrl; // Support both formats + + if (!imageData) { + console.error('No image data found for verification photo:', photo); + return; + } + + // Create download link + const link = document.createElement('a'); + link.href = imageData; + link.download = `verification-${photoType}-${dateStr}_${timeStr}.png`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + if (window.game && window.game.flashMessageManager) { + window.game.flashMessageManager.show(`๐Ÿ“ฅ Downloaded verification photo: ${photoType}`, 'success'); + } +} + +function deleteVerificationPhoto(index) { + const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]'); + + if (index < 0 || index >= verificationPhotos.length) { + console.error('Invalid verification photo index:', index); + return; + } + + const photo = verificationPhotos[index]; + const photoType = photo.phase === 'start' ? 'START' : 'END'; + const confirmed = confirm(`Are you sure you want to delete this ${photoType} verification photo?\n\nThis action cannot be undone.`); + + if (confirmed) { + // Remove the photo from the array + verificationPhotos.splice(index, 1); + + // Update localStorage + localStorage.setItem('verificationPhotos', JSON.stringify(verificationPhotos)); + + // Refresh the gallery + setupLibraryGalleryTab(); + + if (window.game && window.game.flashMessageManager) { + window.game.flashMessageManager.show(`๐Ÿ—‘๏ธ Deleted verification photo: ${photoType}`, 'info'); + } + + console.log(`๐Ÿ—‘๏ธ Deleted verification photo ${index} (${photoType})`); + } +} + +// Show photo preview in modal +function showPhotoPreview(imageData, title) { + // Create modal overlay + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; + cursor: pointer; + `; + + // Create image element + const img = document.createElement('img'); + img.src = imageData; + img.alt = title; + img.style.cssText = ` + max-width: 90%; + max-height: 90%; + border-radius: 10px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + `; + + // Create title + const titleDiv = document.createElement('div'); + titleDiv.textContent = title; + titleDiv.style.cssText = ` + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + color: white; + font-size: 1.2em; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); + `; + + // Add elements to overlay + overlay.appendChild(img); + overlay.appendChild(titleDiv); + + // Close on click + overlay.addEventListener('click', () => { + document.body.removeChild(overlay); + }); + + // Add to page + document.body.appendChild(overlay); +} + +function showVerificationPhotoPreview(imageData, title, message, isStart, timestamp) { + const photoDate = timestamp ? new Date(timestamp).toLocaleString() : 'Unknown date'; + const photoType = isStart ? '๐ŸŸข START Position' : '๐Ÿ”ด END Position'; + + // Create modal overlay + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.9); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + cursor: pointer; + `; + + overlay.innerHTML = ` +
+
+

${title}

+
+ ${photoType} + ${photoDate} +
+
+

Degrading Message:

+

"${message}"

+
+
+ ${title} + +
+ `; + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + overlay.remove(); + } + }); + + document.body.appendChild(overlay); +} + +// Initialize bulk action event listeners +function initializeBulkActions() { + const selectAllBtn = document.getElementById('select-all-photos'); + const deselectAllBtn = document.getElementById('deselect-all-photos'); + const downloadSelectedBtn = document.getElementById('download-selected-photos'); + const deleteSelectedBtn = document.getElementById('delete-selected-photos'); + + if (selectAllBtn) selectAllBtn.addEventListener('click', selectAllPhotos); + if (deselectAllBtn) deselectAllBtn.addEventListener('click', deselectAllPhotos); + if (downloadSelectedBtn) downloadSelectedBtn.addEventListener('click', () => { + console.log('Download selected photos functionality to be implemented'); + }); + if (deleteSelectedBtn) deleteSelectedBtn.addEventListener('click', () => { + console.log('Delete selected photos functionality to be implemented'); + }); +} + +// Image Directory Management Functions +function handleAddImageDirectory() { + console.log('Adding new image directory...'); + + if (window.electronAPI && window.electronAPI.selectDirectory) { + console.log('๐Ÿ“ Calling electronAPI.selectDirectory...'); + + try { + const result = window.electronAPI.selectDirectory(); + console.log('๐Ÿ“ selectDirectory returned:', result, typeof result); + + // Handle both sync and async results + if (result && typeof result.then === 'function') { + result.then((directoryResult) => { + console.log('Directory selection result (async):', directoryResult); + handleDirectoryResult(directoryResult); + }).catch(error => { + console.error('Error selecting image directory (async):', error); + if (window.game && window.game.showNotification) { + window.game.showNotification('โŒ Failed to select directory', 'error'); + } + }); + } else { + console.log('Directory selection result (sync):', result); + handleDirectoryResult(result); + } + } catch (error) { + console.error('Error calling selectDirectory:', error); + if (window.game && window.game.showNotification) { + window.game.showNotification('โŒ Failed to open directory dialog', 'error'); + } + } + } else { + console.log('โŒ electronAPI.selectDirectory not available'); + if (window.game && window.game.showNotification) { + window.game.showNotification('Directory linking is only available in desktop mode', 'warning'); + } + } +} + +function handleDirectoryResult(result) { + console.log('๐Ÿ“ Processing directory result:', result); + + let selectedPath = null; + + if (result && !result.canceled) { + if (result.filePaths && result.filePaths.length > 0) { + selectedPath = result.filePaths[0]; + } else if (result.filePath) { + selectedPath = result.filePath; + } else if (typeof result === 'string') { + selectedPath = result; + } else if (result.paths && result.paths.length > 0) { + selectedPath = result.paths[0]; + } + } + + if (selectedPath) { + console.log('Selected image directory:', selectedPath); + addImageDirectory(selectedPath); + } else { + console.log('No directory selected or selection was canceled'); + } +} + +function addImageDirectory(directoryPath) { + const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]'); + + if (linkedDirs.some(dir => dir.path === directoryPath)) { + if (window.game && window.game.showNotification) { + window.game.showNotification('Directory already linked', 'warning'); + } + return; + } + + const newDir = { + id: Date.now().toString(), + path: directoryPath, + name: directoryPath.split('\\').pop() || directoryPath.split('/').pop(), + addedAt: new Date().toISOString() + }; + + linkedDirs.push(newDir); + localStorage.setItem('linkedImageDirectories', JSON.stringify(linkedDirs)); + + console.log('Added image directory:', newDir); + + updateImageDirectoriesList(); + loadLinkedImages(); + + if (window.game && window.game.showNotification) { + window.game.showNotification(`โœ… Added image directory: ${newDir.name}`, 'success'); + } +} + +function handleAddIndividualImages() { + console.log('Adding individual images - feature to be implemented'); + if (window.game && window.game.showNotification) { + window.game.showNotification('Individual image selection coming soon', 'info'); + } +} + +function handleRefreshImageDirectories() { + console.log('Refreshing image directories...'); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ”„ Refreshing image directories...', 'info'); + } + + loadLinkedImages(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('โœ… Image directories refreshed!', 'success'); + } +} + +function handleClearImageDirectories() { + if (!confirm('Are you sure you want to unlink all image directories? This will not delete your actual image files.')) { + return; + } + + console.log('Clearing all image directories...'); + + localStorage.removeItem('linkedImageDirectories'); + localStorage.removeItem('linkedIndividualImages'); + + updateImageDirectoriesList(); + loadLinkedImages(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ—‘๏ธ All image directories unlinked', 'info'); + } +} + +function removeImageDirectory(directoryId) { + const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]'); + const updatedDirs = linkedDirs.filter(dir => dir.id !== directoryId); + + localStorage.setItem('linkedImageDirectories', JSON.stringify(updatedDirs)); + + updateImageDirectoriesList(); + loadLinkedImages(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ—‘๏ธ Image directory unlinked', 'info'); + } +} + +function updateImageDirectoriesList() { + const listContainer = document.getElementById('linked-image-directories-list'); + if (!listContainer) return; + + const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]'); + + const dirCountElement = document.getElementById('lib-directories-count'); + if (dirCountElement) { + dirCountElement.textContent = `${linkedDirs.length} directories linked`; + } + + if (linkedDirs.length === 0) { + listContainer.innerHTML = '
No image directories linked yet
'; + return; + } + + listContainer.innerHTML = linkedDirs.map(dir => ` +
+
+
${dir.name}
+
${dir.path}
+
+ +
+ `).join(''); +} + +async function loadLinkedImages() { + const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]'); + let allImages = []; + + console.log(`๐Ÿ“ Loading images from ${linkedDirs.length} linked directories...`); + console.log('๐Ÿ“ electronAPI available:', !!window.electronAPI); + console.log('๐Ÿ“ readDirectory available:', !!(window.electronAPI && window.electronAPI.readDirectory)); + + if (window.electronAPI && linkedDirs.length > 0) { + const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i; + + for (const dir of linkedDirs) { + console.log(`๐Ÿ“ Scanning directory: ${dir.path}`); + try { + if (window.electronAPI.readImageDirectoryRecursive) { + const filesPromise = window.electronAPI.readImageDirectoryRecursive(dir.path); + + let files = []; + if (filesPromise && typeof filesPromise.then === 'function') { + files = await filesPromise; + } else if (Array.isArray(filesPromise)) { + files = filesPromise; + } + + if (files && files.length > 0) { + const imageFiles = files.filter(file => { + const fileName = typeof file === 'object' ? file.name : file; + return imageExtensions.test(fileName); + }); + + if (imageFiles.length > 0) { + const dirImages = imageFiles.map(file => { + if (typeof file === 'object' && file.name && file.path) { + return { + path: file.path, + name: file.name, + directory: dir.name, + directoryId: dir.id + }; + } else { + const fileName = typeof file === 'object' ? file.name : file; + const fullPath = window.electronAPI.pathJoin ? window.electronAPI.pathJoin(dir.path, fileName) : `${dir.path}\\${fileName}`; + return { + path: fullPath, + name: fileName, + directory: dir.name, + directoryId: dir.id + }; + } + }); + allImages = allImages.concat(dirImages); + console.log(`๐Ÿ“ธ Added ${dirImages.length} images from ${dir.name}`); + } + } + } + } catch (error) { + console.error(`Error scanning directory ${dir.path}:`, error); + } + } + } + + console.log(`๐Ÿ“ธ Total images found: ${allImages.length}`); + + const imageCountElement = document.getElementById('lib-image-count'); + if (imageCountElement) { + imageCountElement.textContent = `${allImages.length} images`; + } + + window.allLinkedImages = allImages; + + const categoryFilter = document.getElementById('lib-image-category-filter'); + const selectedCategory = categoryFilter ? categoryFilter.value : 'all'; + const filteredImages = filterImagesByCategory(allImages, selectedCategory); + populateImageGallery(filteredImages); +} + +function filterImagesByCategory(images, category) { + if (category === 'all') { + return images; + } + + // File type filtering + if (category === 'jpg' || category === 'png' || category === 'gif') { + return images.filter(image => { + const extension = image.path.toLowerCase().split('.').pop(); + if (category === 'jpg') { + return extension === 'jpg' || extension === 'jpeg'; + } + return extension === category; + }); + } + + // Category filtering based on directory names + return images.filter(image => { + const dirName = image.directory.toLowerCase(); + switch (category) { + case 'tasks': + return dirName.includes('task'); + case 'consequences': + return dirName.includes('consequence'); + case 'rewards': + return dirName.includes('reward'); + case 'verification': + return image.type === 'verification'; + default: + return true; + } + }); +} + +function populateImageGallery(images) { + const imageGallery = document.getElementById('lib-image-gallery'); + if (!imageGallery) { + console.error('โŒ lib-image-gallery element not found!'); + return; + } + + console.log(`๐Ÿ“ธ Populating gallery with ${images.length} images`); + + if (images.length === 0) { + imageGallery.innerHTML = ` +
+

๐Ÿ—ƒ๏ธ No images found in linked directories

+

Click "Add Directory" to link a folder containing images

+
+ `; + return; + } + + imageGallery.innerHTML = ''; + + images.forEach((image, index) => { + const imgElement = document.createElement('div'); + imgElement.className = 'gallery-item image-item'; + + imgElement.innerHTML = ` + ${image.name} +
+
${image.name}
+
${image.directory}
+
+ `; + + imgElement.addEventListener('click', function() { + previewImage(image.path, image.name); + }); + + imageGallery.appendChild(imgElement); + }); + + console.log(`โœ… Created ${images.length} image gallery items`); +} + +function previewImage(imageUrl, imageName) { + let modal = document.getElementById('image-preview-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.id = 'image-preview-modal'; + modal.className = 'image-preview-modal'; + modal.style.cssText = ` + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.95); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + `; + modal.innerHTML = ` +
+
${imageName}
+ ${imageName} + +
+ `; + document.body.appendChild(modal); + + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.remove(); + } + }); + } else { + modal.querySelector('img').src = imageUrl; + modal.querySelector('img').alt = imageName; + modal.querySelector('div > div').textContent = imageName; + modal.style.display = 'flex'; + } +} + +// Video filtering and gallery functions +function filterVideosByCategory(videos, category) { + if (category === 'all') { + return videos; + } + + // Category filtering based on directory names or file names + return videos.filter(video => { + const dirName = video.directory.toLowerCase(); + const fileName = video.name.toLowerCase(); + + switch (category) { + case 'training': + return dirName.includes('train') || fileName.includes('train'); + case 'background': + return dirName.includes('background') || fileName.includes('background') || dirName.includes('bg'); + default: + return true; + } + }); +} + +function populateVideoGallery(videos) { + const videoGallery = document.getElementById('lib-video-gallery'); + if (!videoGallery) { + console.error('โŒ lib-video-gallery element not found!'); + return; + } + + console.log(`๐ŸŽฌ Populating gallery with ${videos.length} videos`); + + if (videos.length === 0) { + videoGallery.innerHTML = ` +
+

๐ŸŽฌ No videos found in linked directories

+

Click "Add Directory" to link a folder containing videos

+
+ `; + return; + } + + videoGallery.innerHTML = ''; + + videos.forEach((video, index) => { + const videoElement = document.createElement('div'); + videoElement.className = 'video-item'; + videoElement.setAttribute('data-video-index', index); + videoElement.setAttribute('data-video-name', video.name); + videoElement.setAttribute('data-video-url', video.path); + + videoElement.innerHTML = ` +
+ +
+
+
+
${video.name}
+
+ ๐Ÿ“ ${video.directory} +
+ +
+
+ `; + + // Add click handler to the preview button + const previewBtn = videoElement.querySelector('.btn-small'); + previewBtn.addEventListener('click', function(e) { + e.stopPropagation(); + previewVideo(video.path, video.name); + }); + + videoGallery.appendChild(videoElement); + }); + + console.log(`โœ… Created ${videos.length} video gallery items`); +} + +function previewVideo(videoUrl, videoName) { + let modal = document.getElementById('video-preview-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.id = 'video-preview-modal'; + modal.className = 'video-preview-modal'; + modal.style.cssText = ` + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.95); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + `; + modal.innerHTML = ` +
+
${videoName}
+ + +
+ `; + document.body.appendChild(modal); + + modal.addEventListener('click', (e) => { + if (e.target === modal) { + const video = modal.querySelector('video'); + if (video) video.pause(); + modal.remove(); + } + }); + } else { + const video = modal.querySelector('video'); + video.src = videoUrl; + video.load(); + video.play(); + modal.querySelector('div > div').textContent = videoName; + modal.style.display = 'flex'; + } +} + +// Video Directory Management Functions +function handleAddVideoDirectory() { + console.log('Adding new video directory...'); + + if (window.electronAPI && window.electronAPI.selectDirectory) { + console.log('๐Ÿ“ Calling electronAPI.selectDirectory...'); + + try { + const result = window.electronAPI.selectDirectory(); + console.log('๐Ÿ“ selectDirectory returned:', result, typeof result); + + // Handle both sync and async results + if (result && typeof result.then === 'function') { + result.then((directoryResult) => { + console.log('Directory selection result (async):', directoryResult); + handleVideoDirectoryResult(directoryResult); + }).catch(error => { + console.error('Error selecting video directory (async):', error); + if (window.game && window.game.showNotification) { + window.game.showNotification('โŒ Failed to select directory', 'error'); + } + }); + } else { + console.log('Directory selection result (sync):', result); + handleVideoDirectoryResult(result); + } + } catch (error) { + console.error('Error calling selectDirectory:', error); + if (window.game && window.game.showNotification) { + window.game.showNotification('โŒ Failed to open directory dialog', 'error'); + } + } + } else { + console.log('โŒ electronAPI.selectDirectory not available'); + if (window.game && window.game.showNotification) { + window.game.showNotification('Directory linking is only available in desktop mode', 'warning'); + } + } +} + +function handleVideoDirectoryResult(result) { + console.log('๐Ÿ“ Processing video directory result:', result); + + let selectedPath = null; + + if (result && !result.canceled) { + if (result.filePaths && result.filePaths.length > 0) { + selectedPath = result.filePaths[0]; + } else if (result.filePath) { + selectedPath = result.filePath; + } else if (typeof result === 'string') { + selectedPath = result; + } else if (result.paths && result.paths.length > 0) { + selectedPath = result.paths[0]; + } + } + + if (selectedPath) { + console.log('Selected video directory:', selectedPath); + addVideoDirectory(selectedPath); + } else { + console.log('No directory selected or selection was canceled'); + } +} + +function addVideoDirectory(directoryPath) { + const linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]'); + + if (linkedDirs.some(dir => dir.path === directoryPath)) { + if (window.game && window.game.showNotification) { + window.game.showNotification('Directory already linked', 'warning'); + } + return; + } + + const newDir = { + id: Date.now().toString(), + path: directoryPath, + name: directoryPath.split('\\').pop() || directoryPath.split('/').pop(), + addedAt: new Date().toISOString() + }; + + linkedDirs.push(newDir); + localStorage.setItem('linkedVideoDirectories', JSON.stringify(linkedDirs)); + + console.log('Added video directory:', newDir); + + updateVideoDirectoriesList(); + loadLinkedVideos(); + + if (window.game && window.game.showNotification) { + window.game.showNotification(`โœ… Added video directory: ${newDir.name}`, 'success'); + } +} + +function handleAddIndividualVideos() { + console.log('Adding individual videos - feature to be implemented'); + if (window.game && window.game.showNotification) { + window.game.showNotification('Individual video selection coming soon', 'info'); + } +} + +function handleRefreshVideoDirectories() { + console.log('Refreshing video directories...'); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ”„ Refreshing video directories...', 'info'); + } + + loadLinkedVideos(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('โœ… Video directories refreshed!', 'success'); + } +} + +function handleClearVideoDirectories() { + if (!confirm('Are you sure you want to unlink all video directories? This will not delete your actual video files.')) { + return; + } + + localStorage.removeItem('linkedVideoDirectories'); + localStorage.removeItem('linkedIndividualVideos'); + + updateVideoDirectoriesList(); + loadLinkedVideos(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ—‘๏ธ All video directories unlinked', 'info'); + } +} + +function updateVideoDirectoriesList() { + const listContainer = document.getElementById('linked-video-directories-list'); + if (!listContainer) return; + + const linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]'); + + const dirCountElement = document.getElementById('lib-video-directories-count'); + if (dirCountElement) { + dirCountElement.textContent = `${linkedDirs.length} directories linked`; + } + + if (linkedDirs.length === 0) { + listContainer.innerHTML = '
No video directories linked yet
'; + return; + } + + listContainer.innerHTML = linkedDirs.map(dir => ` +
+
+
๐Ÿ“ ${dir.name}
+
${dir.path}
+
+ +
+ `).join(''); +} + +function removeVideoDirectory(directoryId) { + const linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]'); + const updatedDirs = linkedDirs.filter(dir => dir.id !== directoryId); + + localStorage.setItem('linkedVideoDirectories', JSON.stringify(updatedDirs)); + + updateVideoDirectoriesList(); + loadLinkedVideos(); + + if (window.game && window.game.showNotification) { + window.game.showNotification('๐Ÿ—‘๏ธ Video directory unlinked', 'info'); + } +} + +async function loadLinkedVideos() { + const linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]'); + let allVideos = []; + + console.log(`๐ŸŽฌ Loading videos from ${linkedDirs.length} linked directories...`); + console.log('๐ŸŽฌ electronAPI available:', !!window.electronAPI); + console.log('๐ŸŽฌ readVideoDirectory available:', !!(window.electronAPI && window.electronAPI.readVideoDirectory)); + + if (window.electronAPI && linkedDirs.length > 0) { + const videoExtensions = /\.(mp4|webm|mov|avi|mkv|m4v)$/i; + + for (const dir of linkedDirs) { + console.log(`๐ŸŽฌ Scanning directory: ${dir.path}`); + try { + if (window.electronAPI.readVideoDirectoryRecursive) { + const filesPromise = window.electronAPI.readVideoDirectoryRecursive(dir.path); + + let files = []; + if (filesPromise && typeof filesPromise.then === 'function') { + files = await filesPromise; + } else if (Array.isArray(filesPromise)) { + files = filesPromise; + } + + console.log(`๐ŸŽฌ Found ${files.length} total files in ${dir.name}`); + if (files.length > 0) { + console.log(`๐ŸŽฌ First file sample:`, files[0]); + } + + if (files && files.length > 0) { + const videoFiles = files.filter(file => { + const fileName = typeof file === 'object' ? file.name : file; + const isVideo = videoExtensions.test(fileName); + if (isVideo) { + console.log(`โœ… Video file found: ${fileName}`); + } + return isVideo; + }); + + if (videoFiles.length > 0) { + const dirVideos = videoFiles.map(file => { + if (typeof file === 'object' && file.name && file.path) { + return { + path: file.path, + name: file.name, + directory: dir.name, + directoryId: dir.id + }; + } else { + const fileName = typeof file === 'object' ? file.name : file; + const fullPath = window.electronAPI.pathJoin ? window.electronAPI.pathJoin(dir.path, fileName) : `${dir.path}\\${fileName}`; + return { + path: fullPath, + name: fileName, + directory: dir.name, + directoryId: dir.id + }; + } + }); + allVideos = allVideos.concat(dirVideos); + console.log(`๐ŸŽฌ Added ${dirVideos.length} videos from ${dir.name}`); + } + } + } + } catch (error) { + console.error(`Error scanning directory ${dir.path}:`, error); + } + } + } + + console.log(`๐ŸŽฌ Total videos found: ${allVideos.length}`); + + const videoCountElement = document.getElementById('lib-video-count'); + if (videoCountElement) { + videoCountElement.textContent = `${allVideos.length} videos`; + } + + window.allLinkedVideos = allVideos; + + const categoryFilter = document.getElementById('lib-video-category-filter'); + const selectedCategory = categoryFilter ? categoryFilter.value : 'all'; + const filteredVideos = filterVideosByCategory(allVideos, selectedCategory); + populateVideoGallery(filteredVideos); +} + +// Make functions globally available +window.setupLibraryImagesTab = setupLibraryImagesTab; +window.setupLibraryAudioTab = setupLibraryAudioTab; +window.setupLibraryVideoTab = setupLibraryVideoTab; +window.setupLibraryGalleryTab = setupLibraryGalleryTab; +window.handleAddImageDirectory = handleAddImageDirectory; +window.handleAddIndividualImages = handleAddIndividualImages; +window.handleRefreshImageDirectories = handleRefreshImageDirectories; +window.handleClearImageDirectories = handleClearImageDirectories; +window.handleAddVideoDirectory = handleAddVideoDirectory; +window.handleAddIndividualVideos = handleAddIndividualVideos; +window.handleRefreshVideoDirectories = handleRefreshVideoDirectories; +window.handleClearVideoDirectories = handleClearVideoDirectories; +window.removeImageDirectory = removeImageDirectory; +window.removeVideoDirectory = removeVideoDirectory; +window.deletePhoto = deletePhoto; +window.downloadSinglePhoto = downloadSinglePhoto; +window.downloadVerificationPhoto = downloadVerificationPhoto; +window.deleteVerificationPhoto = deleteVerificationPhoto; +window.showPhotoPreview = showPhotoPreview; +window.showVerificationPhotoPreview = showVerificationPhotoPreview; +window.updateSelectionCount = updateSelectionCount; diff --git a/training-academy.html b/training-academy.html index bc8ad51..dd268da 100644 --- a/training-academy.html +++ b/training-academy.html @@ -1993,55 +1993,32 @@ console.log(`๐Ÿ“ธ Total photos loaded: ${totalPhotos} (${linkedPhotoDirectories.length} directories + ${linkedIndividualImages.length} individual images)`); - // Load previously captured webcam photos from localStorage + // Load previously captured photos from file system or localStorage try { - const savedWebcamPhotos = JSON.parse(localStorage.getItem('trainingAcademyPhotos') || '[]'); - if (savedWebcamPhotos.length > 0) { - trainingPhotoLibrary.push(...savedWebcamPhotos); - console.log(`๐Ÿ“ธ Loaded ${savedWebcamPhotos.length} previously captured photos from localStorage`); - } - - // Also load main captured photos (including verification photos) - const mainCapturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); - console.log('๐Ÿ“ธ Raw main captured photos from localStorage:', mainCapturedPhotos.length); - console.log('๐Ÿ“ธ Main captured photos sample:', mainCapturedPhotos.slice(-2)); - - if (mainCapturedPhotos.length > 0) { - // Convert main photos to library format and add them - let addedCount = 0; - mainCapturedPhotos.forEach(photo => { - if (!trainingPhotoLibrary.some(existing => existing.id === photo.id)) { - const libraryPhoto = { - id: photo.id || Date.now().toString(), - path: photo.data, // Base64 data - filename: photo.filename || `captured_${photo.timestamp}.jpg`, - timestamp: photo.timestamp, - isWebcamCapture: true, - type: photo.type || 'webcam_capture', - phase: photo.phase, - message: photo.message - }; - trainingPhotoLibrary.push(libraryPhoto); - addedCount++; - } - }); - console.log(`๐Ÿ“ธ Added ${addedCount} main captured photos to training library (${mainCapturedPhotos.length} total available)`); - console.log('๐Ÿ“ธ Training photo library now has:', trainingPhotoLibrary.length, 'photos'); - console.log('๐Ÿ“ธ Verification photos in library:', trainingPhotoLibrary.filter(p => p.type === 'position_verification').length); + // Try loading from file system first (Electron) + if (window.desktopFileManager && window.desktopFileManager.isElectron) { + const filePhotos = await window.desktopFileManager.loadCapturedPhotos(); + if (filePhotos.length > 0) { + trainingPhotoLibrary.push(...filePhotos); + console.log(`๐Ÿ“ธ Loaded ${filePhotos.length} photos from file system`); + } + } else { + // Browser fallback - load from localStorage + const savedWebcamPhotos = JSON.parse(localStorage.getItem('trainingAcademyPhotos') || '[]'); + if (savedWebcamPhotos.length > 0) { + trainingPhotoLibrary.push(...savedWebcamPhotos); + console.log(`๐Ÿ“ธ Loaded ${savedWebcamPhotos.length} previously captured photos from localStorage`); + } } } catch (error) { - console.warn('โš ๏ธ Failed to load saved webcam photos:', error); + console.warn('โš ๏ธ Failed to load saved photos:', error); } document.getElementById('photoLibraryStatus').innerHTML = `โœ… ${trainingPhotoLibrary.length} photos available`; - // Check for verification photos as well - const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]'); - const mainCapturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); - - // Show gallery button if there are photos or verification photos - const totalPhotoCount = trainingPhotoLibrary.length + verificationPhotos.length; + // Show gallery button if there are photos + const totalPhotoCount = trainingPhotoLibrary.length; if (totalPhotoCount > 0) { document.getElementById('view-gallery-btn').style.display = 'inline-block'; @@ -4717,6 +4694,66 @@ document.addEventListener('DOMContentLoaded', () => { console.log('๐ŸŽ“ Training Academy DOM loaded'); + // Initialize desktop file manager if in Electron environment + if (window.electronAPI && typeof DesktopFileManager !== 'undefined') { + // Create a simple data manager for the file manager + const simpleDataManager = { + get: (key) => { + try { + return JSON.parse(localStorage.getItem(key)); + } catch { + return null; + } + }, + set: (key, value) => { + localStorage.setItem(key, JSON.stringify(value)); + } + }; + + window.desktopFileManager = new DesktopFileManager(simpleDataManager); + console.log('๐Ÿ–ฅ๏ธ Desktop File Manager initialized for training academy'); + + // Track saved photo IDs to prevent duplicates + const savedPhotoIds = new Set(); + + // Intercept localStorage.setItem to save photos to file system + const originalSetItem = localStorage.setItem.bind(localStorage); + localStorage.setItem = function(key, value) { + // Call original first + originalSetItem(key, value); + + // If it's a photo being saved, also save to file system + if ((key === 'capturedPhotos' || key === 'verificationPhotos') && window.desktopFileManager?.isElectron) { + try { + const photos = JSON.parse(value); + if (Array.isArray(photos) && photos.length > 0) { + const lastPhoto = photos[photos.length - 1]; + if (lastPhoto.data) { + // Create unique ID based on timestamp and first 20 chars of data + const photoId = `${lastPhoto.timestamp}_${lastPhoto.data.substring(0, 20)}`; + + // Only save if we haven't saved this photo yet + if (!savedPhotoIds.has(photoId)) { + savedPhotoIds.add(photoId); + + // Save to file system asynchronously + window.desktopFileManager.savePhoto(lastPhoto.data, 'training-academy') + .then(photoData => { + if (photoData) { + console.log('๐Ÿ“ธ Intercepted and saved photo to file system:', photoData.filename); + } + }) + .catch(err => console.error('๐Ÿ“ธ Failed to intercept save:', err)); + } + } + } + } catch (err) { + // Ignore parsing errors + } + } + }; + } + // Initialize theme switcher UI if (window.themeManager) { const themeSwitcher = window.themeManager.createThemeToggle(); @@ -4857,7 +4894,7 @@ if (photosNeededEl) photosNeededEl.textContent = photosNeeded; } - function completePhotoSession() { + async function completePhotoSession() { // Prevent multiple calls if (!photoSessionActive) { console.log('๐Ÿ“ธ Photo session already completed, skipping'); @@ -4880,7 +4917,7 @@ // Add captured photos to training photo library if (capturedPhotos.length > 0) { - addCapturedPhotosToLibrary(capturedPhotos); + await addCapturedPhotosToLibrary(capturedPhotos); } // Show completion message @@ -4902,51 +4939,64 @@ } // Add captured photos to the training photo library - function addCapturedPhotosToLibrary(capturedPhotos) { + async function addCapturedPhotosToLibrary(capturedPhotos) { + console.log('๐Ÿ“ธ addCapturedPhotosToLibrary called with', capturedPhotos.length, 'photos'); + console.log('๐Ÿ“ธ desktopFileManager available:', !!window.desktopFileManager); + console.log('๐Ÿ“ธ isElectron:', window.desktopFileManager?.isElectron); + const newPhotos = []; - // Get existing photos from localStorage - const existingPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]'); - - capturedPhotos.forEach((photo, index) => { + for (const [index, photo] of capturedPhotos.entries()) { if (photo.dataURL) { - const photoData = { - filename: `captured_photo_${Date.now()}_${index}.jpg`, - path: photo.dataURL, // Use data URL as path for webcam photos - fullPath: photo.dataURL, - isWebcamCapture: true, - timestamp: photo.timestamp || Date.now(), - sessionType: photo.sessionType || 'dress-up-session', - imageData: photo.dataURL // This is what the main gallery expects - }; + console.log(`๐Ÿ“ธ Processing photo ${index + 1}/${capturedPhotos.length}`); + + // Save photo to file system if in Electron + let photoData; + if (window.desktopFileManager && window.desktopFileManager.isElectron) { + console.log('๐Ÿ“ธ Saving to file system...'); + photoData = await window.desktopFileManager.savePhoto(photo.dataURL, 'training-academy'); + + if (!photoData) { + console.error('๐Ÿ“ธ Failed to save photo to file system - using fallback'); + // Fallback to data URL + photoData = { + filename: `captured_photo_${Date.now()}_${index}.jpg`, + path: photo.dataURL, + fullPath: photo.dataURL, + isWebcamCapture: true, + timestamp: photo.timestamp || Date.now(), + sessionType: 'training-academy', + imageData: photo.dataURL + }; + } + } else { + console.log('๐Ÿ“ธ Using browser fallback (data URL)'); + // Browser fallback - use data URL + photoData = { + filename: `captured_photo_${Date.now()}_${index}.jpg`, + path: photo.dataURL, + fullPath: photo.dataURL, + isWebcamCapture: true, + timestamp: photo.timestamp || Date.now(), + sessionType: 'training-academy', + imageData: photo.dataURL + }; + } newPhotos.push(photoData); trainingPhotoLibrary.push(photoData); - existingPhotos.push(photoData); // Add to localStorage format + } else { + console.warn('๐Ÿ“ธ Photo missing dataURL:', photo); } - }); + } - // Save updated photos to localStorage for main gallery - localStorage.setItem('capturedPhotos', JSON.stringify(existingPhotos)); - - console.log(`๐Ÿ“ธ Added ${newPhotos.length} photos to training library and localStorage`); - console.log(`๐Ÿ“ธ Total photos in localStorage: ${existingPhotos.length}`); + console.log(`๐Ÿ“ธ Added ${newPhotos.length} photos to training library`); // Update photo library status const statusEl = document.getElementById('photoLibraryStatus'); if (statusEl) { statusEl.innerHTML = `โœ… ${trainingPhotoLibrary.length} photos available (${newPhotos.length} newly captured)`; } - - // Save to localStorage for persistence - try { - const existingPhotos = JSON.parse(localStorage.getItem('trainingAcademyPhotos') || '[]'); - existingPhotos.push(...newPhotos); - localStorage.setItem('trainingAcademyPhotos', JSON.stringify(existingPhotos)); - console.log('๐Ÿ“ธ Photos saved to localStorage for persistence'); - } catch (error) { - console.warn('โš ๏ธ Failed to save photos to localStorage:', error); - } } // Show captured photos in a gallery view @@ -4962,14 +5012,17 @@

๐Ÿ“ธ Captured Photos Gallery

- ${webcamPhotos.map((photo, index) => ` + ${webcamPhotos.map((photo, index) => { + // Use file URL if available, otherwise fallback to path (data URL for browser mode) + const imageSrc = photo.url || photo.path; + return `
- Captured photo ${index + 1} + Captured photo ${index + 1}
Photo ${index + 1} - ${new Date(photo.timestamp).toLocaleTimeString()}
- `).join('')} + `}).join('')}