feat: Enhanced mirror task timer system and condescending game over dialog

- Fixed mirror task timer display to show actual duration (not hardcoded 60s)
- Added working progress bar with real-time countdown
- Disabled complete button until timer expires - prevents early completion
- Complete button shows countdown and enables only when timer finished
- Changed close mirror button to trigger immediate game over instead of confirmation
- Added condescending but professional game over dialog for training abandonment
- Fixed WebcamManager syntax errors that prevented proper initialization
- Enhanced training academy button styling for professional appearance
- Improved timer synchronization between training academy and webcam interfaces
This commit is contained in:
dilgenfritz 2025-11-04 19:48:07 -06:00
parent 2112142563
commit dbadd4a828
3 changed files with 585 additions and 115 deletions

View File

@ -41,7 +41,13 @@
<h1>⚡ Quick Play</h1>
<div class="quick-play-controls">
<button id="back-to-home" class="btn btn-secondary">🏠 Home</button>
<button id="overlay-video-btn" class="btn btn-secondary">📺 Overlay Video</button>
<div class="overlay-video-controls" style="display: flex; align-items: center; gap: 8px;">
<button id="overlay-video-btn" class="btn btn-secondary">📺 Overlay Video</button>
<select id="playlist-selector" class="btn btn-secondary" style="max-width: 200px;">
<option value="random">🎲 Random Videos</option>
<option value="" disabled>Loading playlists...</option>
</select>
</div>
<button id="force-exit" class="btn btn-danger" title="Force close application">❌ Force Exit</button>
<button id="pause-game-btn" class="btn btn-secondary" style="display: none;">⏸️ Pause</button>
<button id="settings-btn" class="btn btn-secondary">⚙️ Settings</button>
@ -1073,6 +1079,9 @@
});
}
// Load playlist selector for overlay video player
loadPlaylistSelector();
// Setup screen interactions
setupSetupScreenListeners();
@ -2686,6 +2695,137 @@
}
}
// Load available playlists into the selector
function loadPlaylistSelector() {
const selector = document.getElementById('playlist-selector');
if (!selector) return;
try {
// Get saved playlists from localStorage (same as VideoLibrary)
const saved = localStorage.getItem('pornCinema_savedPlaylists');
const playlists = saved ? JSON.parse(saved) : [];
console.log(`📺 Found ${playlists.length} saved playlists`);
// Clear existing options (except Random)
const randomOption = selector.querySelector('option[value="random"]');
selector.innerHTML = '';
selector.appendChild(randomOption);
// Add playlist options
if (playlists.length > 0) {
playlists.forEach(playlist => {
const option = document.createElement('option');
option.value = playlist.name;
option.textContent = `📋 ${playlist.name} (${playlist.videos.length} videos)`;
selector.appendChild(option);
});
} else {
const noPlaylistsOption = document.createElement('option');
noPlaylistsOption.value = '';
noPlaylistsOption.disabled = true;
noPlaylistsOption.textContent = '📝 No playlists created yet';
selector.appendChild(noPlaylistsOption);
}
} catch (error) {
console.warn('Failed to load playlists:', error);
const errorOption = document.createElement('option');
errorOption.value = '';
errorOption.disabled = true;
errorOption.textContent = '⚠️ Error loading playlists';
selector.appendChild(errorOption);
}
}
// Load videos from a specific playlist
async function loadPlaylistVideos(playlistName) {
try {
const saved = localStorage.getItem('pornCinema_savedPlaylists');
const playlists = saved ? JSON.parse(saved) : [];
const playlist = playlists.find(p => p.name === playlistName);
if (!playlist) {
console.warn(`Playlist "${playlistName}" not found`);
return [];
}
console.log(`📋 Found playlist "${playlistName}" with ${playlist.videos.length} videos`);
return playlist.videos || [];
} catch (error) {
console.error('Failed to load playlist videos:', error);
return [];
}
}
// Get all available videos (same logic as background video loading)
async function getAllAvailableVideos() {
let allVideos = [];
// First check if we have a video library instance
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
allVideos = window.videoLibrary.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
}
// Try to access VideoLibrary videos directly if it exists
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
allVideos = window.VideoLibrary.videos;
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
}
// Try desktop file manager (this is likely where your videos are)
if (allVideos.length === 0 && window.desktopFileManager) {
try {
if (typeof window.desktopFileManager.getAllVideos === 'function') {
allVideos = window.desktopFileManager.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
} else {
console.log('🎬 DesktopFileManager.getAllVideos not available');
}
} catch (dmError) {
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
}
}
// Fallback to unified storage (use same logic as background video)
if (allVideos.length === 0) {
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
}
// Fallback to legacy storage
if (allVideos.length === 0) {
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
allVideos = Object.values(storedVideos).flat();
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
}
// Try accessing VideoLibrary legacy storage directly
if (allVideos.length === 0) {
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
}
// If still no videos, try to initialize video library (same as background loading)
if (allVideos.length === 0) {
console.log('🎬 Overlay: No videos found, attempting to initialize video library...');
const initSuccess = await initializeVideoLibrary();
if (initSuccess) {
// Try again after initialization
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
console.log(`🎬 Overlay: After initialization: ${allVideos.length} videos available`);
}
}
return allVideos;
}
// Open overlay video player
async function openOverlayVideoPlayer() {
console.log('📺 Opening overlay video player...');
@ -2698,67 +2838,25 @@
return;
}
// Get available videos using the same logic as loadRandomBackgroundVideo
// Check if user selected a playlist
const playlistSelector = document.getElementById('playlist-selector');
const selectedPlaylist = playlistSelector ? playlistSelector.value : 'random';
let allVideos = [];
// First check if we have a video library instance
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
allVideos = window.videoLibrary.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
}
// Try to access VideoLibrary videos directly if it exists
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
allVideos = window.VideoLibrary.videos;
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
}
// Try desktop file manager (this is likely where your videos are)
if (allVideos.length === 0 && window.desktopFileManager) {
try {
if (typeof window.desktopFileManager.getAllVideos === 'function') {
allVideos = window.desktopFileManager.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
} else {
console.log('🎬 DesktopFileManager.getAllVideos not available');
}
} catch (dmError) {
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
}
}
// Fallback to unified storage (use same logic as background video)
if (allVideos.length === 0) {
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
}
// Fallback to legacy storage
if (allVideos.length === 0) {
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
allVideos = Object.values(storedVideos).flat();
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
}
// Try accessing VideoLibrary legacy storage directly
if (allVideos.length === 0) {
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
}
// If still no videos, try to initialize video library (same as background loading)
if (allVideos.length === 0) {
console.log('🎬 Overlay: No videos found, attempting to initialize video library...');
const initSuccess = await initializeVideoLibrary();
if (selectedPlaylist !== 'random') {
// Load specific playlist
console.log(`📋 Loading playlist: ${selectedPlaylist}`);
allVideos = await loadPlaylistVideos(selectedPlaylist);
if (initSuccess) {
// Try again after initialization
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
console.log(`🎬 Overlay: After initialization: ${allVideos.length} videos available`);
if (allVideos.length === 0) {
alert(`Playlist "${selectedPlaylist}" is empty or could not be loaded.`);
return;
}
} else {
// Use random mode - get all available videos
console.log('🎲 Using random video mode');
allVideos = await getAllAvailableVideos();
}
if (allVideos.length === 0) {
@ -2821,14 +2919,26 @@
window.quickPlayOverlayPlayer = overlayPlayer;
}
// Select a random video and show the overlay player
// Select a random video
const randomIndex = Math.floor(Math.random() * allVideos.length);
const selectedVideo = allVideos[randomIndex];
console.log(`🎬 Selected video for overlay: ${selectedVideo.name || selectedVideo.filename}`);
console.log(`🎬 Selected video for overlay: ${selectedVideo.name || selectedVideo.filename || selectedVideo.title}`);
console.log(`🎬 Video path: ${selectedVideo.path || selectedVideo.filePath}`);
// Show the overlay player first
await overlayPlayer.show();
// Then load the specific video
const videoPath = selectedVideo.path || selectedVideo.filePath;
if (videoPath) {
overlayPlayer.loadVideo(videoPath, true);
overlayPlayer.currentVideo = selectedVideo;
overlayPlayer.updateVideoInfo(selectedVideo);
} else {
console.error('❌ No valid video path found for selected video:', selectedVideo);
}
// Show the overlay player with the selected video
overlayPlayer.show(selectedVideo);
console.log('✅ Overlay video player opened');
} catch (error) {

View File

@ -1153,11 +1153,11 @@ class WebcamManager {
<div class="mirror-progress" id="mirror-progress">
<div class="progress-bar" id="mirror-progress-bar"></div>
</div>
<div class="timer-text">Time remaining: <span id="mirror-time">60</span>s</div>
<div class="timer-text">Time remaining: <span id="mirror-time">${taskData?.duration || 60}</span>s</div>
</div>
<div class="mirror-controls">
<button id="mirror-complete-btn" class="btn btn-primary">
Complete Task
<button id="mirror-complete-btn" class="btn btn-primary" disabled>
Complete Task (${taskData?.duration || 60}s)
</button>
<button id="mirror-close-btn" class="btn btn-secondary">
Close Mirror
@ -1227,8 +1227,17 @@ class WebcamManager {
btn.style.cssText = buttonStyle;
});
// Style specific buttons
// Style disabled complete button
const completeBtn = overlay.querySelector('#mirror-complete-btn');
if (completeBtn && completeBtn.disabled) {
completeBtn.style.cssText += `
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
cursor: not-allowed;
opacity: 0.6;
`;
}
// Style specific buttons
const closeBtn = overlay.querySelector('#mirror-close-btn');
console.log('🔍 Button elements found:', {
@ -1337,25 +1346,95 @@ class WebcamManager {
});
closeBtn.addEventListener('click', () => {
console.log('❌ CLOSE BUTTON CLICKED - Attempting to close mirror');
if (this.preventClose) {
// Show warning that camera cannot be closed during timer
if (this.game && this.game.showNotification) {
this.game.showNotification('Cannot close mirror during active session. Please wait for timer to complete.', 'warning');
} else {
console.log('⚠️ Cannot close mirror during active session');
}
return;
console.log('❌ CLOSE BUTTON CLICKED - Training task abandoned');
// Clear timer if running
if (this.mirrorTimer) {
clearInterval(this.mirrorTimer);
this.mirrorTimer = null;
}
// Show harsh confirmation dialog for abandonment
console.log('🛑 Showing abandonment confirmation dialog');
this.showAbandonmentConfirmation();
// Close mirror interface
this.closeMirrorMode();
// Trigger game over for abandoning training task
console.log('💀 Training task abandoned - triggering game over');
if (this.game && this.game.triggerGameOver) {
this.game.triggerGameOver('Training task abandoned. You must complete your assigned tasks to progress.');
} else if (window.game && window.game.triggerGameOver) {
window.game.triggerGameOver('Training task abandoned. You must complete your assigned tasks to progress.');
} else {
// Create condescending styled game over dialog
this.showCondescendingGameOver();
}
});
// Start timer if duration is specified
if (taskData?.duration) {
this.startMirrorTimer(taskData.duration, overlay);
}
console.log('🪞 Mirror interface displayed');
}
/**
* Start mirror task timer with visual countdown
*/
startMirrorTimer(duration, overlay) {
console.log(`⏱️ Starting mirror timer for ${duration} seconds`);
const timerDisplay = overlay.querySelector('#mirror-time');
const progressBar = overlay.querySelector('#mirror-progress-bar');
const timerElement = overlay.querySelector('#mirror-timer');
// Show timer
if (timerElement) {
timerElement.style.display = 'block';
}
let timeLeft = duration;
const completeBtn = overlay.querySelector('#mirror-complete-btn');
const timer = setInterval(() => {
timeLeft--;
// Update timer display
if (timerDisplay) {
timerDisplay.textContent = timeLeft;
}
// Update button text with countdown
if (completeBtn && timeLeft > 0) {
completeBtn.textContent = `⏱️ Complete Task (${timeLeft}s)`;
}
// Update progress bar
if (progressBar) {
const progress = ((duration - timeLeft) / duration) * 100;
progressBar.style.width = progress + '%';
}
// Timer complete
if (timeLeft <= 0) {
clearInterval(timer);
console.log('⏰ Mirror timer completed');
// Enable complete button
if (completeBtn) {
completeBtn.disabled = false;
completeBtn.textContent = '✅ Time Complete - Click to Continue';
completeBtn.style.background = '#27ae60';
completeBtn.style.animation = 'pulse 1s infinite';
completeBtn.style.cursor = 'pointer';
}
}
}, 1000);
// Store timer reference for cleanup
this.mirrorTimer = timer;
}
/**
* Complete the mirror task
*/
@ -1515,6 +1594,13 @@ class WebcamManager {
closeMirrorMode() {
console.log('🔚 Closing mirror mode');
// Clean up timer
if (this.mirrorTimer) {
clearInterval(this.mirrorTimer);
this.mirrorTimer = null;
console.log('⏱️ Mirror timer cleared');
}
// Track webcam mirror end for XP
if (this.game && this.game.trackWebcamMirror) {
this.game.trackWebcamMirror(false);
@ -1530,6 +1616,174 @@ class WebcamManager {
this.stopCamera();
}
/**
* Show condescending game over dialog for training abandonment
*/
showCondescendingGameOver() {
const overlay = document.createElement('div');
overlay.id = 'condescending-game-over';
overlay.innerHTML = `
<div class="game-over-container">
<div class="game-over-header">
<h1>Training Abandoned</h1>
<p class="subtitle">Task Failed</p>
</div>
<div class="condescending-content">
<p class="main-message">
Really? You couldn't even handle a simple training exercise.
</p>
<div class="failure-details">
<p> <strong>Status:</strong> Task abandoned before completion</p>
<p> <strong>Reason:</strong> Lack of commitment and discipline</p>
<p> <strong>Result:</strong> Complete failure to follow instructions</p>
</div>
<div class="harsh-verdict">
<p>This training was designed to test your dedication. You failed that test miserably.</p>
<p>If you can't handle basic mirror exercises, maybe you're not ready for real training. Consider whether you actually want to improve yourself or if you're just wasting everyone's time.</p>
</div>
<div class="consequence-section">
<p><strong>Consequence:</strong> Session will restart. Try to show some backbone next time.</p>
</div>
</div>
<div class="game-over-controls">
<button id="restart-session-btn" class="failure-btn">
Restart Session
</button>
</div>
</div>
`;
// Clean, condescending styling without excessive theatrics
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: 99999;
font-family: 'Arial', sans-serif;
`;
const container = overlay.querySelector('.game-over-container');
container.style.cssText = `
background: linear-gradient(135deg, #2c3e50, #34495e);
border: 2px solid #e74c3c;
border-radius: 12px;
padding: 40px;
max-width: 500px;
width: 90%;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
`;
const header = overlay.querySelector('.game-over-header');
header.style.cssText = `
margin-bottom: 25px;
border-bottom: 1px solid #e74c3c;
padding-bottom: 15px;
`;
const title = overlay.querySelector('h1');
title.style.cssText = `
color: #e74c3c;
font-size: 1.8em;
margin: 0 0 10px 0;
font-weight: bold;
`;
const subtitle = overlay.querySelector('.subtitle');
subtitle.style.cssText = `
color: #bdc3c7;
font-size: 1em;
margin: 0;
`;
const content = overlay.querySelector('.condescending-content');
content.style.cssText = `
color: #ecf0f1;
line-height: 1.6;
font-size: 1em;
`;
const mainMessage = overlay.querySelector('.main-message');
mainMessage.style.cssText = `
font-size: 1.1em;
color: #e67e22;
margin-bottom: 20px;
font-weight: 500;
`;
const failureDetails = overlay.querySelector('.failure-details');
failureDetails.style.cssText = `
background: rgba(231, 76, 60, 0.1);
border-left: 3px solid #e74c3c;
padding: 15px;
margin: 20px 0;
text-align: left;
border-radius: 4px;
`;
const harshVerdict = overlay.querySelector('.harsh-verdict');
harshVerdict.style.cssText = `
background: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 6px;
margin: 20px 0;
border: 1px solid #34495e;
`;
const consequenceSection = overlay.querySelector('.consequence-section');
consequenceSection.style.cssText = `
background: rgba(231, 76, 60, 0.15);
padding: 15px;
border-radius: 6px;
margin: 20px 0;
border: 1px solid #e74c3c;
`;
const restartBtn = overlay.querySelector('#restart-session-btn');
restartBtn.style.cssText = `
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
border: none;
border-radius: 6px;
padding: 12px 24px;
font-size: 1em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
`;
// Simple hover effect
restartBtn.addEventListener('mouseenter', () => {
restartBtn.style.background = 'linear-gradient(135deg, #c0392b, #a93226)';
});
restartBtn.addEventListener('mouseleave', () => {
restartBtn.style.background = 'linear-gradient(135deg, #e74c3c, #c0392b)';
});
// Add event listener for restart
restartBtn.addEventListener('click', () => {
console.log('🔄 Restarting session after training abandonment...');
window.location.reload();
});
document.body.appendChild(overlay);
console.log('💀 Condescending game over dialog displayed');
}
/**
* Stop camera stream
*/

View File

@ -59,55 +59,61 @@
}
.academy-mode-selection {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
margin: 1rem;
margin: 1rem auto;
max-width: 1000px;
}
.training-mode-card {
background: linear-gradient(135deg, #2c3e50, #34495e);
border: 2px solid #3498db;
border-radius: 12px;
padding: 1.5rem;
border-radius: 8px;
padding: 0.8rem;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
margin: 0.5rem;
width: 200px;
flex-shrink: 0;
}
.training-mode-card:hover {
border-color: #e74c3c;
box-shadow: 0 8px 25px rgba(231, 76, 60, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
transform: translateY(-1px);
}
.training-mode-card.selected {
border-color: #e74c3c;
background: linear-gradient(135deg, #c0392b, #e74c3c);
box-shadow: 0 8px 25px rgba(231, 76, 60, 0.4);
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
}
.mode-icon {
font-size: 3rem;
margin-bottom: 1rem;
font-size: 1.8rem;
margin-bottom: 0.4rem;
}
.mode-name {
font-size: 1.3rem;
font-size: 1rem;
font-weight: bold;
color: #ecf0f1;
margin-bottom: 0.5rem;
margin-bottom: 0.2rem;
}
.mode-description {
color: #bdc3c7;
font-size: 0.9rem;
line-height: 1.4;
font-size: 0.75rem;
line-height: 1.2;
}
.academy-start-controls {
text-align: center;
margin: 2rem;
margin: 1rem;
}
.start-training-btn {
@ -158,6 +164,67 @@
border-color: #3498db;
}
/* Mirror Task Action Buttons */
.action-btn {
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
margin: 0.5rem;
}
.action-btn:hover {
background: linear-gradient(135deg, #219a52, #27ae60);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
transform: translateY(-2px);
}
.skip-btn {
background: linear-gradient(135deg, #95a5a6, #bdc3c7);
color: #2c3e50;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(149, 165, 166, 0.3);
margin: 0.5rem;
}
.skip-btn:hover {
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
box-shadow: 0 6px 20px rgba(149, 165, 166, 0.4);
transform: translateY(-2px);
}
.next-btn {
background: linear-gradient(135deg, #3498db, #5dade2);
color: white;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
margin: 0.5rem;
}
.next-btn:hover {
background: linear-gradient(135deg, #2980b9, #3498db);
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
transform: translateY(-2px);
}
/* Video player container for background videos */
.training-video-container {
position: fixed;
@ -576,7 +643,6 @@
<!-- Training Mode Selection -->
<div class="training-controls">
<h2>🎯 Choose Your Training Path</h2>
<div class="academy-mode-selection" id="trainingModeSelection">
<!-- Training modes will be populated here -->
</div>
@ -837,26 +903,41 @@
try {
console.log('📸 Training Academy: Initializing photo library...');
// Check if we're in Electron environment with proper API access
const isElectron = window.electronAPI && typeof window.electronAPI.getImageFiles === 'function';
// Debug: Check what APIs are available
console.log('🔍 Available APIs:', {
electronAPI: !!window.electronAPI,
getImageFiles: window.electronAPI && typeof window.electronAPI.getImageFiles,
readDirectory: window.electronAPI && typeof window.electronAPI.readDirectory,
desktopFileManager: !!window.desktopFileManager
});
if (!isElectron) {
// Check if we're in Electron environment with proper API access
const isElectron = window.electronAPI && (
typeof window.electronAPI.getImageFiles === 'function' ||
typeof window.electronAPI.readDirectory === 'function'
);
if (!isElectron && !window.desktopFileManager) {
document.getElementById('photoLibraryStatus').innerHTML =
'<span style="color: #f39c12;">⚠️ Photo library unavailable - Electron API not accessible</span>';
return;
}
const linkedPhotoDirectories = JSON.parse(localStorage.getItem('linkedPhotoDirectories') || '[]');
const linkedPhotoDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
const linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
console.log('📁 Linked photo directories:', linkedPhotoDirectories);
console.log('📷 Linked individual images:', linkedIndividualImages);
if (linkedPhotoDirectories.length === 0) {
if (linkedPhotoDirectories.length === 0 && linkedIndividualImages.length === 0) {
document.getElementById('photoLibraryStatus').innerHTML =
'<span style="color: #f39c12;">⚠️ No photo directories linked</span>';
return;
}
trainingPhotoLibrary = [];
let totalPhotos = 0;
// Process linked directories
for (const directoryData of linkedPhotoDirectories) {
try {
// Handle both string paths and directory objects
@ -868,7 +949,23 @@
}
console.log(`📂 Scanning photo directory: ${directoryPath}`);
const photos = await window.electronAPI.getImageFiles(directoryPath);
let photos = [];
// Try different Electron APIs for reading image files
if (window.electronAPI && window.electronAPI.getImageFiles) {
photos = await window.electronAPI.getImageFiles(directoryPath);
} else if (window.electronAPI && window.electronAPI.readDirectory) {
const allFiles = await window.electronAPI.readDirectory(directoryPath);
// Filter for image files
photos = allFiles.filter(file =>
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.filename)
);
} else if (window.desktopFileManager) {
// Fallback to desktop file manager
photos = window.desktopFileManager.getImagesFromDirectory(directoryPath) || [];
}
console.log(`📷 Found ${photos.length} photos in ${directoryPath}`);
const photosWithPath = photos.map(photo => ({
@ -878,13 +975,28 @@
}));
trainingPhotoLibrary.push(...photosWithPath);
totalPhotos += photos.length;
} catch (error) {
console.error(`❌ Error scanning photo directory ${directoryData}:`, error);
}
}
console.log(`📸 Total photos loaded: ${trainingPhotoLibrary.length}`);
// Process individual linked images
for (const imageData of linkedIndividualImages) {
try {
const imageWithPath = {
...imageData,
fullPath: imageData.path || imageData.filePath
};
trainingPhotoLibrary.push(imageWithPath);
totalPhotos++;
} catch (error) {
console.error(`❌ Error processing individual image ${imageData}:`, error);
}
}
console.log(`📸 Total photos loaded: ${totalPhotos} (${linkedPhotoDirectories.length} directories + ${linkedIndividualImages.length} individual images)`);
// Load previously captured webcam photos from localStorage
try {
@ -1806,7 +1918,7 @@
const isPhotoAction = (step.actionText && step.actionText.toLowerCase().includes('photograph')) ||
(step.story && step.story.toLowerCase().includes('photograph')) ||
(step.story && step.story.toLowerCase().includes('photo')) ||
currentMode === 'photography-studio';
selectedTrainingMode === 'photography-studio';
stepHtml = `
<div class="training-task scenario-action">
@ -1916,6 +2028,9 @@
// Store next step for completion callback
window.trainingAcademyNextStep = nextStep;
// Always start the visual timer regardless of webcam status
startActionTimer(duration, nextStep, true);
// Create mirror task data for the webcam system
const mirrorTaskData = {
mirrorInstructions: currentScenarioTask.interactiveData.steps[currentScenarioStep].mirrorInstructions,
@ -1934,24 +2049,15 @@
window.game.webcamManager.startMirrorMode(mirrorTaskData).then((success) => {
if (success) {
console.log('🪞 Mirror mode started successfully');
// Set up timer to automatically proceed after duration (fallback)
setTimeout(() => {
console.log('🪞 Mirror task timer completed, proceeding to:', nextStep);
proceedToNextStep(nextStep);
}, duration * 1000);
} else {
console.warn('⚠️ Mirror mode failed to start, continuing anyway');
selectScenarioChoice(nextStep);
console.warn('⚠️ Mirror mode failed to start, timer will handle completion');
}
}).catch(error => {
console.error('❌ Mirror mode failed:', error);
// Continue anyway
selectScenarioChoice(nextStep);
console.log('⚠️ Timer will handle completion');
});
} else {
console.warn('⚠️ Webcam manager not available, skipping mirror task');
// Fallback to regular timer
startActionTimer(duration, nextStep, true);
console.warn('⚠️ Webcam manager not available, using timer only');
}
}