updated library to be on its own page
This commit is contained in:
parent
8789b8e16b
commit
cbdea7bf3b
Binary file not shown.
294
index.html
294
index.html
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
<span class="feature-icon">🌀</span>
|
||||
<span class="feature-text">Hypno Gallery</span>
|
||||
</button>
|
||||
<button class="hero-feature btn-feature" id="library-btn">
|
||||
<button class="hero-feature btn-feature" id="library-btn" onclick="window.location.href='library.html'">
|
||||
<span class="cassie-icon"></span>
|
||||
<span class="feature-text">Library</span>
|
||||
</button>
|
||||
|
|
@ -883,259 +883,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Library Screen -->
|
||||
<div id="library-screen" class="screen">
|
||||
<h2>📚 Media Library</h2>
|
||||
<p>Manage all your media content in one place</p>
|
||||
|
||||
<!-- Library Navigation Tabs -->
|
||||
<div class="library-tabs">
|
||||
<button class="library-tab active" data-tab="images">🖼️ Images</button>
|
||||
<button class="library-tab" data-tab="audio">🎵 Audio</button>
|
||||
<button class="library-tab" data-tab="video">🎬 Video</button>
|
||||
<button class="library-tab" data-tab="gallery">📸 Gallery</button>
|
||||
</div>
|
||||
|
||||
<!-- Images Tab Content -->
|
||||
<div id="library-images-content" class="library-content active">
|
||||
<div class="content-section">
|
||||
<h3>🖼️ Image Library Management</h3>
|
||||
<p>Link directories from your computer to access image content</p>
|
||||
|
||||
<!-- Directory Management Section -->
|
||||
<div class="directory-management-section">
|
||||
<h4>📁 Linked Image Directories</h4>
|
||||
<div class="directory-controls">
|
||||
<button id="lib-add-image-directory-btn" class="btn btn-primary">📁 Add Directory</button>
|
||||
<button id="lib-add-individual-images-btn" class="btn btn-primary">🖼️ Add Individual Images</button>
|
||||
<button id="lib-refresh-image-directories-btn" class="btn btn-secondary">🔄 Refresh</button>
|
||||
<button id="lib-clear-image-directories-btn" class="btn btn-danger">🗑️ Clear All</button>
|
||||
<span id="lib-directories-count">0 directories linked</span>
|
||||
</div>
|
||||
<div class="linked-directories-container">
|
||||
<div id="linked-image-directories-list">
|
||||
<div class="no-directories">No image directories linked yet</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Gallery -->
|
||||
<div class="gallery-section">
|
||||
<div class="gallery-header">
|
||||
<h4>🖼️ Current Image Library</h4>
|
||||
<div class="gallery-controls">
|
||||
<select id="lib-image-category-filter">
|
||||
<option value="all">All Images</option>
|
||||
<option value="tasks">🎯 Task Images</option>
|
||||
<option value="consequences">⚠️ Consequence Images</option>
|
||||
<option value="rewards">🎁 Reward Images</option>
|
||||
<option value="verification">📷 Verification Photos</option>
|
||||
<option value="jpg">JPEG Images</option>
|
||||
<option value="png">PNG Images</option>
|
||||
<option value="gif">GIF Images</option>
|
||||
</select>
|
||||
<span id="lib-image-count">0 images</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-gallery active" id="lib-image-gallery">
|
||||
<div class="no-images-message">
|
||||
<p><EFBFBD> No images found in linked directories</p>
|
||||
<p>Click "Add Directory" to link a folder containing images</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Tab Content -->
|
||||
<div id="library-audio-content" class="library-content">
|
||||
<div class="content-section">
|
||||
<h3>🎵 Audio Library Management</h3>
|
||||
<p>Organize your background music and ambient sounds</p>
|
||||
|
||||
<!-- Audio Upload Section -->
|
||||
<div class="upload-section">
|
||||
<h4>🎵 Import Audio Files</h4>
|
||||
<div class="upload-controls">
|
||||
<button id="lib-import-background-music-btn" class="btn btn-primary">🎵 Background Music</button>
|
||||
<button id="lib-import-ambient-audio-btn" class="btn btn-secondary">🌊 Ambient Sounds</button>
|
||||
<input type="file" id="lib-audio-upload-input" accept="audio/*" multiple style="display: none;">
|
||||
</div>
|
||||
<div class="upload-info desktop-feature">
|
||||
<span>💻 Desktop: Native file dialogs • Supports MP3, WAV, OGG, M4A formats</span>
|
||||
</div>
|
||||
<div class="directory-controls">
|
||||
<button id="lib-audio-storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
|
||||
<button id="lib-cleanup-invalid-audio-btn" class="btn btn-warning">🧹 Cleanup</button>
|
||||
<button id="lib-clear-all-audio-btn" class="btn btn-danger">🗑️ Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Library -->
|
||||
<div class="audio-gallery-section">
|
||||
<div class="gallery-header">
|
||||
<h4>🎵 Current Audio Library</h4>
|
||||
<div class="gallery-controls">
|
||||
<select id="lib-audio-category-filter">
|
||||
<option value="all">All Categories</option>
|
||||
<option value="background">Background Music</option>
|
||||
<option value="ambient">Ambient Sounds</option>
|
||||
</select>
|
||||
<span id="lib-audio-count">0 files</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-gallery" id="lib-audio-gallery">
|
||||
<div class="no-audio-message">
|
||||
<p>🎵 No audio files found</p>
|
||||
<p>Import audio to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Tab Content -->
|
||||
<div id="library-video-content" class="library-content">
|
||||
<div class="content-section">
|
||||
<h3>🎬 Video Library Management</h3>
|
||||
<p>Manage your video content for enhanced training sessions</p>
|
||||
<!-- Video Directory Management Section -->
|
||||
<div class="directory-management-section">
|
||||
<h4>📁 Linked Video Directories</h4>
|
||||
<p>Link directories from your computer to access video content</p>
|
||||
<div class="directory-controls">
|
||||
<button id="lib-add-video-directory-btn" class="btn btn-primary">📁 Add Directory</button>
|
||||
<button id="lib-add-individual-videos-btn" class="btn btn-primary">🎬 Add Individual Videos</button>
|
||||
<button id="lib-refresh-video-directories-btn" class="btn btn-secondary">🔄 Refresh</button>
|
||||
<button id="lib-clear-video-directories-btn" class="btn btn-danger">🗑️ Clear All</button>
|
||||
<span id="lib-video-directories-count">0 directories linked</span>
|
||||
</div>
|
||||
<div class="linked-directories-container">
|
||||
<div id="linked-video-directories-list">
|
||||
<div class="no-directories">No video directories linked yet</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Library -->
|
||||
<div class="video-gallery-section">
|
||||
<div class="gallery-header">
|
||||
<h4>🎬 Current Video Library</h4>
|
||||
<div class="gallery-controls">
|
||||
<select id="lib-video-category-filter">
|
||||
<option value="all">All Categories</option>
|
||||
<option value="training">Training Videos</option>
|
||||
<option value="background">Background Videos</option>
|
||||
</select>
|
||||
<span id="lib-video-count">0 files</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-gallery active" id="lib-video-gallery">
|
||||
<div class="no-video-message">
|
||||
<p>🎬 No video files found</p>
|
||||
<p>Import videos to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery Tab Content -->
|
||||
<div id="library-gallery-content" class="library-content">
|
||||
<div class="content-section">
|
||||
<h3>📸 Photo Gallery</h3>
|
||||
<p>Browse and organize your photo collections</p>
|
||||
|
||||
<!-- Gallery Categories -->
|
||||
<div class="gallery-categories">
|
||||
<button class="gallery-category-btn active" data-category="all">All Photos</button>
|
||||
<button class="gallery-category-btn" data-category="dress-up">Dress Up</button>
|
||||
<button class="gallery-category-btn" data-category="studio">Studio</button>
|
||||
<button class="gallery-category-btn" data-category="custom">Custom</button>
|
||||
</div>
|
||||
|
||||
<!-- Photo Gallery Display -->
|
||||
<div class="photo-galleries">
|
||||
<div id="lib-all-photos-gallery" class="photo-gallery active">
|
||||
<div class="gallery-header">
|
||||
<h4>📸 All Photos</h4>
|
||||
<span class="photo-count" id="lib-all-photos-count">0 photos</span>
|
||||
</div>
|
||||
<div class="bulk-actions">
|
||||
<div class="selection-controls">
|
||||
<button id="select-all-photos" class="btn btn-small">☑️ Select All</button>
|
||||
<button id="deselect-all-photos" class="btn btn-small">☐ Deselect All</button>
|
||||
<span id="selected-count" class="selected-count">0 selected</span>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button id="download-selected-photos" class="btn btn-success" disabled>📥 Download Selected</button>
|
||||
<button id="delete-selected-photos" class="btn btn-danger" disabled>🗑️ Delete Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid" id="lib-all-photos-grid">
|
||||
<div class="no-photos-message">
|
||||
<p>📸 No photos found</p>
|
||||
<p>Take some photos during gameplay to see them here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lib-dress-up-photos-gallery" class="photo-gallery">
|
||||
<div class="gallery-header">
|
||||
<h4>👗 Dress Up Photos</h4>
|
||||
<span class="photo-count" id="lib-dress-up-photos-count">0 photos</span>
|
||||
</div>
|
||||
<div class="bulk-actions">
|
||||
<div class="selection-controls">
|
||||
<button id="select-all-dress-up" class="btn btn-small">☑️ Select All</button>
|
||||
<button id="deselect-all-dress-up" class="btn btn-small">☐ Deselect All</button>
|
||||
<span id="selected-dress-up-count" class="selected-count">0 selected</span>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button id="download-selected-dress-up" class="btn btn-success" disabled>📥 Download Selected</button>
|
||||
<button id="delete-selected-dress-up" class="btn btn-danger" disabled>🗑️ Delete Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid" id="lib-dress-up-photos-grid">
|
||||
<div class="no-photos-message">
|
||||
<p>👗 No dress up photos found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lib-studio-photos-gallery" class="photo-gallery">
|
||||
<div class="gallery-header">
|
||||
<h4>🎬 Studio Photos</h4>
|
||||
<span class="photo-count" id="lib-studio-photos-count">0 photos</span>
|
||||
</div>
|
||||
<div class="photo-grid" id="lib-studio-photos-grid">
|
||||
<div class="no-photos-message">
|
||||
<p>🎬 No studio photos found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lib-custom-photos-gallery" class="photo-gallery">
|
||||
<div class="gallery-header">
|
||||
<h4>⭐ Custom Photos</h4>
|
||||
<span class="photo-count" id="lib-custom-photos-count">0 photos</span>
|
||||
</div>
|
||||
<div class="photo-grid" id="lib-custom-photos-grid">
|
||||
<div class="no-photos-message">
|
||||
<p>⭐ No custom photos found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Library Controls -->
|
||||
<div class="management-buttons">
|
||||
<button id="back-to-start-from-library-btn" class="btn btn-secondary">Back to Start</button>
|
||||
<button id="refresh-library-btn" class="btn btn-primary">🔄 Refresh Library</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
|
@ -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';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
201
porn-cinema.html
201
porn-cinema.html
|
|
@ -239,91 +239,117 @@
|
|||
<script>
|
||||
// Initialize cinema when page loads
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
console.log('🎬 Initializing Porn Cinema...');
|
||||
|
||||
// Initialize theme switcher UI
|
||||
if (window.themeManager) {
|
||||
const themeSwitcher = window.themeManager.createThemeToggle();
|
||||
const container = document.getElementById('theme-switcher-container');
|
||||
if (container) {
|
||||
container.appendChild(themeSwitcher);
|
||||
console.log('✅ Theme switcher initialized');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize desktop file manager if in Electron environment
|
||||
if (window.electronAPI) {
|
||||
// Try to reuse existing desktop file manager from parent window first
|
||||
if (window.opener && window.opener.game && window.opener.game.fileManager) {
|
||||
console.log('🔗 Reusing desktop file manager from main window');
|
||||
window.desktopFileManager = window.opener.game.fileManager;
|
||||
} else {
|
||||
console.log('🆕 Creating new desktop file manager instance');
|
||||
// Create a minimal data manager for the cinema (since we don't have the full game instance)
|
||||
const minimalDataManager = {
|
||||
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(minimalDataManager);
|
||||
}
|
||||
console.log('🖥️ Desktop File Manager initialized for porn cinema');
|
||||
try {
|
||||
console.log('🎬 Initializing Porn Cinema...');
|
||||
|
||||
// Wait for the desktop file manager to fully initialize
|
||||
// This includes loading linked directories and video files
|
||||
let retries = 0;
|
||||
const maxRetries = 50; // Wait up to 5 seconds
|
||||
|
||||
while (retries < maxRetries) {
|
||||
// Check if initialization is complete by verifying video directories are set up
|
||||
if (window.desktopFileManager.videoDirectories &&
|
||||
window.desktopFileManager.videoDirectories.background) {
|
||||
console.log('✅ Desktop file manager video directories are ready');
|
||||
break;
|
||||
// Initialize theme switcher UI
|
||||
if (window.themeManager) {
|
||||
const themeSwitcher = window.themeManager.createThemeToggle();
|
||||
const container = document.getElementById('theme-switcher-container');
|
||||
if (container) {
|
||||
container.appendChild(themeSwitcher);
|
||||
console.log('✅ Theme switcher initialized');
|
||||
}
|
||||
|
||||
// Waiting for desktop file manager to initialize...
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (retries >= maxRetries) {
|
||||
console.warn('⚠️ Desktop file manager took too long to initialize');
|
||||
}
|
||||
|
||||
// Force refresh of linked directories to ensure we have the latest video data
|
||||
try {
|
||||
// If we're reusing the main window's file manager, don't reload/refresh
|
||||
// Initialize desktop file manager if in Electron environment
|
||||
if (window.electronAPI) {
|
||||
// Try to reuse existing desktop file manager from parent window first
|
||||
if (window.opener && window.opener.game && window.opener.game.fileManager) {
|
||||
console.log('📁 Using directories from main window file manager');
|
||||
console.log('🔗 Reusing desktop file manager from main window');
|
||||
window.desktopFileManager = window.opener.game.fileManager;
|
||||
} else {
|
||||
// Only reload and refresh if we created a new instance
|
||||
await window.desktopFileManager.loadLinkedDirectories();
|
||||
console.log('🆕 Creating new desktop file manager instance');
|
||||
// Create a minimal data manager for the cinema (since we don't have the full game instance)
|
||||
const minimalDataManager = {
|
||||
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(minimalDataManager);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Error refreshing directories:', error);
|
||||
console.log('🖥️ Desktop File Manager initialized for porn cinema');
|
||||
|
||||
// Wait for the desktop file manager to fully initialize
|
||||
// This includes loading linked directories and video files
|
||||
let retries = 0;
|
||||
const maxRetries = 50; // Wait up to 5 seconds
|
||||
|
||||
while (retries < maxRetries) {
|
||||
// Check if initialization is complete by verifying video directories are set up
|
||||
if (window.desktopFileManager.videoDirectories &&
|
||||
window.desktopFileManager.videoDirectories.background) {
|
||||
console.log('✅ Desktop file manager video directories are ready');
|
||||
break;
|
||||
}
|
||||
|
||||
// Waiting for desktop file manager to initialize...
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (retries >= maxRetries) {
|
||||
console.warn('⚠️ Desktop file manager took too long to initialize');
|
||||
}
|
||||
|
||||
// Force refresh of linked directories to ensure we have the latest video data
|
||||
try {
|
||||
// If we're reusing the main window's file manager, don't reload/refresh
|
||||
if (window.opener && window.opener.game && window.opener.game.fileManager) {
|
||||
console.log('📁 Using directories from main window file manager');
|
||||
} else {
|
||||
// Only reload and refresh if we created a new instance
|
||||
await window.desktopFileManager.loadLinkedDirectories();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Error refreshing directories:', error);
|
||||
}
|
||||
|
||||
} else if (!window.electronAPI) {
|
||||
console.warn('⚠️ Running in browser mode - video management limited');
|
||||
}
|
||||
|
||||
} else if (!window.electronAPI) {
|
||||
console.warn('⚠️ Running in browser mode - video management limited');
|
||||
// Initialize the cinema after desktop file manager is ready
|
||||
window.pornCinema = new PornCinema();
|
||||
await window.pornCinema.initialize();
|
||||
|
||||
// Hide loading overlay
|
||||
setTimeout(() => {
|
||||
const loadingOverlay = document.getElementById('cinema-loading');
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.display = 'none';
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Critical error initializing Porn Cinema:', error);
|
||||
|
||||
// Show error to user
|
||||
const loadingOverlay = document.getElementById('cinema-loading');
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.innerHTML = `
|
||||
<div class="loading-content">
|
||||
<h2 style="color: #ff4444;">⚠️ Error Loading Cinema</h2>
|
||||
<p>${error.message || 'Unknown error occurred'}</p>
|
||||
<p style="font-size: 0.9em; opacity: 0.7;">Check console for details</p>
|
||||
<button onclick="location.reload()" class="btn btn-primary" style="margin-top: 20px;">
|
||||
🔄 Retry
|
||||
</button>
|
||||
<button onclick="location.href='index.html'" class="btn btn-secondary" style="margin-top: 10px;">
|
||||
🏠 Return Home
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the cinema after desktop file manager is ready
|
||||
window.pornCinema = new PornCinema();
|
||||
await window.pornCinema.initialize();
|
||||
|
||||
// Hide loading overlay
|
||||
setTimeout(() => {
|
||||
document.getElementById('cinema-loading').style.display = 'none';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Back to home functionality
|
||||
|
|
@ -331,6 +357,20 @@
|
|||
showExitConfirmationDialog();
|
||||
});
|
||||
|
||||
// Cleanup on window close to prevent memory leaks
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (window.pornCinema && typeof window.pornCinema.destroy === 'function') {
|
||||
window.pornCinema.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup when navigating away
|
||||
window.addEventListener('pagehide', () => {
|
||||
if (window.pornCinema && typeof window.pornCinema.destroy === 'function') {
|
||||
window.pornCinema.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function getSassyTheaterAttendantDialog() {
|
||||
// Array of sassy theater attendant responses
|
||||
const sassyResponses = [
|
||||
|
|
@ -795,8 +835,15 @@
|
|||
}
|
||||
|
||||
// Theater mode button event listeners
|
||||
document.getElementById('theater-mode').addEventListener('click', toggleTheaterMode);
|
||||
document.getElementById('theater-mode-btn').addEventListener('click', toggleTheaterMode);
|
||||
const theaterModeBtn = document.getElementById('theater-mode');
|
||||
const theaterModeBtnControl = document.getElementById('theater-mode-btn');
|
||||
|
||||
if (theaterModeBtn) {
|
||||
theaterModeBtn.addEventListener('click', toggleTheaterMode);
|
||||
}
|
||||
if (theaterModeBtnControl) {
|
||||
theaterModeBtnControl.addEventListener('click', toggleTheaterMode);
|
||||
}
|
||||
|
||||
// Keyboard shortcut to toggle help
|
||||
document.addEventListener('keydown', (e) => {
|
||||
|
|
|
|||
500
quick-play.html
500
quick-play.html
|
|
@ -1293,8 +1293,6 @@
|
|||
|
||||
// Check MP4 codec support for Electron
|
||||
function checkMP4Support() {
|
||||
console.log('🎥 Checking MP4 codec support...');
|
||||
|
||||
const mp4Codecs = [
|
||||
'video/mp4;codecs=avc1.42E01E',
|
||||
'video/mp4;codecs=avc1.4D401E',
|
||||
|
|
@ -1305,7 +1303,6 @@
|
|||
const supportedCodecs = mp4Codecs.filter(codec => MediaRecorder.isTypeSupported(codec));
|
||||
|
||||
if (supportedCodecs.length > 0) {
|
||||
console.log('✅ MP4 codec support confirmed:', supportedCodecs);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ No MP4 codecs supported. Available types:');
|
||||
|
|
@ -1330,15 +1327,12 @@
|
|||
|
||||
// Initialize Quick Play when page loads
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
console.log('⚡ Initializing Quick Play...');
|
||||
|
||||
// Initialize theme switcher UI
|
||||
if (window.themeManager) {
|
||||
const themeSwitcher = window.themeManager.createThemeToggle();
|
||||
const container = document.getElementById('theme-switcher-container');
|
||||
if (container) {
|
||||
container.appendChild(themeSwitcher);
|
||||
console.log('✅ Theme switcher initialized');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1348,21 +1342,17 @@
|
|||
|
||||
// Load saved settings
|
||||
await loadSavedSettings();
|
||||
console.log('✅ Settings loaded');
|
||||
|
||||
// Setup event listeners
|
||||
setupEventListeners();
|
||||
console.log('✅ Event listeners setup');
|
||||
|
||||
// Initialize player stats
|
||||
if (typeof PlayerStats !== 'undefined') {
|
||||
window.playerStats = new PlayerStats();
|
||||
console.log('✅ Player stats initialized');
|
||||
}
|
||||
|
||||
// Initialize desktop file manager if in Electron environment
|
||||
await initializeFileManager();
|
||||
console.log('✅ File manager initialized');
|
||||
|
||||
// Add window unload handler with better cleanup
|
||||
let unloadHandlerAdded = false;
|
||||
|
|
@ -2267,12 +2257,10 @@
|
|||
}
|
||||
|
||||
function updateRecordingOverlay() {
|
||||
if (!recordingOverlayEnabled) {
|
||||
console.log('🎬 updateRecordingOverlay called but recording not enabled');
|
||||
return;
|
||||
}
|
||||
// Don't update if game is ending or not running
|
||||
if (!isGameRunning || window.isEndingGame) return;
|
||||
|
||||
console.log('🎬 Updating recording overlay content');
|
||||
if (!recordingOverlayEnabled) return;
|
||||
|
||||
// Try multiple selectors to find task elements
|
||||
let taskText = document.getElementById('task-text');
|
||||
|
|
@ -2368,7 +2356,9 @@
|
|||
|
||||
// Call this function whenever task content changes
|
||||
function syncTaskWithOverlay() {
|
||||
console.log('🎬 syncTaskWithOverlay called, recordingOverlayEnabled:', recordingOverlayEnabled);
|
||||
// Don't run if game is ending or not running
|
||||
if (!isGameRunning || window.isEndingGame) return;
|
||||
|
||||
if (recordingOverlayEnabled) {
|
||||
updateRecordingOverlay();
|
||||
|
||||
|
|
@ -2939,6 +2929,46 @@
|
|||
|
||||
window.desktopFileManager = new DesktopFileManager(minimalDataManager);
|
||||
console.log('🖥️ Desktop File Manager initialized for Quick Play');
|
||||
|
||||
// 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, 'quick-play')
|
||||
.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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2965,7 +2995,6 @@
|
|||
skipped: 0,
|
||||
xp: 0
|
||||
};
|
||||
console.log('📊 Session stats reset:', sessionStats);
|
||||
|
||||
// Start periodic XP display updates (every minute)
|
||||
if (window.xpDisplayInterval) {
|
||||
|
|
@ -3590,11 +3619,17 @@
|
|||
}
|
||||
|
||||
function startGameTimer(timeLimit) {
|
||||
// Clear any existing timer first
|
||||
if (window.gameTimerInterval) {
|
||||
clearInterval(window.gameTimerInterval);
|
||||
window.gameTimerInterval = null;
|
||||
}
|
||||
|
||||
// Handle endless mode
|
||||
if (timeLimit === -1) {
|
||||
console.log('🔄 Starting endless session - no time limit');
|
||||
let timeElapsed = 0;
|
||||
const timerInterval = setInterval(() => {
|
||||
window.gameTimerInterval = setInterval(() => {
|
||||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3607,7 +3642,7 @@
|
|||
|
||||
// Handle timed mode
|
||||
let timeLeft = timeLimit;
|
||||
const timerInterval = setInterval(() => {
|
||||
window.gameTimerInterval = setInterval(() => {
|
||||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3616,7 +3651,8 @@
|
|||
updateGameStatus({ timer: formatTime(timeLeft) });
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(timerInterval);
|
||||
clearInterval(window.gameTimerInterval);
|
||||
window.gameTimerInterval = null;
|
||||
endGame();
|
||||
}
|
||||
}, 1000);
|
||||
|
|
@ -3708,7 +3744,6 @@
|
|||
taskTags.includes(includeTag)
|
||||
);
|
||||
});
|
||||
console.log(`🏷️ Filtered to ${filteredTasks.length} tasks with include tags:`, quickPlaySettings.includeTags);
|
||||
}
|
||||
|
||||
// Step 2: Remove any tasks that have exclude tags (this overrides include)
|
||||
|
|
@ -3719,12 +3754,10 @@
|
|||
taskTags.includes(excludeTag)
|
||||
);
|
||||
});
|
||||
console.log(`🚫 Filtered out tasks with exclude tags:`, quickPlaySettings.excludeTags, `- ${filteredTasks.length} tasks remaining`);
|
||||
}
|
||||
|
||||
if (filteredTasks.length > 0) {
|
||||
const randomTask = filteredTasks[Math.floor(Math.random() * filteredTasks.length)];
|
||||
console.log('📋 Loading filtered task:', randomTask.text || randomTask.description, 'Tags:', randomTask.tags);
|
||||
|
||||
displayTask(randomTask);
|
||||
} else {
|
||||
|
|
@ -3756,6 +3789,15 @@
|
|||
function quickPlayCompleteTask(task) {
|
||||
console.log('✅ Quick Play task completed:', task.text);
|
||||
|
||||
// Clear inactivity timeout when completing task
|
||||
if (inactivityTimeout) {
|
||||
clearTimeout(inactivityTimeout);
|
||||
inactivityTimeout = null;
|
||||
}
|
||||
|
||||
// Reset pause state
|
||||
isTimerPaused = false;
|
||||
|
||||
// Check timer validation
|
||||
if (!taskCompleteAllowed) {
|
||||
console.log('⏱️ Task completion blocked - timer not finished');
|
||||
|
|
@ -4163,9 +4205,187 @@
|
|||
}
|
||||
|
||||
// Global timer variables
|
||||
let taskTimer = null;
|
||||
window.taskTimer = null;
|
||||
let taskTimeRemaining = 0;
|
||||
let taskCompleteAllowed = false;
|
||||
let inactivityTimeout = null;
|
||||
let isTimerPaused = false;
|
||||
let pausedTimeRemaining = 0;
|
||||
|
||||
const timeoutMessages = [
|
||||
"Hello? Did you fall asleep, slut? 🥱",
|
||||
"Still there, or did you chicken out? 🐔",
|
||||
"Getting bored already? Typical... 💅",
|
||||
"Did you get distracted, or are you just slow? 🤔",
|
||||
"Knock knock... anyone home? 🚪",
|
||||
"Are you still playing or did you give up? 😏",
|
||||
"Time to wake up, sleeping beauty! ⏰",
|
||||
"Lost interest already? How disappointing... 😒",
|
||||
"Did you forget what you were doing? 🤦",
|
||||
"Still with us, or did reality call? 📱",
|
||||
"Zoned out? That's not very obedient... 😈",
|
||||
"Are we having fun yet? ...Anyone? 🙄",
|
||||
"Did you wander off, or are you just thinking really hard? 🧠",
|
||||
"Helloooo? Earth to slut! 🌍",
|
||||
"Taking a break without permission? Naughty... 😤"
|
||||
];
|
||||
|
||||
function startInactivityTimeout() {
|
||||
// Clear any existing timeout
|
||||
if (inactivityTimeout) {
|
||||
clearTimeout(inactivityTimeout);
|
||||
}
|
||||
|
||||
// Start 30-second timeout
|
||||
inactivityTimeout = setTimeout(() => {
|
||||
if (taskTimeRemaining <= 0 && !isTimerPaused) {
|
||||
// Timer has hit 0 but task wasn't completed
|
||||
pauseTimer();
|
||||
showTimeoutDialog();
|
||||
}
|
||||
}, 30000); // 30 seconds
|
||||
}
|
||||
|
||||
function pauseTimer() {
|
||||
if (window.taskTimer && !isTimerPaused) {
|
||||
clearInterval(window.taskTimer);
|
||||
isTimerPaused = true;
|
||||
pausedTimeRemaining = taskTimeRemaining;
|
||||
console.log('⏸️ Timer paused due to inactivity');
|
||||
}
|
||||
}
|
||||
|
||||
function resumeTimer(timerElement, completeButton) {
|
||||
if (isTimerPaused) {
|
||||
isTimerPaused = false;
|
||||
taskTimeRemaining = pausedTimeRemaining;
|
||||
|
||||
// Restart the timer interval
|
||||
window.taskTimer = setInterval(() => {
|
||||
try {
|
||||
taskTimeRemaining--;
|
||||
updateTimerDisplay(timerElement);
|
||||
|
||||
if (taskTimeRemaining <= 0) {
|
||||
clearInterval(window.taskTimer);
|
||||
window.taskTimer = null;
|
||||
taskCompleteAllowed = true;
|
||||
|
||||
const currentCompleteBtn = document.getElementById('complete-task');
|
||||
if (currentCompleteBtn) {
|
||||
currentCompleteBtn.disabled = false;
|
||||
currentCompleteBtn.textContent = 'Complete Task';
|
||||
currentCompleteBtn.style.opacity = '1';
|
||||
}
|
||||
|
||||
if (timerElement) {
|
||||
timerElement.style.color = '#00ff00';
|
||||
timerElement.textContent = 'COMPLETE!';
|
||||
setTimeout(() => {
|
||||
timerElement.style.color = '';
|
||||
timerElement.textContent = '0:00';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Start inactivity timeout after timer completes
|
||||
startInactivityTimeout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🔧 Error in timer callback:', error);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
console.log('▶️ Timer resumed');
|
||||
}
|
||||
}
|
||||
|
||||
function showTimeoutDialog() {
|
||||
const message = timeoutMessages[Math.floor(Math.random() * timeoutMessages.length)];
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100000;
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
|
||||
dialog.innerHTML = `
|
||||
<style>
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
</style>
|
||||
<div style="
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 60px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
">
|
||||
<div style="
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
">⏰</div>
|
||||
<h2 style="
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
">${message}</h2>
|
||||
<p style="
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 18px;
|
||||
margin-bottom: 30px;
|
||||
">You've been idle for 30 seconds.<br>Click below to continue your session.</p>
|
||||
<button id="continue-session-btn" style="
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
padding: 15px 40px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s ease;
|
||||
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
|
||||
Yes, I'm Still Here! 👋
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
// Add click handler
|
||||
const continueBtn = dialog.querySelector('#continue-session-btn');
|
||||
continueBtn.addEventListener('click', () => {
|
||||
dialog.remove();
|
||||
const timerElement = document.getElementById('task-timer');
|
||||
const completeButton = document.getElementById('complete-task');
|
||||
resumeTimer(timerElement, completeButton);
|
||||
|
||||
// Restart inactivity timeout
|
||||
if (taskTimeRemaining <= 0) {
|
||||
startInactivityTimeout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startTaskTimer(durationMinutes, timerElement, completeButton) {
|
||||
// console.log('🔧 startTaskTimer called with:', {
|
||||
|
|
@ -4177,8 +4397,8 @@
|
|||
// });
|
||||
|
||||
// Clear any existing timer
|
||||
if (taskTimer) {
|
||||
clearInterval(taskTimer);
|
||||
if (window.taskTimer) {
|
||||
clearInterval(window.taskTimer);
|
||||
// console.log('🔧 Cleared existing timer');
|
||||
}
|
||||
|
||||
|
|
@ -4203,7 +4423,7 @@
|
|||
// Start countdown
|
||||
console.log('🔧 About to create timer interval...');
|
||||
try {
|
||||
taskTimer = setInterval(() => {
|
||||
window.taskTimer = setInterval(() => {
|
||||
try {
|
||||
// console.log('🔧 Timer callback fired, timeRemaining:', taskTimeRemaining);
|
||||
taskTimeRemaining--;
|
||||
|
|
@ -4216,7 +4436,8 @@
|
|||
|
||||
// Check if timer completed
|
||||
if (taskTimeRemaining <= 0) {
|
||||
clearInterval(taskTimer);
|
||||
clearInterval(window.taskTimer);
|
||||
window.taskTimer = null;
|
||||
taskCompleteAllowed = true;
|
||||
console.log('✅ Task timer completed!');
|
||||
|
||||
|
|
@ -4242,13 +4463,16 @@
|
|||
}
|
||||
|
||||
console.log('✅ Task timer completed - task can now be completed');
|
||||
|
||||
// Start inactivity timeout - will trigger after 30 seconds if user doesn't complete task
|
||||
startInactivityTimeout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🔧 Error in timer callback:', error);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
console.log('🔧 Timer interval created with ID:', taskTimer);
|
||||
console.log('🔧 Timer interval created with ID:', window.taskTimer);
|
||||
|
||||
// Test if the timer is actually working by checking in 2 seconds
|
||||
setTimeout(() => {
|
||||
|
|
@ -4261,6 +4485,9 @@
|
|||
}
|
||||
|
||||
function updateTimerDisplay(timerElement) {
|
||||
// Don't update if game is ending or not running
|
||||
if (!isGameRunning || window.isEndingGame) return;
|
||||
|
||||
// console.log('🔧 updateTimerDisplay called with timeRemaining:', taskTimeRemaining, 'element:', !!timerElement);
|
||||
if (!timerElement) return;
|
||||
|
||||
|
|
@ -4292,9 +4519,10 @@
|
|||
}
|
||||
|
||||
function stopTaskTimer() {
|
||||
if (taskTimer) {
|
||||
clearInterval(taskTimer);
|
||||
taskTimer = null;
|
||||
if (window.taskTimer) {
|
||||
clearInterval(window.taskTimer);
|
||||
window.taskTimer = null;
|
||||
console.log('✅ Task timer stopped and cleared');
|
||||
}
|
||||
taskTimeRemaining = 0;
|
||||
taskCompleteAllowed = true;
|
||||
|
|
@ -4308,66 +4536,143 @@
|
|||
}
|
||||
|
||||
function skipTask(task) {
|
||||
// Clear inactivity timeout when skipping
|
||||
if (inactivityTimeout) {
|
||||
clearTimeout(inactivityTimeout);
|
||||
inactivityTimeout = null;
|
||||
}
|
||||
|
||||
// Reset pause state
|
||||
isTimerPaused = false;
|
||||
|
||||
// Load consequence or next task
|
||||
loadNextTask();
|
||||
}
|
||||
|
||||
function endGame() {
|
||||
console.log('endGame called - starting game termination');
|
||||
console.log('🛑 ========== END GAME CALLED ==========');
|
||||
|
||||
try {
|
||||
// Clear XP display update interval
|
||||
if (window.xpDisplayInterval) {
|
||||
clearInterval(window.xpDisplayInterval);
|
||||
window.xpDisplayInterval = null;
|
||||
console.log('🔄 Cleared XP display update interval');
|
||||
}
|
||||
|
||||
// Immediately mute all videos to stop audio instantly
|
||||
const allVideos = document.querySelectorAll('video');
|
||||
allVideos.forEach(video => {
|
||||
video.muted = true;
|
||||
video.pause();
|
||||
});
|
||||
console.log(`Immediately muted and paused ${allVideos.length} video elements`);
|
||||
|
||||
// Immediately set flag to prevent multiple calls
|
||||
if (window.isEndingGame) {
|
||||
console.log('Game already ending, ignoring duplicate call');
|
||||
console.log('⚠️ Game already ending, ignoring duplicate call');
|
||||
return;
|
||||
}
|
||||
window.isEndingGame = true;
|
||||
|
||||
console.log('Setting isGameRunning to false');
|
||||
console.log('🛑 Setting isGameRunning to false');
|
||||
isGameRunning = false;
|
||||
|
||||
// Clear ALL timers and intervals immediately - MOST IMPORTANT
|
||||
console.log('🛑 Clearing all timers and intervals...');
|
||||
|
||||
// Clear game timer interval
|
||||
if (window.gameTimerInterval) {
|
||||
console.log('🛑 Clearing gameTimerInterval ID:', window.gameTimerInterval);
|
||||
clearInterval(window.gameTimerInterval);
|
||||
window.gameTimerInterval = null;
|
||||
}
|
||||
|
||||
// Clear XP display interval
|
||||
if (window.xpDisplayInterval) {
|
||||
console.log('🛑 Clearing xpDisplayInterval ID:', window.xpDisplayInterval);
|
||||
clearInterval(window.xpDisplayInterval);
|
||||
window.xpDisplayInterval = null;
|
||||
}
|
||||
|
||||
// Clear recording timer interval
|
||||
if (window.recordingTimerInterval) {
|
||||
console.log('🛑 Clearing recordingTimerInterval ID:', window.recordingTimerInterval);
|
||||
clearInterval(window.recordingTimerInterval);
|
||||
window.recordingTimerInterval = null;
|
||||
}
|
||||
|
||||
// Clear task timer - THIS IS CRITICAL
|
||||
if (window.taskTimer) {
|
||||
console.log('🛑 Clearing taskTimer ID:', window.taskTimer);
|
||||
clearInterval(window.taskTimer);
|
||||
window.taskTimer = null;
|
||||
}
|
||||
|
||||
// Clear inactivity timeout
|
||||
if (inactivityTimeout) {
|
||||
console.log('🛑 Clearing inactivityTimeout');
|
||||
clearTimeout(inactivityTimeout);
|
||||
inactivityTimeout = null;
|
||||
}
|
||||
|
||||
// Reset pause state
|
||||
isTimerPaused = false;
|
||||
taskTimeRemaining = 0;
|
||||
|
||||
// Clear any task-related timers from game instance
|
||||
if (gameInstance) {
|
||||
if (gameInstance.taskTimer) {
|
||||
console.log('🛑 Clearing gameInstance.taskTimer');
|
||||
clearInterval(gameInstance.taskTimer);
|
||||
gameInstance.taskTimer = null;
|
||||
}
|
||||
if (gameInstance.gameTimer) {
|
||||
console.log('🛑 Clearing gameInstance.gameTimer');
|
||||
clearInterval(gameInstance.gameTimer);
|
||||
gameInstance.gameTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// IMMEDIATELY STOP ALL VIDEOS - MOST IMPORTANT FOR USER EXPERIENCE
|
||||
console.log('🛑 Stopping all videos...');
|
||||
const allVideos = document.querySelectorAll('video');
|
||||
console.log(`🛑 Found ${allVideos.length} video elements`);
|
||||
allVideos.forEach((video, index) => {
|
||||
console.log(`🛑 Stopping video ${index + 1}:`, video.id || 'no-id', video.src?.substring(0, 50));
|
||||
video.pause();
|
||||
video.muted = true;
|
||||
video.currentTime = 0;
|
||||
video.src = '';
|
||||
// Remove source elements too
|
||||
const sources = video.querySelectorAll('source');
|
||||
sources.forEach(source => source.src = '');
|
||||
});
|
||||
|
||||
// Stop flash message system
|
||||
stopFlashMessageSystem();
|
||||
|
||||
// Stop all task loading and selection
|
||||
currentTask = null;
|
||||
|
||||
// Stop the task timer (redundant but safe)
|
||||
stopTaskTimer();
|
||||
|
||||
// Clear any pending task loads
|
||||
if (window.nextTaskTimeout) {
|
||||
clearTimeout(window.nextTaskTimeout);
|
||||
window.nextTaskTimeout = null;
|
||||
}
|
||||
|
||||
// Clean up game instance
|
||||
if (gameInstance) {
|
||||
console.log('Cleaning up game instance');
|
||||
try {
|
||||
// Stop any task presentation
|
||||
if (gameInstance.currentTask) {
|
||||
gameInstance.currentTask = null;
|
||||
}
|
||||
|
||||
if (gameInstance.pauseGame) {
|
||||
gameInstance.pauseGame();
|
||||
console.log('Game paused');
|
||||
}
|
||||
if (gameInstance.cleanup) {
|
||||
gameInstance.cleanup();
|
||||
console.log('Game cleanup called');
|
||||
}
|
||||
if (gameInstance.audioManager) {
|
||||
gameInstance.audioManager.stopAll();
|
||||
console.log('Audio stopped');
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Error during game cleanup:', cleanupError);
|
||||
console.error('❌ Error during game cleanup:', cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up background video (check if it exists first)
|
||||
if (typeof backgroundVideoPlayer !== 'undefined' && backgroundVideoPlayer) {
|
||||
console.log('Stopping background video via backgroundVideoPlayer');
|
||||
try {
|
||||
backgroundVideoPlayer.pause();
|
||||
if (backgroundVideoPlayer.destroy) {
|
||||
|
|
@ -4375,85 +4680,37 @@
|
|||
}
|
||||
backgroundVideoPlayer = null;
|
||||
} catch (videoError) {
|
||||
console.warn('Error stopping background video via backgroundVideoPlayer:', videoError);
|
||||
console.error('❌ Error stopping background video via backgroundVideoPlayer:', videoError);
|
||||
}
|
||||
} else {
|
||||
console.log('No backgroundVideoPlayer found - checking for background video element directly');
|
||||
}
|
||||
|
||||
// Quick Play specific background video cleanup
|
||||
// Quick Play specific background video cleanup (already done above but being thorough)
|
||||
const backgroundVideo = document.getElementById('background-video');
|
||||
if (backgroundVideo) {
|
||||
console.log('Found Quick Play background video - stopping it');
|
||||
try {
|
||||
backgroundVideo.pause();
|
||||
backgroundVideo.currentTime = 0;
|
||||
backgroundVideo.src = '';
|
||||
backgroundVideo.load(); // Force reload to clear
|
||||
const source = backgroundVideo.querySelector('source');
|
||||
if (source) {
|
||||
source.src = '';
|
||||
}
|
||||
console.log('✅ Quick Play background video stopped and cleared');
|
||||
} catch (bgVideoError) {
|
||||
console.warn('Error stopping Quick Play background video:', bgVideoError);
|
||||
}
|
||||
} else {
|
||||
console.log('No Quick Play background video element found');
|
||||
}
|
||||
|
||||
// Also clean up the background video container
|
||||
const backgroundVideoContainer = document.getElementById('background-video-container');
|
||||
if (backgroundVideoContainer) {
|
||||
console.log('Clearing background video container');
|
||||
try {
|
||||
backgroundVideoContainer.innerHTML = '<div style="opacity: 0;">Video stopped</div>';
|
||||
console.log('✅ Background video container cleared');
|
||||
} catch (containerError) {
|
||||
console.warn('Error clearing background video container:', containerError);
|
||||
console.error('❌ Error stopping Quick Play background video:', bgVideoError);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up video player manager - THIS IS THE IMPORTANT ONE
|
||||
// Clean up video player manager
|
||||
if (window.videoPlayerManager) {
|
||||
console.log('Stopping all videos via VideoPlayerManager');
|
||||
try {
|
||||
window.videoPlayerManager.stopAllVideos();
|
||||
console.log('All videos stopped via VideoPlayerManager');
|
||||
} catch (videoManagerError) {
|
||||
console.warn('Error stopping videos via VideoPlayerManager:', videoManagerError);
|
||||
console.error('❌ Error stopping videos via VideoPlayerManager:', videoManagerError);
|
||||
}
|
||||
} else {
|
||||
console.log('VideoPlayerManager not found - trying manual video cleanup');
|
||||
}
|
||||
|
||||
// Manual fallback - find and stop all video elements (do this regardless)
|
||||
try {
|
||||
const allVideos = document.querySelectorAll('video');
|
||||
console.log(`Found ${allVideos.length} video elements to stop`);
|
||||
allVideos.forEach((video, index) => {
|
||||
const videoId = video.id || 'unknown';
|
||||
const videoSrc = video.src || video.currentSrc || 'no source';
|
||||
console.log(`Stopping video element ${index}: ID="${videoId}", src="${videoSrc}"`);
|
||||
|
||||
try {
|
||||
video.pause();
|
||||
video.currentTime = 0;
|
||||
video.muted = true; // Mute first to stop audio immediately
|
||||
video.src = '';
|
||||
// Remove source elements too
|
||||
const sources = video.querySelectorAll('source');
|
||||
sources.forEach(source => source.src = '');
|
||||
video.load(); // Force reload to clear the video
|
||||
|
||||
console.log(`✅ Stopped video ${index} (${videoId})`);
|
||||
} catch (singleVideoError) {
|
||||
console.warn(`❌ Error stopping video ${index} (${videoId}):`, singleVideoError);
|
||||
}
|
||||
});
|
||||
console.log(`✅ Manual video cleanup complete - processed ${allVideos.length} videos`);
|
||||
} catch (manualError) {
|
||||
console.warn('❌ Error in manual video cleanup:', manualError);
|
||||
}
|
||||
console.log('✅ All timers cleared and videos stopped');
|
||||
|
||||
// Clean up periodic popups and any active popups
|
||||
if (gameInstance && gameInstance.popupImageManager) {
|
||||
|
|
@ -5449,7 +5706,26 @@
|
|||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `quick-play-session-${timestamp}.${extension}`;
|
||||
|
||||
// Try to save to user-selected directory first (if available)
|
||||
// Try to save to file system first (Electron)
|
||||
if (window.desktopFileManager && window.desktopFileManager.isElectron) {
|
||||
try {
|
||||
const videoData = await window.desktopFileManager.saveVideo(blob, 'quick-play', extension);
|
||||
if (videoData) {
|
||||
console.log('✅ Session recording saved to file system:', videoData.filename);
|
||||
|
||||
if (window.flashMessageManager) {
|
||||
window.flashMessageManager.show(`📹 Recording saved: ${videoData.filename}`, 'positive');
|
||||
}
|
||||
|
||||
recordedChunks = [];
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ File system save failed, trying directory handle:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to save to user-selected directory (if available)
|
||||
if (quickPlaySettings.webcamDirectoryHandle) {
|
||||
try {
|
||||
const fileHandle = await quickPlaySettings.webcamDirectoryHandle.getFileHandle(filename, { create: true });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
@echo off
|
||||
REM ==============================================================================
|
||||
REM Reset Gooner Training Academy to Fresh Install
|
||||
REM Windows Batch Script
|
||||
REM ==============================================================================
|
||||
|
||||
echo.
|
||||
echo ========================================================================
|
||||
echo Gooner Training Academy - Fresh Install Reset
|
||||
echo ========================================================================
|
||||
echo.
|
||||
echo This will DELETE ALL local data including settings and progress.
|
||||
echo This action CANNOT be undone!
|
||||
echo.
|
||||
|
||||
choice /C YN /M "Are you sure you want to continue"
|
||||
if errorlevel 2 goto :cancel
|
||||
if errorlevel 1 goto :proceed
|
||||
|
||||
:cancel
|
||||
echo.
|
||||
echo Reset cancelled. No changes were made.
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
:proceed
|
||||
echo.
|
||||
echo Starting reset process...
|
||||
echo.
|
||||
|
||||
REM Close any running Electron instances
|
||||
echo [1/2] Closing running instances...
|
||||
taskkill /F /IM electron.exe 2>nul
|
||||
timeout /t 2 /nobreak >nul
|
||||
|
||||
REM Clear Electron userData directory (this is where localStorage persists)
|
||||
echo [2/2] Clearing Electron user data...
|
||||
set USERDATA_PATH=%APPDATA%\webGame
|
||||
if exist "%USERDATA_PATH%" (
|
||||
echo Deleting: %USERDATA_PATH%
|
||||
rmdir /S /Q "%USERDATA_PATH%"
|
||||
echo Deleted Electron user data directory
|
||||
) else (
|
||||
echo User data directory not found (this is normal for first run)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ========================================================================
|
||||
echo Reset Complete!
|
||||
echo ========================================================================
|
||||
echo.
|
||||
echo All settings and progress have been cleared.
|
||||
echo Next time you run 'npm start', it will be like a fresh install.
|
||||
echo.
|
||||
pause
|
||||
exit /b 0
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# Reset Gooner Training Academy to Fresh Install
|
||||
# macOS/Linux Shell Script
|
||||
# ==============================================================================
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo ""
|
||||
echo "========================================================================"
|
||||
echo " Gooner Training Academy - Fresh Install Reset"
|
||||
echo "========================================================================"
|
||||
echo ""
|
||||
echo -e "${YELLOW}This will DELETE ALL local data including settings and progress.${NC}"
|
||||
echo -e "${RED}This action CANNOT be undone!${NC}"
|
||||
echo ""
|
||||
|
||||
# Prompt for confirmation
|
||||
read -p "Are you sure you want to continue? (yes/no): " -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]
|
||||
then
|
||||
echo -e "${GREEN}Reset cancelled. No changes were made.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Starting reset process..."
|
||||
echo ""
|
||||
|
||||
# Close any running Electron instances
|
||||
echo "[1/2] Closing running instances..."
|
||||
pkill -f "electron" 2>/dev/null
|
||||
pkill -f "Electron" 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# Clear Electron userData directory
|
||||
echo "[2/2] Clearing Electron user data..."
|
||||
|
||||
# Determine the userData path based on OS
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
USERDATA_PATH="$HOME/Library/Application Support/webGame"
|
||||
else
|
||||
# Linux
|
||||
USERDATA_PATH="$HOME/.config/webGame"
|
||||
fi
|
||||
|
||||
if [ -d "$USERDATA_PATH" ]; then
|
||||
echo "Deleting: $USERDATA_PATH"
|
||||
rm -rf "$USERDATA_PATH"
|
||||
echo "Deleted Electron user data directory"
|
||||
else
|
||||
echo "User data directory not found (this is normal for first run)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================================================"
|
||||
echo -e "${GREEN} Reset Complete!${NC}"
|
||||
echo "========================================================================"
|
||||
echo ""
|
||||
echo "All settings and progress have been cleared."
|
||||
echo "Next time you run 'npm start', it will be like a fresh install."
|
||||
echo ""
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Reset to Fresh Install - Gooner Training Academy</title>
|
||||
<link rel="stylesheet" href="src/styles/color-variables.css">
|
||||
<link rel="stylesheet" href="src/styles/styles.css">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.reset-container {
|
||||
max-width: 600px;
|
||||
background: var(--bg-secondary);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-container h1 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: rgba(255, 59, 48, 0.1);
|
||||
border: 2px solid #ff3b30;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
color: #ff3b30;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.data-list {
|
||||
text-align: left;
|
||||
background: var(--bg-primary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.data-list ul {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.data-list li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 15px 30px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ff3b30;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #ff1f14;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-primary-overlay-10);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-message.success {
|
||||
background: rgba(52, 199, 89, 0.1);
|
||||
border: 2px solid #34c759;
|
||||
color: #34c759;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
background: rgba(255, 59, 48, 0.1);
|
||||
border: 2px solid #ff3b30;
|
||||
color: #ff3b30;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-size: 48px;
|
||||
color: var(--color-primary);
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="reset-container">
|
||||
<h1>🔄 Reset to Fresh Install</h1>
|
||||
|
||||
<div class="warning">
|
||||
⚠️ IMPORTANT: You must use the script files to reset!
|
||||
</div>
|
||||
|
||||
<div class="data-list">
|
||||
<p><strong>Electron stores data persistently - clearing it here won't work!</strong></p>
|
||||
<p style="margin-top: 15px;">To properly reset the application:</p>
|
||||
<ol style="text-align: left; margin-top: 10px;">
|
||||
<li><strong>Close this application completely</strong></li>
|
||||
<li><strong>Windows:</strong> Run <code>reset-to-fresh.bat</code></li>
|
||||
<li><strong>Mac/Linux:</strong> Run <code>./reset-to-fresh.sh</code></li>
|
||||
</ol>
|
||||
<hr style="margin: 20px 0; border-color: var(--color-primary);">
|
||||
<p><strong>The script will delete ALL of the following:</strong></p>
|
||||
<ul>
|
||||
<li>Player stats and progress</li>
|
||||
<li>Custom tasks and consequences</li>
|
||||
<li>Saved Quick Play settings and presets</li>
|
||||
<li>Flash messages and popup image settings</li>
|
||||
<li>Theme preferences</li>
|
||||
<li>Linked image directories</li>
|
||||
<li>Linked video directories</li>
|
||||
<li>Linked audio directories</li>
|
||||
<li>Captured photos</li>
|
||||
<li>Verification photos</li>
|
||||
<li>All localStorage data</li>
|
||||
<li>All IndexedDB data</li>
|
||||
</ul>
|
||||
<p style="color: var(--color-primary); margin-top: 15px;">
|
||||
<strong>NOTE:</strong> Your actual media files will NOT be deleted.
|
||||
Only the links to them will be cleared.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="reset-btn" class="btn btn-danger">Reset to Fresh Install</button>
|
||||
<button id="cancel-btn" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div id="status-message" class="status-message"></div>
|
||||
<div id="countdown" class="countdown" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const resetBtn = document.getElementById('reset-btn');
|
||||
const cancelBtn = document.getElementById('cancel-btn');
|
||||
const statusMessage = document.getElementById('status-message');
|
||||
const countdownElement = document.getElementById('countdown');
|
||||
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
window.location.href = 'index.html';
|
||||
});
|
||||
|
||||
resetBtn.addEventListener('click', async () => {
|
||||
// Double confirmation
|
||||
const confirmed = confirm('Are you absolutely sure you want to delete ALL your data?\n\nThis action CANNOT be undone!');
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
resetBtn.disabled = true;
|
||||
cancelBtn.disabled = true;
|
||||
|
||||
// Clear all localStorage
|
||||
console.log('Clearing localStorage...');
|
||||
const itemsCleared = [];
|
||||
|
||||
// List of known localStorage keys to clear
|
||||
const keysToRemove = [
|
||||
'playerStats',
|
||||
'gameProgress',
|
||||
'quickPlaySettings',
|
||||
'linkedImageDirectories',
|
||||
'linkedVideoDirectories',
|
||||
'linkedIndividualVideos',
|
||||
'linkedIndividualImages',
|
||||
'linkedAudioDirectories',
|
||||
'capturedPhotos',
|
||||
'verificationPhotos',
|
||||
'unifiedVideoLibrary',
|
||||
'videoLibrary',
|
||||
'videoFiles',
|
||||
'customTasks',
|
||||
'disabledTasks',
|
||||
'flashMessages',
|
||||
'flashMessageConfig',
|
||||
'popupImageConfig',
|
||||
'themePreference',
|
||||
'audioSettings',
|
||||
'webcamRecordingDirectory',
|
||||
'webcamDirectoryHandleId',
|
||||
'taskData',
|
||||
'consequenceData',
|
||||
'customTags',
|
||||
'sessionHistory',
|
||||
'achievements'
|
||||
];
|
||||
|
||||
// Remove specific keys
|
||||
keysToRemove.forEach(key => {
|
||||
if (localStorage.getItem(key) !== null) {
|
||||
localStorage.removeItem(key);
|
||||
itemsCleared.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Also clear any remaining keys (in case there are others)
|
||||
const allKeys = Object.keys(localStorage);
|
||||
allKeys.forEach(key => {
|
||||
if (!itemsCleared.includes(key)) {
|
||||
localStorage.removeItem(key);
|
||||
itemsCleared.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Cleared ${itemsCleared.length} localStorage items:`, itemsCleared);
|
||||
|
||||
// Clear all IndexedDB databases
|
||||
console.log('Clearing IndexedDB...');
|
||||
const databases = await indexedDB.databases();
|
||||
const dbPromises = databases.map(db => {
|
||||
return new Promise((resolve) => {
|
||||
console.log(`Deleting IndexedDB: ${db.name}`);
|
||||
const request = indexedDB.deleteDatabase(db.name);
|
||||
request.onsuccess = () => {
|
||||
console.log(`✅ Deleted IndexedDB: ${db.name}`);
|
||||
resolve();
|
||||
};
|
||||
request.onerror = () => {
|
||||
console.warn(`Failed to delete IndexedDB: ${db.name}`);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(dbPromises);
|
||||
|
||||
// Clear sessionStorage too
|
||||
console.log('Clearing sessionStorage...');
|
||||
sessionStorage.clear();
|
||||
|
||||
// Show success message
|
||||
statusMessage.className = 'status-message success';
|
||||
statusMessage.textContent = `✅ Success! Cleared ${itemsCleared.length} localStorage items and ${databases.length} databases.`;
|
||||
|
||||
// Show countdown and redirect
|
||||
countdownElement.style.display = 'block';
|
||||
let countdown = 3;
|
||||
countdownElement.textContent = countdown;
|
||||
|
||||
const countdownInterval = setInterval(() => {
|
||||
countdown--;
|
||||
if (countdown > 0) {
|
||||
countdownElement.textContent = countdown;
|
||||
} else {
|
||||
clearInterval(countdownInterval);
|
||||
countdownElement.textContent = 'Redirecting...';
|
||||
setTimeout(() => {
|
||||
window.location.href = 'index.html';
|
||||
}, 500);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during reset:', error);
|
||||
statusMessage.className = 'status-message error';
|
||||
statusMessage.textContent = `❌ Error: ${error.message}`;
|
||||
resetBtn.disabled = false;
|
||||
cancelBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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%
|
||||
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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<Object|null>} 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>} 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<boolean>} 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<Object|null>} 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}`;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 =
|
||||
`<span style="color: var(--color-success);">✅ ${trainingPhotoLibrary.length} photos available</span>`;
|
||||
|
||||
// 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 = `<span style="color: var(--color-success);">✅ ${trainingPhotoLibrary.length} photos available (${newPhotos.length} newly captured)</span>`;
|
||||
}
|
||||
|
||||
// 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 @@
|
|||
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<h2 style="color: white; margin-bottom: 20px;">📸 Captured Photos Gallery</h2>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 15px; max-width: 90%; max-height: 70%; overflow-y: auto; justify-content: center;">
|
||||
${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 `
|
||||
<div style="border: 2px solid var(--text-white); border-radius: 10px; overflow: hidden;">
|
||||
<img src="${photo.path}" style="width: 200px; height: 150px; object-fit: cover;" alt="Captured photo ${index + 1}">
|
||||
<img src="${imageSrc}" style="width: 200px; height: 150px; object-fit: cover;" alt="Captured photo ${index + 1}">
|
||||
<div style="background: rgba(0,0,0,0.8); color: white; padding: 5px; text-align: center; font-size: 12px;">
|
||||
Photo ${index + 1} - ${new Date(photo.timestamp).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`}).join('')}
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" style="margin-top: 20px; padding: 10px 20px; background: var(--color-danger); color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px;">
|
||||
❌ Close Gallery
|
||||
|
|
|
|||
Loading…
Reference in New Issue