1539 lines
63 KiB
HTML
1539 lines
63 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Hypno Gallery - Immersive Slideshow</title>
|
|
|
|
<!-- Core Styles -->
|
|
<link rel="stylesheet" href="src/styles/styles.css">
|
|
<link rel="stylesheet" href="src/styles/styles-dark-edgy.css">
|
|
|
|
<style>
|
|
/* Hypno Gallery Specific Styles */
|
|
.hypno-header {
|
|
text-align: center;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
padding: 2rem;
|
|
margin: 1rem;
|
|
border-radius: 15px;
|
|
border: 2px solid #667eea;
|
|
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
|
}
|
|
|
|
.hypno-header h1 {
|
|
color: #ffffff;
|
|
font-size: 2.5rem;
|
|
margin: 0;
|
|
text-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.hypno-header .subtitle {
|
|
color: #e8e8e8;
|
|
font-size: 1.2rem;
|
|
margin-top: 0.5rem;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Settings Panel */
|
|
.settings-panel {
|
|
background: rgba(102, 126, 234, 0.1);
|
|
border: 2px solid #667eea;
|
|
border-radius: 15px;
|
|
padding: 2rem;
|
|
margin: 1rem;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.settings-section {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.settings-section h3 {
|
|
color: #667eea;
|
|
margin-bottom: 1rem;
|
|
font-size: 1.3rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.setting-group {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 10px;
|
|
padding: 1.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.setting-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin: 1rem 0;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.setting-label {
|
|
color: #e8e8e8;
|
|
font-weight: bold;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.setting-control {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.setting-control input[type="range"] {
|
|
width: 100%;
|
|
background: rgba(102, 126, 234, 0.2);
|
|
border-radius: 5px;
|
|
height: 8px;
|
|
outline: none;
|
|
}
|
|
|
|
.setting-control input[type="range"]::-webkit-slider-thumb {
|
|
background: #667eea;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
.setting-control select {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
color: #ffffff;
|
|
border: 1px solid #667eea;
|
|
border-radius: 5px;
|
|
padding: 0.5rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.setting-value {
|
|
color: #667eea;
|
|
font-weight: bold;
|
|
min-width: 80px;
|
|
text-align: right;
|
|
}
|
|
|
|
.checkbox-setting {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.checkbox-setting input[type="checkbox"] {
|
|
transform: scale(1.2);
|
|
accent-color: #667eea;
|
|
}
|
|
|
|
/* Library Status */
|
|
.library-status {
|
|
background: rgba(52, 152, 219, 0.1);
|
|
border: 1px solid #3498db;
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin: 1rem 0;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.library-status h3 {
|
|
color: #3498db;
|
|
margin: 0 0 0.5rem 0;
|
|
}
|
|
|
|
/* Control Buttons */
|
|
.control-buttons {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 2rem;
|
|
margin: 2rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.start-btn {
|
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 25px;
|
|
padding: 1rem 2rem;
|
|
font-size: 1.2rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
|
min-width: 200px;
|
|
}
|
|
|
|
.start-btn:hover:not(:disabled) {
|
|
background: linear-gradient(135deg, #5a6fd8, #6a42a0);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.start-btn:disabled {
|
|
background: #6c757d;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.back-btn {
|
|
background: rgba(52, 73, 94, 0.9);
|
|
color: #ecf0f1;
|
|
border: 1px solid #34495e;
|
|
border-radius: 8px;
|
|
padding: 0.5rem 1rem;
|
|
text-decoration: none;
|
|
font-weight: bold;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.back-btn:hover {
|
|
background: rgba(52, 73, 94, 1);
|
|
border-color: #3498db;
|
|
}
|
|
|
|
/* Slideshow Container (hidden initially) */
|
|
.slideshow-container {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: #000;
|
|
display: none;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 9999;
|
|
}
|
|
|
|
.slideshow-image {
|
|
max-width: 90%;
|
|
max-height: 90%;
|
|
object-fit: contain;
|
|
transition: opacity 0.5s ease-in-out;
|
|
}
|
|
|
|
.slideshow-controls {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
gap: 1rem;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
padding: 1rem;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.slideshow-controls button {
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
padding: 0.5rem 1rem;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.slideshow-controls button:hover {
|
|
background: #5a6fd8;
|
|
}
|
|
|
|
.slideshow-info {
|
|
position: fixed;
|
|
top: 20px;
|
|
left: 20px;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
color: white;
|
|
padding: 1rem;
|
|
border-radius: 10px;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.navigation {
|
|
position: fixed;
|
|
top: 20px;
|
|
left: 20px;
|
|
z-index: 1000;
|
|
}
|
|
|
|
/* Progress Bar */
|
|
.progress-container {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 4px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
display: none;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
width: 0%;
|
|
transition: width linear;
|
|
}
|
|
|
|
/* Directory Selection Styles */
|
|
.directory-selection-container {
|
|
width: 100%;
|
|
}
|
|
|
|
.linked-directories-list {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 1px solid rgba(102, 126, 234, 0.3);
|
|
border-radius: 8px;
|
|
padding: 0.8rem;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.directory-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0.5rem;
|
|
margin: 0.3rem 0;
|
|
background: rgba(102, 126, 234, 0.1);
|
|
border: 1px solid rgba(102, 126, 234, 0.2);
|
|
border-radius: 6px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.directory-item:hover {
|
|
background: rgba(102, 126, 234, 0.2);
|
|
border-color: rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.directory-item.selected {
|
|
background: rgba(102, 126, 234, 0.3);
|
|
border-color: #667eea;
|
|
}
|
|
|
|
.directory-checkbox {
|
|
margin-right: 0.5rem;
|
|
transform: scale(1.1);
|
|
accent-color: #667eea;
|
|
}
|
|
|
|
.directory-path {
|
|
flex: 1;
|
|
color: #e8e8e8;
|
|
font-size: 0.85rem;
|
|
word-break: break-all;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.directory-info {
|
|
color: #667eea;
|
|
font-size: 0.75rem;
|
|
text-align: right;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.no-directories-message {
|
|
text-align: center;
|
|
color: #ccc;
|
|
font-style: italic;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.directory-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
.setting-row {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.setting-label {
|
|
min-width: auto;
|
|
}
|
|
|
|
.setting-control {
|
|
min-width: auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.control-buttons {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.directory-item {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.directory-path {
|
|
margin: 0.2rem 0;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Navigation -->
|
|
<div class="navigation">
|
|
<a href="index.html" class="back-btn">← Back to Main</a>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="main-content">
|
|
<!-- Header -->
|
|
<div class="hypno-header">
|
|
<h1>🌀 Hypno Gallery</h1>
|
|
<div class="subtitle">Immersive Visual Slideshow Experience</div>
|
|
</div>
|
|
|
|
<!-- Library Status -->
|
|
<div class="library-status" id="libraryStatus">
|
|
<h3>📚 Media Library Status</h3>
|
|
<div id="imageLibraryStatus">Initializing image library...</div>
|
|
<button onclick="showImageGallery()" id="view-images-btn" style="margin-top: 10px; padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; display: none;">
|
|
🖼️ View Image Library
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Settings Panel -->
|
|
<div class="settings-panel">
|
|
<!-- Timing Settings -->
|
|
<div class="settings-section">
|
|
<h3>⏱️ Timing Settings</h3>
|
|
<div class="setting-group">
|
|
<div class="setting-row">
|
|
<span class="setting-label">Timing Mode:</span>
|
|
<div class="setting-control">
|
|
<select id="timingMode">
|
|
<option value="constant">Constant - Fixed interval</option>
|
|
<option value="random">Random - Varies between min/max</option>
|
|
<option value="wave">Wave - Sine wave variation</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row" id="constantDuration">
|
|
<span class="setting-label">Duration:</span>
|
|
<div class="setting-control">
|
|
<input type="range" id="durationSlider" min="500" max="10000" value="3000" step="100">
|
|
</div>
|
|
<span class="setting-value" id="durationValue">3.0s</span>
|
|
</div>
|
|
|
|
<div class="setting-row" id="randomRange" style="display: none;">
|
|
<span class="setting-label">Random Range:</span>
|
|
<div class="setting-control" style="display: flex; gap: 1rem; align-items: center;">
|
|
<div style="flex: 1;">
|
|
<label style="color: #ccc; font-size: 0.9rem;">Min:</label>
|
|
<input type="range" id="minDurationSlider" min="500" max="8000" value="1500" step="100">
|
|
<span id="minDurationValue" style="color: #667eea;">1.5s</span>
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<label style="color: #ccc; font-size: 0.9rem;">Max:</label>
|
|
<input type="range" id="maxDurationSlider" min="2000" max="12000" value="5000" step="100">
|
|
<span id="maxDurationValue" style="color: #667eea;">5.0s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row" id="waveSettings" style="display: none;">
|
|
<span class="setting-label">Wave Settings:</span>
|
|
<div class="setting-control" style="display: flex; gap: 1rem; align-items: center;">
|
|
<div style="flex: 1;">
|
|
<label style="color: #ccc; font-size: 0.9rem;">Min:</label>
|
|
<input type="range" id="waveMinSlider" min="500" max="5000" value="1000" step="100">
|
|
<span id="waveMinValue" style="color: #667eea;">1.0s</span>
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<label style="color: #ccc; font-size: 0.9rem;">Max:</label>
|
|
<input type="range" id="waveMaxSlider" min="2000" max="10000" value="5000" step="100">
|
|
<span id="waveMaxValue" style="color: #667eea;">5.0s</span>
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<label style="color: #ccc; font-size: 0.9rem;">Rate:</label>
|
|
<input type="range" id="waveRateSlider" min="50" max="200" value="100" step="10">
|
|
<span id="waveRateValue" style="color: #667eea;">100</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Directory Selection -->
|
|
<div class="settings-section">
|
|
<h3>📁 Directory Selection</h3>
|
|
<div class="setting-group">
|
|
<div class="setting-row" style="align-items: flex-start;">
|
|
<span class="setting-label">Image Sources:</span>
|
|
<div class="setting-control">
|
|
<div class="directory-selection-container">
|
|
<div class="checkbox-setting" style="margin-bottom: 0.5rem;">
|
|
<input type="checkbox" id="includeCapturedPhotos" checked>
|
|
<span class="setting-label">📷 Include Captured Photos</span>
|
|
</div>
|
|
<div id="linkedDirectoriesList" class="linked-directories-list">
|
|
<!-- Linked directories will be populated here -->
|
|
</div>
|
|
<div class="directory-actions" style="margin-top: 0.5rem;">
|
|
<button id="refreshDirectories" class="btn btn-small" style="background: rgba(102, 126, 234, 0.8); color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 0.8rem;">
|
|
🔄 Refresh
|
|
</button>
|
|
<span id="selectedDirectoryCount" style="color: #667eea; font-size: 0.85rem; margin-left: 0.5rem;">
|
|
0 directories selected
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Settings -->
|
|
<div class="settings-section">
|
|
<h3>🖼️ Image Settings</h3>
|
|
<div class="setting-group">
|
|
<div class="setting-row">
|
|
<span class="setting-label">Image Order:</span>
|
|
<div class="setting-control">
|
|
<select id="imageOrder">
|
|
<option value="random">Random - Shuffle images</option>
|
|
<option value="sequential">Sequential - Original order</option>
|
|
<option value="reverse">Reverse - Reverse order</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<span class="setting-label">Fit Mode:</span>
|
|
<div class="setting-control">
|
|
<select id="fitMode">
|
|
<option value="contain">Contain - Fit entire image</option>
|
|
<option value="cover">Cover - Fill screen (crop if needed)</option>
|
|
<option value="stretch">Stretch - Fill screen (distort if needed)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<div class="checkbox-setting">
|
|
<input type="checkbox" id="loopPlayback" checked>
|
|
<span class="setting-label">Loop Playback</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<div class="checkbox-setting">
|
|
<input type="checkbox" id="showProgress" checked>
|
|
<span class="setting-label">Show Progress Bar</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transition Settings -->
|
|
<div class="settings-section">
|
|
<h3>✨ Transition Settings</h3>
|
|
<div class="setting-group">
|
|
<div class="setting-row">
|
|
<span class="setting-label">Transition Type:</span>
|
|
<div class="setting-control">
|
|
<select id="transitionType">
|
|
<option value="fade">Fade - Cross-fade between images</option>
|
|
<option value="none">None - Instant change</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row" id="transitionDurationRow">
|
|
<span class="setting-label">Transition Duration:</span>
|
|
<div class="setting-control">
|
|
<input type="range" id="transitionDuration" min="100" max="2000" value="500" step="50">
|
|
</div>
|
|
<span class="setting-value" id="transitionDurationValue">0.5s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Control Buttons -->
|
|
<div class="control-buttons">
|
|
<button class="start-btn" id="startSlideshowBtn" onclick="startSlideshow()">
|
|
🌀 Start Hypno Gallery
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slideshow Container (Hidden) -->
|
|
<div class="slideshow-container" id="slideshowContainer">
|
|
<img class="slideshow-image" id="slideshowImage" alt="Slideshow Image">
|
|
|
|
<div class="slideshow-info" id="slideshowInfo">
|
|
<div>Image: <span id="currentImageIndex">0</span> / <span id="totalImages">0</span></div>
|
|
<div>Timer: <span id="remainingTime">0.0s</span></div>
|
|
<div>Status: <span id="slideshowStatus">Ready</span></div>
|
|
</div>
|
|
|
|
<div class="slideshow-controls">
|
|
<button onclick="pauseSlideshow()" id="pauseBtn">⏸️ Pause</button>
|
|
<button onclick="previousImage()" id="prevBtn">⏮️ Previous</button>
|
|
<button onclick="nextImage()" id="nextBtn">⏭️ Next</button>
|
|
<button onclick="stopSlideshow()" id="stopBtn">⏹️ Stop</button>
|
|
</div>
|
|
|
|
<div class="progress-container" id="progressContainer">
|
|
<div class="progress-bar" id="progressBar"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Core Scripts -->
|
|
<script>
|
|
// Only load Electron-specific scripts if in Electron environment
|
|
if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
|
|
console.log('🖥️ Electron environment detected - loading desktop scripts');
|
|
} else {
|
|
console.log('🌐 Browser environment detected - skipping desktop scripts');
|
|
}
|
|
</script>
|
|
<script src="src/utils/desktop-file-manager.js"></script>
|
|
<script src="src/features/images/popupImageManager.js"></script>
|
|
|
|
<script>
|
|
// Hypno Gallery Global Variables
|
|
let imageLibrary = [];
|
|
let currentSettings = {
|
|
timingMode: 'constant',
|
|
duration: 3000,
|
|
minDuration: 1500,
|
|
maxDuration: 5000,
|
|
waveMin: 1000,
|
|
waveMax: 5000,
|
|
waveRate: 100,
|
|
imageOrder: 'random',
|
|
fitMode: 'contain',
|
|
loopPlayback: true,
|
|
showProgress: true,
|
|
transitionType: 'fade',
|
|
transitionDuration: 500,
|
|
selectedDirectories: [], // Array of selected directory paths
|
|
includeCapturedPhotos: true
|
|
};
|
|
|
|
let slideshow = {
|
|
images: [],
|
|
currentIndex: 0,
|
|
isPlaying: false,
|
|
isPaused: false,
|
|
timer: null,
|
|
nextTimeout: null,
|
|
startTime: 0,
|
|
waveTime: 0
|
|
};
|
|
|
|
// Initialize Hypno Gallery
|
|
async function initializeHypnoGallery() {
|
|
console.log('🌀 Initializing Hypno Gallery...');
|
|
|
|
try {
|
|
// Initialize directory selection
|
|
await initializeDirectorySelection();
|
|
|
|
// Initialize image library
|
|
await initializeImageLibrary();
|
|
|
|
// Set up event listeners
|
|
setupEventListeners();
|
|
|
|
console.log('✅ Hypno Gallery ready');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error initializing Hypno Gallery:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize Directory Selection
|
|
async function initializeDirectorySelection() {
|
|
console.log('📁 Initializing directory selection...');
|
|
|
|
try {
|
|
// Load linked directories from localStorage
|
|
const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
|
console.log('📂 Found linked directories:', linkedDirectories);
|
|
|
|
// Load saved directory selection preferences
|
|
const savedSettings = JSON.parse(localStorage.getItem('hypnoGallerySettings') || '{}');
|
|
if (savedSettings.selectedDirectories && Array.isArray(savedSettings.selectedDirectories)) {
|
|
currentSettings.selectedDirectories = savedSettings.selectedDirectories;
|
|
} else {
|
|
// If no previous selection, default to empty array (user must manually select)
|
|
currentSettings.selectedDirectories = [];
|
|
}
|
|
if (savedSettings.includeCapturedPhotos !== undefined) {
|
|
currentSettings.includeCapturedPhotos = savedSettings.includeCapturedPhotos;
|
|
document.getElementById('includeCapturedPhotos').checked = savedSettings.includeCapturedPhotos;
|
|
}
|
|
|
|
console.log('💾 Loaded directory preferences:', currentSettings.selectedDirectories);
|
|
|
|
// Populate directory list
|
|
populateDirectoryList(linkedDirectories);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error initializing directory selection:', error);
|
|
}
|
|
}
|
|
|
|
// Populate Directory List
|
|
function populateDirectoryList(directories) {
|
|
const directoriesList = document.getElementById('linkedDirectoriesList');
|
|
|
|
if (directories.length === 0) {
|
|
directoriesList.innerHTML = '<div class="no-directories-message">No image directories linked. Use Library to add directories.</div>';
|
|
updateSelectedDirectoryCount();
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
directories.forEach((directory, index) => {
|
|
const dirPath = typeof directory === 'string' ? directory : directory.path;
|
|
const dirName = typeof directory === 'string' ? directory : (directory.name || directory.path);
|
|
|
|
if (!dirPath) return;
|
|
|
|
const isSelected = currentSettings.selectedDirectories.includes(dirPath);
|
|
|
|
// Get directory name from path for display
|
|
const displayName = dirPath.split('\\').pop() || dirPath.split('/').pop() || dirPath;
|
|
|
|
html += `
|
|
<div class="directory-item ${isSelected ? 'selected' : ''}" data-path="${dirPath}">
|
|
<input type="checkbox" class="directory-checkbox"
|
|
data-directory="${dirPath}"
|
|
${isSelected ? 'checked' : ''}>
|
|
<div class="directory-path" title="${dirPath}">
|
|
📁 ${displayName}
|
|
</div>
|
|
<div class="directory-info" id="dir-count-${index}">
|
|
Scanning...
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
directoriesList.innerHTML = html;
|
|
|
|
// Add event listeners to checkboxes
|
|
directoriesList.querySelectorAll('.directory-checkbox').forEach(checkbox => {
|
|
checkbox.addEventListener('change', handleDirectorySelection);
|
|
});
|
|
|
|
// Count images in each directory (async)
|
|
directories.forEach(async (directory, index) => {
|
|
const dirPath = typeof directory === 'string' ? directory : directory.path;
|
|
if (dirPath) {
|
|
const count = await countImagesInDirectory(dirPath);
|
|
const countElement = document.getElementById(`dir-count-${index}`);
|
|
if (countElement) {
|
|
countElement.textContent = `${count} images`;
|
|
}
|
|
}
|
|
});
|
|
|
|
updateSelectedDirectoryCount();
|
|
}
|
|
|
|
// Handle Directory Selection
|
|
function handleDirectorySelection(event) {
|
|
const directoryPath = event.target.dataset.directory;
|
|
const isChecked = event.target.checked;
|
|
const directoryItem = event.target.closest('.directory-item');
|
|
|
|
if (isChecked) {
|
|
if (!currentSettings.selectedDirectories.includes(directoryPath)) {
|
|
currentSettings.selectedDirectories.push(directoryPath);
|
|
}
|
|
directoryItem.classList.add('selected');
|
|
} else {
|
|
currentSettings.selectedDirectories = currentSettings.selectedDirectories.filter(
|
|
path => path !== directoryPath
|
|
);
|
|
directoryItem.classList.remove('selected');
|
|
}
|
|
|
|
updateSelectedDirectoryCount();
|
|
saveHypnoGallerySettings();
|
|
|
|
console.log('📁 Directory selection updated:', currentSettings.selectedDirectories);
|
|
}
|
|
|
|
// Count Images in Directory
|
|
async function countImagesInDirectory(directoryPath) {
|
|
try {
|
|
if (!window.electronAPI || !window.electronAPI.readDirectory) {
|
|
return 0;
|
|
}
|
|
|
|
const files = await window.electronAPI.readDirectory(directoryPath);
|
|
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
|
|
|
|
return files.filter(file => {
|
|
const fileName = typeof file === 'object' ? file.name : file;
|
|
return imageExtensions.test(fileName);
|
|
}).length;
|
|
|
|
} catch (error) {
|
|
console.warn(`Warning: Could not count images in ${directoryPath}:`, error);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Update Selected Directory Count
|
|
function updateSelectedDirectoryCount() {
|
|
const countElement = document.getElementById('selectedDirectoryCount');
|
|
if (countElement) {
|
|
const selectedCount = currentSettings.selectedDirectories.length;
|
|
countElement.textContent = `${selectedCount} directories selected`;
|
|
|
|
if (selectedCount === 0) {
|
|
countElement.style.color = '#f39c12';
|
|
} else {
|
|
countElement.style.color = '#667eea';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save Hypno Gallery Settings
|
|
function saveHypnoGallerySettings() {
|
|
try {
|
|
localStorage.setItem('hypnoGallerySettings', JSON.stringify({
|
|
selectedDirectories: currentSettings.selectedDirectories,
|
|
includeCapturedPhotos: currentSettings.includeCapturedPhotos
|
|
}));
|
|
} catch (error) {
|
|
console.warn('Warning: Could not save Hypno Gallery settings:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize Image Library
|
|
async function initializeImageLibrary() {
|
|
try {
|
|
console.log('🖼️ Initializing image library...');
|
|
|
|
// Check if we're in Electron environment
|
|
const isElectron = window.electronAPI && (
|
|
window.electronAPI.getImageFiles ||
|
|
window.electronAPI.readDirectory
|
|
);
|
|
|
|
if (!isElectron) {
|
|
console.log('🌐 Electron API not available - checking alternative methods...');
|
|
|
|
// Try to use unified image library from desktop file manager
|
|
if (window.desktopFileManager && window.desktopFileManager.unifiedImageLibrary) {
|
|
let unifiedLibrary = window.desktopFileManager.unifiedImageLibrary;
|
|
|
|
// Filter by selected directories if any are selected
|
|
if (currentSettings.selectedDirectories.length > 0) {
|
|
unifiedLibrary = unifiedLibrary.filter(image => {
|
|
// Check if image path is within any selected directory
|
|
return currentSettings.selectedDirectories.some(selectedDir => {
|
|
const imagePath = image.path || image.fullPath || '';
|
|
const normalizedImagePath = imagePath.replace(/\\/g, '/');
|
|
const normalizedSelectedDir = selectedDir.replace(/\\/g, '/');
|
|
return normalizedImagePath.startsWith(normalizedSelectedDir);
|
|
});
|
|
});
|
|
console.log(`📁 Filtered unified library to ${unifiedLibrary.length} images from selected directories`);
|
|
}
|
|
|
|
imageLibrary = [...unifiedLibrary];
|
|
console.log(`🖼️ Using unified image library: ${imageLibrary.length} images`);
|
|
} else {
|
|
// Check for popup image library data from localStorage
|
|
const popupImageLibrary = localStorage.getItem('popupImageLibrary');
|
|
if (popupImageLibrary) {
|
|
try {
|
|
let unifiedLibrary = JSON.parse(popupImageLibrary);
|
|
|
|
// Filter by selected directories if any are selected
|
|
if (currentSettings.selectedDirectories.length > 0) {
|
|
unifiedLibrary = unifiedLibrary.filter(image => {
|
|
return currentSettings.selectedDirectories.some(selectedDir => {
|
|
const imagePath = image.path || image.fullPath || '';
|
|
const normalizedImagePath = imagePath.replace(/\\/g, '/');
|
|
const normalizedSelectedDir = selectedDir.replace(/\\/g, '/');
|
|
return normalizedImagePath.startsWith(normalizedSelectedDir);
|
|
});
|
|
});
|
|
console.log(`📁 Filtered popup library to ${unifiedLibrary.length} images from selected directories`);
|
|
}
|
|
|
|
imageLibrary = [...unifiedLibrary];
|
|
console.log(`🖼️ Using popup image library: ${imageLibrary.length} images`);
|
|
} catch (error) {
|
|
console.error('Failed to parse popup image library:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (imageLibrary.length === 0) {
|
|
const message = currentSettings.selectedDirectories.length > 0
|
|
? '⚠️ No images found in selected directories'
|
|
: '⚠️ Image library unavailable - Open from Quick Play to load library';
|
|
document.getElementById('imageLibraryStatus').innerHTML =
|
|
`<span style="color: #f39c12;">${message}</span>`;
|
|
return;
|
|
}
|
|
} else {
|
|
// Get linked image directories from localStorage
|
|
const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
|
const linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
|
|
|
|
console.log('📁 All linked directories:', linkedDirectories);
|
|
console.log('📁 Selected directories:', currentSettings.selectedDirectories);
|
|
console.log('🖼️ Linked individual images:', linkedIndividualImages);
|
|
|
|
// Filter directories based on selection (if no selection, use all)
|
|
const directoriesToScan = currentSettings.selectedDirectories.length > 0
|
|
? linkedDirectories.filter(dir => {
|
|
const dirPath = typeof dir === 'string' ? dir : dir.path;
|
|
return currentSettings.selectedDirectories.includes(dirPath);
|
|
})
|
|
: linkedDirectories;
|
|
|
|
if (directoriesToScan.length === 0 && linkedIndividualImages.length === 0 && !currentSettings.includeCapturedPhotos) {
|
|
document.getElementById('imageLibraryStatus').innerHTML =
|
|
'<span style="color: #f39c12;">⚠️ No directories selected for slideshow. Select directories in the settings above.</span>';
|
|
return;
|
|
}
|
|
|
|
imageLibrary = [];
|
|
|
|
console.log(`📂 Scanning ${directoriesToScan.length} selected directories for slideshow`);
|
|
|
|
// Scan each selected directory for images
|
|
for (const directoryData of directoriesToScan) {
|
|
try {
|
|
const directoryPath = typeof directoryData === 'string' ? directoryData : directoryData.path;
|
|
|
|
if (!directoryPath) {
|
|
console.warn('⚠️ Invalid directory data:', directoryData);
|
|
continue;
|
|
}
|
|
|
|
console.log(`📂 Scanning selected directory: ${directoryPath}`);
|
|
|
|
let images = [];
|
|
|
|
if (window.electronAPI.getImageFiles) {
|
|
images = await window.electronAPI.getImageFiles(directoryPath);
|
|
} else if (window.electronAPI.readDirectory) {
|
|
const allFiles = await window.electronAPI.readDirectory(directoryPath);
|
|
images = allFiles.filter(file =>
|
|
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.filename)
|
|
);
|
|
}
|
|
|
|
console.log(`🖼️ Found ${images.length} images in ${directoryPath}`);
|
|
|
|
images.forEach(image => {
|
|
imageLibrary.push({
|
|
name: image.name,
|
|
path: image.path,
|
|
fullPath: image.path,
|
|
size: image.size || 0,
|
|
directory: directoryPath,
|
|
dateAdded: new Date().toISOString(),
|
|
category: 'directory'
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Error scanning directory ${directoryData}:`, error);
|
|
}
|
|
}
|
|
|
|
// Add individual linked images (only if no directory selection is active, or if explicitly included)
|
|
if (currentSettings.selectedDirectories.length === 0) {
|
|
// If no directories are specifically selected, include individual images
|
|
linkedIndividualImages.forEach(imageData => {
|
|
imageLibrary.push({
|
|
...imageData,
|
|
fullPath: imageData.path || imageData.filePath,
|
|
category: 'individual'
|
|
});
|
|
});
|
|
console.log(`🖼️ Added ${linkedIndividualImages.length} individual images (no directory filter)`);
|
|
} else {
|
|
console.log(`🚫 Skipped ${linkedIndividualImages.length} individual images (directory filter active)`);
|
|
}
|
|
}
|
|
|
|
// Load captured photos from localStorage if enabled
|
|
if (currentSettings.includeCapturedPhotos) {
|
|
try {
|
|
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
|
|
capturedPhotos.forEach(photo => {
|
|
if (photo.data && (photo.data.startsWith('data:image/') || photo.imageData)) {
|
|
imageLibrary.push({
|
|
name: photo.filename || `captured_${photo.timestamp}.jpg`,
|
|
path: photo.data || photo.imageData,
|
|
fullPath: photo.data || photo.imageData,
|
|
isWebcamCapture: true,
|
|
timestamp: photo.timestamp,
|
|
category: 'captured'
|
|
});
|
|
}
|
|
});
|
|
console.log(`🖼️ Added ${capturedPhotos.length} captured photos`);
|
|
} catch (error) {
|
|
console.warn('⚠️ Failed to load captured photos:', error);
|
|
}
|
|
} else {
|
|
console.log('📷 Captured photos excluded from slideshow');
|
|
}
|
|
|
|
console.log(`🖼️ Total images loaded: ${imageLibrary.length}`);
|
|
|
|
// Update status display
|
|
if (imageLibrary.length > 0) {
|
|
let statusMessage = `✅ ${imageLibrary.length} images available`;
|
|
|
|
// Add context about filtering
|
|
if (currentSettings.selectedDirectories.length > 0) {
|
|
statusMessage += ` (from ${currentSettings.selectedDirectories.length} selected directories)`;
|
|
}
|
|
if (!currentSettings.includeCapturedPhotos) {
|
|
statusMessage += ` (captured photos excluded)`;
|
|
}
|
|
|
|
document.getElementById('imageLibraryStatus').innerHTML =
|
|
`<span style="color: #27ae60;">${statusMessage}</span>`;
|
|
document.getElementById('view-images-btn').style.display = 'inline-block';
|
|
document.getElementById('startSlideshowBtn').disabled = false;
|
|
} else {
|
|
let errorMessage = '❌ No images found';
|
|
|
|
if (currentSettings.selectedDirectories.length > 0) {
|
|
errorMessage = '❌ No images found in selected directories';
|
|
} else if (!currentSettings.includeCapturedPhotos) {
|
|
errorMessage = '❌ No images available (captured photos excluded, no directories selected)';
|
|
}
|
|
|
|
document.getElementById('imageLibraryStatus').innerHTML =
|
|
`<span style="color: #e74c3c;">${errorMessage}</span>`;
|
|
document.getElementById('startSlideshowBtn').disabled = true;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error initializing image library:', error);
|
|
document.getElementById('imageLibraryStatus').innerHTML =
|
|
'<span style="color: #e74c3c;">❌ Error loading image library</span>';
|
|
}
|
|
}
|
|
|
|
// Set up event listeners for settings
|
|
function setupEventListeners() {
|
|
// Timing mode change
|
|
document.getElementById('timingMode').addEventListener('change', (e) => {
|
|
updateTimingSettings(e.target.value);
|
|
});
|
|
|
|
// Duration sliders
|
|
document.getElementById('durationSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.duration = value;
|
|
document.getElementById('durationValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
});
|
|
|
|
document.getElementById('minDurationSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.minDuration = value;
|
|
document.getElementById('minDurationValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
|
|
// Ensure max is greater than min
|
|
const maxSlider = document.getElementById('maxDurationSlider');
|
|
if (value >= parseInt(maxSlider.value)) {
|
|
maxSlider.value = value + 500;
|
|
currentSettings.maxDuration = value + 500;
|
|
document.getElementById('maxDurationValue').textContent = ((value + 500) / 1000).toFixed(1) + 's';
|
|
}
|
|
});
|
|
|
|
document.getElementById('maxDurationSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
const minValue = parseInt(document.getElementById('minDurationSlider').value);
|
|
|
|
if (value <= minValue) {
|
|
e.target.value = minValue + 500;
|
|
currentSettings.maxDuration = minValue + 500;
|
|
document.getElementById('maxDurationValue').textContent = ((minValue + 500) / 1000).toFixed(1) + 's';
|
|
} else {
|
|
currentSettings.maxDuration = value;
|
|
document.getElementById('maxDurationValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
}
|
|
});
|
|
|
|
// Wave settings
|
|
document.getElementById('waveMinSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.waveMin = value;
|
|
document.getElementById('waveMinValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
});
|
|
|
|
document.getElementById('waveMaxSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.waveMax = value;
|
|
document.getElementById('waveMaxValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
});
|
|
|
|
document.getElementById('waveRateSlider').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.waveRate = value;
|
|
document.getElementById('waveRateValue').textContent = value;
|
|
});
|
|
|
|
// Transition duration
|
|
document.getElementById('transitionDuration').addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentSettings.transitionDuration = value;
|
|
document.getElementById('transitionDurationValue').textContent = (value / 1000).toFixed(1) + 's';
|
|
});
|
|
|
|
// Other settings
|
|
document.getElementById('imageOrder').addEventListener('change', (e) => {
|
|
currentSettings.imageOrder = e.target.value;
|
|
});
|
|
|
|
document.getElementById('fitMode').addEventListener('change', (e) => {
|
|
currentSettings.fitMode = e.target.value;
|
|
});
|
|
|
|
document.getElementById('loopPlayback').addEventListener('change', (e) => {
|
|
currentSettings.loopPlayback = e.target.checked;
|
|
});
|
|
|
|
document.getElementById('showProgress').addEventListener('change', (e) => {
|
|
currentSettings.showProgress = e.target.checked;
|
|
});
|
|
|
|
document.getElementById('transitionType').addEventListener('change', (e) => {
|
|
currentSettings.transitionType = e.target.value;
|
|
updateTransitionSettings(e.target.value);
|
|
});
|
|
|
|
// Include captured photos checkbox
|
|
document.getElementById('includeCapturedPhotos').addEventListener('change', (e) => {
|
|
currentSettings.includeCapturedPhotos = e.target.checked;
|
|
saveHypnoGallerySettings();
|
|
console.log('📷 Captured photos inclusion:', e.target.checked);
|
|
|
|
// Refresh image library
|
|
setTimeout(() => {
|
|
initializeImageLibrary();
|
|
}, 100);
|
|
});
|
|
|
|
// Refresh directories button
|
|
document.getElementById('refreshDirectories').addEventListener('click', async () => {
|
|
console.log('🔄 Refreshing directories...');
|
|
await initializeDirectorySelection();
|
|
await initializeImageLibrary();
|
|
});
|
|
|
|
// Keyboard controls
|
|
document.addEventListener('keydown', handleKeyPress);
|
|
}
|
|
|
|
// Update timing settings display
|
|
function updateTimingSettings(mode) {
|
|
currentSettings.timingMode = mode;
|
|
|
|
// Hide all timing-specific controls
|
|
document.getElementById('constantDuration').style.display = 'none';
|
|
document.getElementById('randomRange').style.display = 'none';
|
|
document.getElementById('waveSettings').style.display = 'none';
|
|
|
|
// Show relevant controls
|
|
switch (mode) {
|
|
case 'constant':
|
|
document.getElementById('constantDuration').style.display = 'flex';
|
|
break;
|
|
case 'random':
|
|
document.getElementById('randomRange').style.display = 'flex';
|
|
break;
|
|
case 'wave':
|
|
document.getElementById('waveSettings').style.display = 'flex';
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update transition settings display
|
|
function updateTransitionSettings(type) {
|
|
const durationRow = document.getElementById('transitionDurationRow');
|
|
if (type === 'none') {
|
|
durationRow.style.display = 'none';
|
|
} else {
|
|
durationRow.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
// Start slideshow
|
|
function startSlideshow() {
|
|
if (imageLibrary.length === 0) {
|
|
alert('No images available for slideshow. Please add images to your library first.');
|
|
return;
|
|
}
|
|
|
|
console.log('🌀 Starting Hypno Gallery slideshow...');
|
|
|
|
// Prepare image array
|
|
slideshow.images = [...imageLibrary];
|
|
|
|
// Apply image ordering
|
|
switch (currentSettings.imageOrder) {
|
|
case 'random':
|
|
shuffleArray(slideshow.images);
|
|
break;
|
|
case 'reverse':
|
|
slideshow.images.reverse();
|
|
break;
|
|
// 'sequential' needs no change
|
|
}
|
|
|
|
// Initialize slideshow state
|
|
slideshow.currentIndex = 0;
|
|
slideshow.isPlaying = true;
|
|
slideshow.isPaused = false;
|
|
slideshow.startTime = Date.now();
|
|
slideshow.waveTime = 0;
|
|
|
|
// Show slideshow container
|
|
document.querySelector('.main-content').style.display = 'none';
|
|
document.getElementById('slideshowContainer').style.display = 'flex';
|
|
|
|
// Show/hide progress bar
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
if (currentSettings.showProgress) {
|
|
progressContainer.style.display = 'block';
|
|
} else {
|
|
progressContainer.style.display = 'none';
|
|
}
|
|
|
|
// Update info display
|
|
document.getElementById('totalImages').textContent = slideshow.images.length;
|
|
document.getElementById('slideshowStatus').textContent = 'Playing';
|
|
|
|
// Load first image
|
|
loadCurrentImage();
|
|
|
|
// Start timer
|
|
scheduleNextImage();
|
|
}
|
|
|
|
// Load current image
|
|
function loadCurrentImage() {
|
|
if (slideshow.currentIndex >= slideshow.images.length) {
|
|
if (currentSettings.loopPlayback) {
|
|
slideshow.currentIndex = 0;
|
|
} else {
|
|
stopSlideshow();
|
|
return;
|
|
}
|
|
}
|
|
|
|
const currentImage = slideshow.images[slideshow.currentIndex];
|
|
const imgElement = document.getElementById('slideshowImage');
|
|
|
|
console.log(`🖼️ Loading image ${slideshow.currentIndex + 1}: ${currentImage.name}`);
|
|
|
|
// Update info
|
|
document.getElementById('currentImageIndex').textContent = slideshow.currentIndex + 1;
|
|
|
|
// Set image source
|
|
if (currentImage.isWebcamCapture) {
|
|
imgElement.src = currentImage.path; // Base64 data URL
|
|
} else {
|
|
imgElement.src = `file://${currentImage.fullPath}`;
|
|
}
|
|
|
|
// Apply fit mode
|
|
switch (currentSettings.fitMode) {
|
|
case 'contain':
|
|
imgElement.style.objectFit = 'contain';
|
|
break;
|
|
case 'cover':
|
|
imgElement.style.objectFit = 'cover';
|
|
imgElement.style.width = '100vw';
|
|
imgElement.style.height = '100vh';
|
|
imgElement.style.maxWidth = 'none';
|
|
imgElement.style.maxHeight = 'none';
|
|
break;
|
|
case 'stretch':
|
|
imgElement.style.objectFit = 'fill';
|
|
imgElement.style.width = '100vw';
|
|
imgElement.style.height = '100vh';
|
|
imgElement.style.maxWidth = 'none';
|
|
imgElement.style.maxHeight = 'none';
|
|
break;
|
|
}
|
|
|
|
// Apply transition if needed
|
|
if (currentSettings.transitionType === 'fade') {
|
|
imgElement.style.transition = `opacity ${currentSettings.transitionDuration}ms ease-in-out`;
|
|
imgElement.style.opacity = '0';
|
|
|
|
setTimeout(() => {
|
|
imgElement.style.opacity = '1';
|
|
}, 50);
|
|
} else {
|
|
imgElement.style.transition = 'none';
|
|
imgElement.style.opacity = '1';
|
|
}
|
|
}
|
|
|
|
// Calculate next image timing
|
|
function calculateNextTiming() {
|
|
switch (currentSettings.timingMode) {
|
|
case 'constant':
|
|
return currentSettings.duration;
|
|
|
|
case 'random':
|
|
return Math.random() * (currentSettings.maxDuration - currentSettings.minDuration) + currentSettings.minDuration;
|
|
|
|
case 'wave':
|
|
slideshow.waveTime += currentSettings.waveRate / 1000;
|
|
const waveValue = (Math.sin(slideshow.waveTime) + 1) / 2; // 0 to 1
|
|
return waveValue * (currentSettings.waveMax - currentSettings.waveMin) + currentSettings.waveMin;
|
|
|
|
default:
|
|
return currentSettings.duration;
|
|
}
|
|
}
|
|
|
|
// Schedule next image
|
|
function scheduleNextImage() {
|
|
if (!slideshow.isPlaying) return;
|
|
|
|
const nextTiming = calculateNextTiming();
|
|
console.log(`⏰ Next image in ${(nextTiming / 1000).toFixed(1)}s`);
|
|
|
|
// Update progress bar
|
|
if (currentSettings.showProgress) {
|
|
updateProgressBar(nextTiming);
|
|
}
|
|
|
|
// Update countdown timer
|
|
updateCountdownTimer(nextTiming);
|
|
|
|
slideshow.nextTimeout = setTimeout(() => {
|
|
if (slideshow.isPlaying && !slideshow.isPaused) {
|
|
nextImage();
|
|
}
|
|
}, nextTiming);
|
|
}
|
|
|
|
// Update progress bar
|
|
function updateProgressBar(duration) {
|
|
const progressBar = document.getElementById('progressBar');
|
|
progressBar.style.width = '0%';
|
|
progressBar.style.transition = `width ${duration}ms linear`;
|
|
|
|
setTimeout(() => {
|
|
if (slideshow.isPlaying) {
|
|
progressBar.style.width = '100%';
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
// Update countdown timer
|
|
function updateCountdownTimer(duration) {
|
|
const startTime = Date.now();
|
|
|
|
if (slideshow.timer) {
|
|
clearInterval(slideshow.timer);
|
|
}
|
|
|
|
slideshow.timer = setInterval(() => {
|
|
if (!slideshow.isPlaying || slideshow.isPaused) {
|
|
clearInterval(slideshow.timer);
|
|
return;
|
|
}
|
|
|
|
const elapsed = Date.now() - startTime;
|
|
const remaining = Math.max(0, duration - elapsed);
|
|
|
|
document.getElementById('remainingTime').textContent = (remaining / 1000).toFixed(1) + 's';
|
|
|
|
if (remaining <= 0) {
|
|
clearInterval(slideshow.timer);
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// Go to next image
|
|
function nextImage() {
|
|
if (!slideshow.isPlaying) return;
|
|
|
|
slideshow.currentIndex++;
|
|
loadCurrentImage();
|
|
scheduleNextImage();
|
|
}
|
|
|
|
// Go to previous image
|
|
function previousImage() {
|
|
if (!slideshow.isPlaying) return;
|
|
|
|
slideshow.currentIndex--;
|
|
if (slideshow.currentIndex < 0) {
|
|
slideshow.currentIndex = slideshow.images.length - 1;
|
|
}
|
|
|
|
// Cancel current timing
|
|
if (slideshow.nextTimeout) {
|
|
clearTimeout(slideshow.nextTimeout);
|
|
}
|
|
|
|
loadCurrentImage();
|
|
scheduleNextImage();
|
|
}
|
|
|
|
// Pause slideshow
|
|
function pauseSlideshow() {
|
|
if (slideshow.isPaused) {
|
|
// Resume
|
|
slideshow.isPaused = false;
|
|
document.getElementById('pauseBtn').textContent = '⏸️ Pause';
|
|
document.getElementById('slideshowStatus').textContent = 'Playing';
|
|
scheduleNextImage();
|
|
} else {
|
|
// Pause
|
|
slideshow.isPaused = true;
|
|
document.getElementById('pauseBtn').textContent = '▶️ Resume';
|
|
document.getElementById('slideshowStatus').textContent = 'Paused';
|
|
|
|
if (slideshow.nextTimeout) {
|
|
clearTimeout(slideshow.nextTimeout);
|
|
}
|
|
if (slideshow.timer) {
|
|
clearInterval(slideshow.timer);
|
|
}
|
|
|
|
// Stop progress bar
|
|
const progressBar = document.getElementById('progressBar');
|
|
progressBar.style.transition = 'none';
|
|
}
|
|
}
|
|
|
|
// Stop slideshow
|
|
function stopSlideshow() {
|
|
console.log('⏹️ Stopping slideshow...');
|
|
|
|
slideshow.isPlaying = false;
|
|
slideshow.isPaused = false;
|
|
|
|
// Clear timers
|
|
if (slideshow.nextTimeout) {
|
|
clearTimeout(slideshow.nextTimeout);
|
|
}
|
|
if (slideshow.timer) {
|
|
clearInterval(slideshow.timer);
|
|
}
|
|
|
|
// Hide slideshow, show settings
|
|
document.getElementById('slideshowContainer').style.display = 'none';
|
|
document.querySelector('.main-content').style.display = 'block';
|
|
|
|
console.log('✅ Slideshow stopped');
|
|
}
|
|
|
|
// Handle keyboard input
|
|
function handleKeyPress(event) {
|
|
if (!slideshow.isPlaying) return;
|
|
|
|
switch (event.key) {
|
|
case ' ':
|
|
case 'p':
|
|
event.preventDefault();
|
|
pauseSlideshow();
|
|
break;
|
|
case 'ArrowRight':
|
|
case 'n':
|
|
event.preventDefault();
|
|
nextImage();
|
|
break;
|
|
case 'ArrowLeft':
|
|
case 'b':
|
|
event.preventDefault();
|
|
previousImage();
|
|
break;
|
|
case 'Escape':
|
|
case 'q':
|
|
event.preventDefault();
|
|
stopSlideshow();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Utility function to shuffle array
|
|
function shuffleArray(array) {
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
}
|
|
}
|
|
|
|
// Show image gallery (for testing/preview)
|
|
function showImageGallery() {
|
|
if (imageLibrary.length === 0) {
|
|
alert('No images available in the gallery.');
|
|
return;
|
|
}
|
|
|
|
// Create gallery popup
|
|
const galleryHtml = `
|
|
<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: flex-start; overflow-y: auto; padding: 20px;">
|
|
<h2 style="color: white; margin-bottom: 20px;">🖼️ Image Gallery Preview</h2>
|
|
<div style="display: flex; flex-wrap: wrap; gap: 15px; max-width: 90%; justify-content: center;">
|
|
${imageLibrary.slice(0, 50).map((image, index) => `
|
|
<div style="border: 2px solid #667eea; border-radius: 10px; overflow: hidden; width: 200px;">
|
|
<img src="${image.isWebcamCapture ? image.path : 'file://' + image.fullPath}" style="width: 100%; height: 150px; object-fit: cover;" alt="Image ${index + 1}">
|
|
<div style="background: rgba(102, 126, 234, 0.8); color: white; padding: 5px; text-align: center; font-size: 12px;">
|
|
${image.name}
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
${imageLibrary.length > 50 ? `<p style="color: #ccc; margin-top: 20px;">Showing first 50 of ${imageLibrary.length} images</p>` : ''}
|
|
<button onclick="this.parentElement.remove()" style="margin-top: 20px; padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px;">
|
|
❌ Close Gallery
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', galleryHtml);
|
|
}
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('🌀 Hypno Gallery DOM loaded');
|
|
setTimeout(initializeHypnoGallery, 1000);
|
|
});
|
|
|
|
// Initialize timing settings display
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
updateTimingSettings('constant');
|
|
updateTransitionSettings('fade');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |