4021 lines
166 KiB
HTML
4021 lines
166 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Grid Hypno Slideshow</title>
|
||
|
||
<!-- Core Styles -->
|
||
<link rel="stylesheet" href="src/styles/color-variables.css">
|
||
<link rel="stylesheet" href="src/styles/styles.css">
|
||
<link rel="stylesheet" href="src/styles/styles-dark-edgy.css">
|
||
<script src="src/utils/themeManager.js"></script>
|
||
|
||
<style>
|
||
/* Hypno Gallery Specific Styles */
|
||
.hypno-header {
|
||
position: relative;
|
||
text-align: center;
|
||
background: var(--color-gradient);
|
||
padding: 2rem;
|
||
margin: 1rem;
|
||
border-radius: 15px;
|
||
border: 2px solid var(--color-primary);
|
||
box-shadow: 0 8px 32px var(--color-primary-transparent);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
border: 2px solid var(--color-primary);
|
||
border-radius: 15px;
|
||
padding: 2rem;
|
||
margin: 1rem;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.settings-section {
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.settings-section h3 {
|
||
color: var(--color-primary);
|
||
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: var(--color-primary-transparent);
|
||
border-radius: 5px;
|
||
height: 8px;
|
||
outline: none;
|
||
}
|
||
|
||
.setting-control input[type="range"]::-webkit-slider-thumb {
|
||
background: var(--color-primary);
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Grid Layout Styles */
|
||
.grid-mode-selector {
|
||
background: var(--color-primary-transparent);
|
||
border: 2px solid var(--color-primary);
|
||
border-radius: 10px;
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.grid-container {
|
||
width: 100%;
|
||
height: 70vh;
|
||
background: #000;
|
||
border-radius: 10px;
|
||
border: 2px solid var(--color-primary);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.grid-container.single {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.grid-container.grid-2x2 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
grid-template-rows: 1fr 1fr;
|
||
gap: 2px;
|
||
}
|
||
|
||
.grid-container.grid-3x3 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
grid-template-rows: 1fr 1fr 1fr;
|
||
gap: 2px;
|
||
}
|
||
|
||
.grid-cell {
|
||
position: relative;
|
||
background: #111;
|
||
border: 1px solid #333;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.grid-cell-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.grid-cell-image.visible {
|
||
opacity: 1;
|
||
}
|
||
|
||
.grid-cell-info {
|
||
position: absolute;
|
||
top: 5px;
|
||
left: 5px;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
color: #fff;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
font-size: 0.7rem;
|
||
z-index: 10;
|
||
}
|
||
|
||
.grid-cell-controls {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
display: flex;
|
||
gap: 2px;
|
||
z-index: 10;
|
||
}
|
||
|
||
.grid-cell-btn {
|
||
background: var(--color-primary-hover);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 3px;
|
||
padding: 2px 4px;
|
||
cursor: pointer;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.grid-cell-btn:hover {
|
||
background: var(--color-primary);
|
||
}
|
||
|
||
.grid-progress-container {
|
||
display: none !important;
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 10;
|
||
}
|
||
|
||
.grid-progress-bar {
|
||
height: 100%;
|
||
background: var(--color-primary);
|
||
width: 0%;
|
||
transition: width linear;
|
||
}
|
||
|
||
.grid-controls {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-top: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.grid-assignment {
|
||
margin-top: 1rem;
|
||
padding: 1rem;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.cell-assignment {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.cell-assignment label {
|
||
min-width: 80px;
|
||
color: #ccc;
|
||
}
|
||
|
||
.cell-assignment select {
|
||
flex: 1;
|
||
padding: 0.25rem;
|
||
background: #333;
|
||
color: #fff;
|
||
border: 1px solid var(--color-primary);
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* Fullscreen Grid Container */
|
||
.grid-slideshow-container {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background: #000;
|
||
z-index: 1000;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.grid-slideshow-display {
|
||
flex: 1;
|
||
width: 100%;
|
||
height: 100%;
|
||
position: relative;
|
||
}
|
||
|
||
.grid-slideshow-display .grid-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
border: none;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.grid-slideshow-display .grid-cell {
|
||
border: 1px solid #333;
|
||
}
|
||
|
||
.grid-slideshow-info {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
color: #fff;
|
||
padding: 10px 15px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
z-index: 10;
|
||
min-width: 200px;
|
||
opacity: 0.9;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.grid-slideshow-info:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.grid-slideshow-controls {
|
||
position: absolute;
|
||
bottom: 30px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
gap: 15px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
padding: 15px 25px;
|
||
border-radius: 50px;
|
||
z-index: 10;
|
||
opacity: 0.9;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.grid-slideshow-controls:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.grid-slideshow-controls button {
|
||
background: var(--color-primary-hover);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 25px;
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.grid-slideshow-controls button:hover {
|
||
background: var(--color-primary);
|
||
}
|
||
|
||
.grid-slideshow-controls button:disabled {
|
||
background: var(--color-primary-transparent);
|
||
cursor: not-allowed;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
}
|
||
|
||
.setting-control select {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
color: #ffffff;
|
||
border: 1px solid var(--color-primary);
|
||
border-radius: 5px;
|
||
padding: 0.5rem;
|
||
width: 100%;
|
||
}
|
||
|
||
.setting-value {
|
||
color: var(--color-primary);
|
||
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: var(--color-primary);
|
||
}
|
||
|
||
/* 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 {
|
||
position: fixed;
|
||
bottom: 40px;
|
||
right: 40px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||
border-radius: 50%;
|
||
width: 80px;
|
||
height: 80px;
|
||
font-size: 0.75rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow:
|
||
0 10px 30px rgba(102, 126, 234, 0.4),
|
||
0 0 0 0 rgba(102, 126, 234, 0.7),
|
||
inset 0 -3px 10px rgba(0, 0, 0, 0.2);
|
||
z-index: 100;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
line-height: 1.2;
|
||
padding: 0.5rem;
|
||
animation: pulse 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% {
|
||
box-shadow:
|
||
0 10px 30px rgba(102, 126, 234, 0.4),
|
||
0 0 0 0 rgba(102, 126, 234, 0.7),
|
||
inset 0 -3px 10px rgba(0, 0, 0, 0.2);
|
||
}
|
||
50% {
|
||
box-shadow:
|
||
0 10px 30px rgba(102, 126, 234, 0.6),
|
||
0 0 0 10px rgba(102, 126, 234, 0),
|
||
inset 0 -3px 10px rgba(0, 0, 0, 0.2);
|
||
}
|
||
}
|
||
|
||
.start-btn:hover:not(:disabled) {
|
||
background: linear-gradient(135deg, #5a6fd8 0%, #6a42a0 100%);
|
||
box-shadow:
|
||
0 15px 40px rgba(102, 126, 234, 0.6),
|
||
0 0 20px rgba(102, 126, 234, 0.8),
|
||
inset 0 -3px 10px rgba(0, 0, 0, 0.3);
|
||
transform: scale(1.15) translateY(-3px);
|
||
border-color: rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.start-btn:active:not(:disabled) {
|
||
transform: scale(1.05) translateY(0px);
|
||
box-shadow:
|
||
0 5px 20px rgba(102, 126, 234, 0.4),
|
||
inset 0 -2px 8px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.start-btn:disabled {
|
||
background: #6c757d;
|
||
cursor: not-allowed;
|
||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||
animation: 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;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease-in-out;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.slideshow-controls.visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.slideshow-container:hover .slideshow-controls {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.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;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease-in-out;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.slideshow-info.visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.slideshow-container:hover .slideshow-info {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.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;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease-in-out;
|
||
}
|
||
|
||
.progress-container.visible {
|
||
opacity: 1;
|
||
}
|
||
|
||
.slideshow-container:hover .progress-container {
|
||
opacity: 1;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* Header Styles */
|
||
.hypno-page-header {
|
||
background: var(--header-bg);
|
||
border-bottom: 1px solid var(--header-border);
|
||
padding: 8px 0;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
width: 100%;
|
||
z-index: 1000;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.hypno-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
.hypno-nav-left {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.hypno-nav-center {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.hypno-nav-right {
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.hypno-nav h1 {
|
||
color: var(--header-title-color);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 20px;
|
||
margin: 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.header-btn {
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.header-btn-secondary {
|
||
background: var(--btn-secondary-bg);
|
||
color: var(--btn-secondary-text);
|
||
border: 1px solid var(--btn-secondary-border);
|
||
}
|
||
|
||
.header-btn-secondary:hover {
|
||
background: var(--btn-secondary-hover-bg);
|
||
border-color: var(--btn-secondary-hover-border);
|
||
}
|
||
|
||
/* Adjust main content for fixed header */
|
||
.main-content {
|
||
margin-top: 60px;
|
||
}
|
||
|
||
/* 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>
|
||
<!-- Header -->
|
||
<header class="hypno-page-header">
|
||
<div class="hypno-nav">
|
||
<div class="hypno-nav-left">
|
||
<h1>🔲 Grid Slideshow</h1>
|
||
</div>
|
||
<div class="hypno-nav-center">
|
||
</div>
|
||
<div class="hypno-nav-right">
|
||
<div id="theme-switcher-container"></div>
|
||
<a href="index.html" class="header-btn header-btn-secondary" title="Return to main menu">🏠 Home</a>
|
||
<a href="hypno-single.html" class="header-btn header-btn-secondary" title="Switch to single slideshow mode">🌀 Single</a>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Navigation -->
|
||
<div class="navigation" style="display: none;">
|
||
<a href="hypno-menu.html" class="back-btn">← Back to Gallery Menu</a>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="main-content">
|
||
<!-- Header -->
|
||
<div class="hypno-header">
|
||
<div id="theme-switcher-container" style="position: absolute; top: 20px; right: 20px;"></div>
|
||
<h1>🔲 Grid Hypno Slideshow</h1>
|
||
<div class="subtitle">Multiple simultaneous slideshows in synchronized grids</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: var(--color-primary); color: white; border: none; border-radius: 5px; cursor: pointer; display: none;">
|
||
🖼️ View Image Library
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Grid Mode Selection -->
|
||
<div class="settings-panel">
|
||
<div class="settings-section">
|
||
<h3>🔲 Display Mode</h3>
|
||
<div class="setting-group">
|
||
<div class="setting-row">
|
||
<span class="setting-label">Layout Mode:</span>
|
||
<div class="setting-control">
|
||
<select id="displayMode" onchange="updateDisplayMode()">
|
||
<option value="grid-2x2">2x2 Grid</option>
|
||
<option value="grid-3x3">3x3 Grid</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Grid View Container (shown when layout selected) -->
|
||
<div id="gridView" style="display: none;">
|
||
<div class="grid-container single" id="gridContainer"></div>
|
||
</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>
|
||
|
||
|
||
|
||
<!-- 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>
|
||
</div>
|
||
|
||
<!-- Background Settings -->
|
||
<div class="settings-section">
|
||
<h3>🎨 Background Settings</h3>
|
||
<div class="setting-group">
|
||
<div class="setting-row">
|
||
<span class="setting-label">Background Type:</span>
|
||
<div class="setting-control">
|
||
<select id="backgroundType" style="width: 100%; padding: 0.5rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 5px; color: white;">
|
||
<option value="solid">Solid Color</option>
|
||
<option value="gradient">Gradient</option>
|
||
<option value="blurred">Blurred Image</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Solid Color Options -->
|
||
<div id="solidColorOptions" class="setting-row">
|
||
<span class="setting-label">Background Color:</span>
|
||
<div class="setting-control" style="display: flex; gap: 0.5rem; align-items: center;">
|
||
<input type="color" id="backgroundColor" value="#000000" style="width: 50px; height: 35px; border: 1px solid #667eea; border-radius: 5px; background: none;">
|
||
<input type="text" id="backgroundColorHex" value="#000000" placeholder="#000000" style="flex: 1; padding: 0.5rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 5px; color: white;">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Gradient Options -->
|
||
<div id="gradientOptions" class="setting-row" style="display: none;">
|
||
<span class="setting-label">Gradient Colors:</span>
|
||
<div class="setting-control">
|
||
<div style="display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem;">
|
||
<label style="color: #ccc; min-width: 60px;">From:</label>
|
||
<input type="color" id="gradientColor1" value="#000000" style="width: 50px; height: 35px; border: 1px solid #667eea; border-radius: 5px;">
|
||
<input type="text" id="gradientColor1Hex" value="#000000" style="flex: 1; padding: 0.5rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 5px; color: white;">
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem;">
|
||
<label style="color: #ccc; min-width: 60px;">To:</label>
|
||
<input type="color" id="gradientColor2" value="#1a1a1a" style="width: 50px; height: 35px; border: 1px solid #667eea; border-radius: 5px;">
|
||
<input type="text" id="gradientColor2Hex" value="#1a1a1a" style="flex: 1; padding: 0.5rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 5px; color: white;">
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||
<label style="color: #ccc; min-width: 60px;">Direction:</label>
|
||
<select id="gradientDirection" style="flex: 1; padding: 0.5rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 5px; color: white;">
|
||
<option value="to bottom">Top to Bottom</option>
|
||
<option value="to right">Left to Right</option>
|
||
<option value="to bottom right">Top-Left to Bottom-Right</option>
|
||
<option value="to bottom left">Top-Right to Bottom-Left</option>
|
||
<option value="45deg">Diagonal (45°)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Blurred Image Options -->
|
||
<div id="blurredOptions" class="setting-row" style="display: none;">
|
||
<span class="setting-label">Blur Settings:</span>
|
||
<div class="setting-control">
|
||
<div style="display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem;">
|
||
<label style="color: #ccc; min-width: 80px;">Blur Amount:</label>
|
||
<input type="range" id="blurAmount" min="5" max="50" value="20" style="flex: 1;">
|
||
<span id="blurAmountValue" style="color: #667eea; min-width: 40px; text-align: right;">20px</span>
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||
<label style="color: #ccc; min-width: 80px;">Opacity:</label>
|
||
<input type="range" id="blurOpacity" min="10" max="100" value="70" style="flex: 1;">
|
||
<span id="blurOpacityValue" style="color: #667eea; min-width: 40px; text-align: right;">70%</span>
|
||
</div>
|
||
</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>
|
||
|
||
<!-- Floating Start Button -->
|
||
<button class="start-btn" id="startSlideshowBtn" onclick="startGridSlideshow()" title="Start Grid Slideshow">
|
||
Start<br>Slideshow
|
||
</button>
|
||
</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>
|
||
|
||
<!-- Grid Slideshow Container (Fullscreen) -->
|
||
<div class="grid-slideshow-container" id="gridSlideshowContainer">
|
||
<div class="grid-slideshow-display" id="gridSlideshowDisplay"></div>
|
||
|
||
<div class="grid-slideshow-info" id="gridSlideshowInfo">
|
||
<div>Grid Mode: <span id="gridModeDisplay">2x2</span></div>
|
||
<div>Active Cells: <span id="activeCells">0</span></div>
|
||
<div>Status: <span id="gridSlideshowStatus">Ready</span></div>
|
||
</div>
|
||
|
||
<div class="grid-slideshow-controls">
|
||
<button onclick="pauseGridSlideshow()" id="gridPauseBtn">⏸️ Pause All</button>
|
||
<button onclick="nextGridAll()" id="gridNextBtn">⏭️ Next All</button>
|
||
<button onclick="stopGridSlideshow()" id="gridStopBtn">⏹️ Stop Grid</button>
|
||
</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,
|
||
backgroundType: 'solid',
|
||
backgroundColor: '#000000',
|
||
gradientColor1: '#000000',
|
||
gradientColor2: '#1a1a1a',
|
||
gradientDirection: 'to bottom',
|
||
blurAmount: 20,
|
||
blurOpacity: 70
|
||
};
|
||
|
||
// Slideshow Management Variables
|
||
let savedSlideshows = {};
|
||
let currentSlideshowName = null;
|
||
|
||
let slideshow = {
|
||
images: [],
|
||
currentIndex: 0,
|
||
isPlaying: false,
|
||
isPaused: false,
|
||
timer: null,
|
||
nextTimeout: null,
|
||
startTime: 0,
|
||
waveTime: 0
|
||
};
|
||
|
||
// Grid Slideshow Variables
|
||
let gridSlideshow = {
|
||
mode: 'single',
|
||
cells: [],
|
||
isPlaying: false,
|
||
isPaused: false,
|
||
configurations: {}
|
||
};
|
||
|
||
// GridCell Class
|
||
class GridCell {
|
||
constructor(index) {
|
||
this.index = index;
|
||
this.slideshow = null;
|
||
this.images = [];
|
||
this.currentIndex = 0;
|
||
this.timer = null;
|
||
this.element = null;
|
||
this.imageElement = null;
|
||
this.infoElement = null;
|
||
this.progressElement = null;
|
||
this.isPlaying = false;
|
||
// Copy current settings safely
|
||
this.settings = {
|
||
timingMode: currentSettings.timingMode,
|
||
duration: currentSettings.duration,
|
||
minDuration: currentSettings.minDuration,
|
||
maxDuration: currentSettings.maxDuration,
|
||
waveMin: currentSettings.waveMin,
|
||
waveMax: currentSettings.waveMax,
|
||
waveRate: currentSettings.waveRate,
|
||
transitionDuration: currentSettings.transitionDuration
|
||
};
|
||
console.log(`GridCell ${index} initialized with settings:`, this.settings);
|
||
}
|
||
|
||
initializeElement() {
|
||
this.element = document.createElement('div');
|
||
this.element.className = 'grid-cell';
|
||
const cellIndex = this.index;
|
||
this.element.innerHTML = `
|
||
<img class="grid-cell-image" alt="Grid Cell Image">
|
||
<div class="grid-cell-info">
|
||
<div>Cell ${cellIndex + 1}: <span class="cell-slideshow">Click to assign</span></div>
|
||
<div>Image: <span class="cell-current">0</span> / <span class="cell-total">0</span></div>
|
||
</div>
|
||
<div class="grid-cell-controls">
|
||
<button class="grid-cell-btn" onclick="pauseGridCell(${cellIndex})">⏸️</button>
|
||
<button class="grid-cell-btn" onclick="nextGridCell(${cellIndex})">⏭️</button>
|
||
</div>
|
||
<div class="grid-progress-container">
|
||
<div class="grid-progress-bar"></div>
|
||
</div>
|
||
`;
|
||
|
||
// Add click handler to assign slideshow
|
||
this.element.style.cursor = 'pointer';
|
||
this.element.addEventListener('click', (e) => {
|
||
// Don't trigger if clicking controls
|
||
if (!e.target.closest('.grid-cell-controls')) {
|
||
showGridCellSlideshowPicker(cellIndex);
|
||
}
|
||
});
|
||
|
||
this.imageElement = this.element.querySelector('.grid-cell-image');
|
||
this.infoElement = this.element.querySelector('.grid-cell-info');
|
||
|
||
return this.element;
|
||
}
|
||
|
||
async loadSlideshow(slideshow) {
|
||
console.log(`Cell ${this.index}: Loading slideshow:`, slideshow ? slideshow.name : 'null');
|
||
this.slideshow = slideshow;
|
||
|
||
if (slideshow) {
|
||
// Load images for this specific slideshow
|
||
this.images = await getGridCellImages(slideshow);
|
||
|
||
// Shuffle images for this cell to ensure randomness
|
||
this.images = this.shuffleArray([...this.images]);
|
||
|
||
// Start at a random position
|
||
this.currentIndex = this.images.length > 0 ? Math.floor(Math.random() * this.images.length) : 0;
|
||
} else {
|
||
this.images = [];
|
||
this.currentIndex = 0;
|
||
}
|
||
|
||
console.log(`Cell ${this.index}: Loaded ${this.images.length} images, starting at index ${this.currentIndex}`);
|
||
this.updateInfo();
|
||
this.displayCurrentImage();
|
||
}
|
||
|
||
shuffleArray(array) {
|
||
// Fisher-Yates shuffle for randomizing images
|
||
const shuffled = [...array];
|
||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||
}
|
||
return shuffled;
|
||
}
|
||
|
||
updateInfo() {
|
||
if (this.infoElement) {
|
||
const slideshowSpan = this.infoElement.querySelector('.cell-slideshow');
|
||
const currentSpan = this.infoElement.querySelector('.cell-current');
|
||
const totalSpan = this.infoElement.querySelector('.cell-total');
|
||
if (slideshowSpan) slideshowSpan.textContent = this.slideshow ? this.slideshow.name : 'Empty';
|
||
if (currentSpan) currentSpan.textContent = this.images.length > 0 ? this.currentIndex + 1 : 0;
|
||
if (totalSpan) totalSpan.textContent = this.images.length;
|
||
}
|
||
}
|
||
|
||
displayCurrentImage() {
|
||
if (this.images.length === 0 || !this.imageElement) return;
|
||
|
||
const imageData = this.images[this.currentIndex];
|
||
console.log(`Cell ${this.index}: Image data structure:`, imageData);
|
||
|
||
let imageSrc;
|
||
if (typeof imageData === 'string') {
|
||
imageSrc = imageData;
|
||
} else {
|
||
// Try different possible path properties in order of preference
|
||
imageSrc = imageData.cachedPath || imageData.fullPath || imageData.path || imageData.src || imageData.dataURL;
|
||
}
|
||
|
||
console.log(`Cell ${this.index}: Using image src: ${imageSrc}`);
|
||
|
||
if (!imageSrc) {
|
||
console.error(`Cell ${this.index}: No valid image source found in:`, imageData);
|
||
return;
|
||
}
|
||
|
||
this.imageElement.style.opacity = '0';
|
||
setTimeout(() => {
|
||
this.imageElement.src = imageSrc;
|
||
this.imageElement.style.opacity = '1';
|
||
this.imageElement.classList.add('visible');
|
||
}, this.settings.transitionDuration / 2);
|
||
}
|
||
|
||
nextImage() {
|
||
if (this.images.length === 0) return;
|
||
|
||
this.currentIndex = (this.currentIndex + 1) % this.images.length;
|
||
this.updateInfo();
|
||
this.displayCurrentImage();
|
||
}
|
||
|
||
startTimer() {
|
||
console.log(`Cell ${this.index}: Starting timer`);
|
||
if (this.timer) clearTimeout(this.timer);
|
||
|
||
const duration = this.calculateDuration();
|
||
console.log(`Cell ${this.index}: Duration = ${duration}ms`);
|
||
this.updateProgress(duration);
|
||
|
||
this.timer = setTimeout(() => {
|
||
console.log(`Cell ${this.index}: Timer fired, advancing image`);
|
||
this.nextImage();
|
||
if (this.isPlaying) {
|
||
this.startTimer();
|
||
}
|
||
}, duration);
|
||
}
|
||
|
||
calculateDuration() {
|
||
switch (this.settings.timingMode) {
|
||
case 'random':
|
||
return Math.random() * (this.settings.maxDuration - this.settings.minDuration) + this.settings.minDuration;
|
||
case 'wave':
|
||
const wavePosition = Date.now() / 1000 / this.settings.waveRate;
|
||
const waveValue = (Math.sin(wavePosition) + 1) / 2;
|
||
return this.settings.waveMin + (this.settings.waveMax - this.settings.waveMin) * waveValue;
|
||
default:
|
||
return this.settings.duration;
|
||
}
|
||
}
|
||
|
||
updateProgress(duration) {
|
||
if (!this.progressElement) return;
|
||
|
||
this.progressElement.style.transition = 'none';
|
||
this.progressElement.style.width = '0%';
|
||
|
||
setTimeout(() => {
|
||
this.progressElement.style.transition = `width ${duration}ms linear`;
|
||
this.progressElement.style.width = '100%';
|
||
}, 50);
|
||
}
|
||
|
||
pause() {
|
||
this.isPlaying = false;
|
||
if (this.timer) {
|
||
clearTimeout(this.timer);
|
||
this.timer = null;
|
||
}
|
||
}
|
||
|
||
resume() {
|
||
this.isPlaying = true;
|
||
this.startTimer();
|
||
}
|
||
|
||
stop() {
|
||
this.isPlaying = false;
|
||
if (this.timer) {
|
||
clearTimeout(this.timer);
|
||
this.timer = null;
|
||
}
|
||
this.currentIndex = 0;
|
||
this.updateInfo();
|
||
if (this.progressElement) {
|
||
this.progressElement.style.width = '0%';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Grid Management Functions
|
||
function updateDisplayMode() {
|
||
const mode = document.getElementById('displayMode').value;
|
||
const gridView = document.getElementById('gridView');
|
||
|
||
if (mode === 'single') {
|
||
gridView.style.display = 'none';
|
||
stopGridSlideshow();
|
||
} else {
|
||
gridView.style.display = 'block';
|
||
setupGridLayout(mode);
|
||
}
|
||
|
||
gridSlideshow.mode = mode;
|
||
}
|
||
|
||
function setupGridLayout(mode) {
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
|
||
// Clear existing grid
|
||
gridContainer.innerHTML = '';
|
||
gridSlideshow.cells = [];
|
||
|
||
// Set grid class
|
||
gridContainer.className = `grid-container ${mode}`;
|
||
|
||
// Create cells based on mode
|
||
let cellCount = 1;
|
||
switch (mode) {
|
||
case 'grid-2x2':
|
||
cellCount = 4;
|
||
break;
|
||
case 'grid-3x3':
|
||
cellCount = 9;
|
||
break;
|
||
default:
|
||
cellCount = 1;
|
||
}
|
||
|
||
// Create grid cells
|
||
for (let i = 0; i < cellCount; i++) {
|
||
const cell = new GridCell(i);
|
||
gridSlideshow.cells.push(cell);
|
||
|
||
const cellElement = cell.initializeElement();
|
||
gridContainer.appendChild(cellElement);
|
||
}
|
||
|
||
updateCellAssignments();
|
||
updateGridControls();
|
||
|
||
// Apply background settings to the grid container
|
||
setTimeout(() => applyBackground(), 50);
|
||
}
|
||
|
||
function showGridCellSlideshowPicker(cellIndex) {
|
||
const cell = gridSlideshow.cells[cellIndex];
|
||
if (!cell) return;
|
||
|
||
// Create modal overlay
|
||
const overlay = document.createElement('div');
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 10000;
|
||
`;
|
||
|
||
// Create modal content
|
||
const modal = document.createElement('div');
|
||
modal.style.cssText = `
|
||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||
border: 2px solid #667eea;
|
||
border-radius: 15px;
|
||
padding: 2rem;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
|
||
`;
|
||
|
||
modal.innerHTML = `
|
||
<h3 style="color: #667eea; margin: 0 0 1.5rem 0;">📋 Select Slideshow for Cell ${cellIndex + 1}</h3>
|
||
<div id="slideshowOptions" style="max-height: 400px; overflow-y: auto;">
|
||
${Object.keys(savedSlideshows).map(name => `
|
||
<div style="
|
||
background: rgba(102, 126, 234, 0.1);
|
||
border: 1px solid #667eea;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
margin-bottom: 0.5rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
" onmouseover="this.style.background='rgba(102, 126, 234, 0.2)'"
|
||
onmouseout="this.style.background='rgba(102, 126, 234, 0.1)'"
|
||
onclick="assignSlideshowToCell(${cellIndex}, '${name}')">
|
||
<div style="color: white; font-weight: bold; margin-bottom: 0.25rem;">${name}</div>
|
||
<div style="color: #aaa; font-size: 0.9rem;">${savedSlideshows[name].images?.length || 0} images</div>
|
||
</div>
|
||
`).join('')}
|
||
<div style="
|
||
background: rgba(255, 100, 100, 0.1);
|
||
border: 1px solid #ff6464;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
margin-bottom: 0.5rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
" onmouseover="this.style.background='rgba(255, 100, 100, 0.2)'"
|
||
onmouseout="this.style.background='rgba(255, 100, 100, 0.1)'"
|
||
onclick="assignSlideshowToCell(${cellIndex}, null)">
|
||
<div style="color: white; font-weight: bold;">Clear Assignment</div>
|
||
</div>
|
||
</div>
|
||
<button onclick="this.closest('.modal-overlay').remove()"
|
||
style="
|
||
margin-top: 1rem;
|
||
width: 100%;
|
||
padding: 0.75rem;
|
||
background: #667eea;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1rem;
|
||
transition: all 0.3s ease;
|
||
"
|
||
onmouseover="this.style.background='#5568d3'"
|
||
onmouseout="this.style.background='#667eea'">Close</button>
|
||
`;
|
||
|
||
overlay.className = 'modal-overlay';
|
||
overlay.appendChild(modal);
|
||
overlay.onclick = (e) => {
|
||
if (e.target === overlay) overlay.remove();
|
||
};
|
||
|
||
document.body.appendChild(overlay);
|
||
}
|
||
|
||
async function assignSlideshowToCell(cellIndex, slideshowName) {
|
||
const cell = gridSlideshow.cells[cellIndex];
|
||
if (!cell) return;
|
||
|
||
if (slideshowName) {
|
||
await cell.loadSlideshow(savedSlideshows[slideshowName]);
|
||
console.log(`✅ Assigned "${slideshowName}" to cell ${cellIndex}`);
|
||
} else {
|
||
await cell.loadSlideshow(null);
|
||
console.log(`🗑️ Cleared slideshow from cell ${cellIndex}`);
|
||
}
|
||
|
||
// Close modal
|
||
document.querySelector('.modal-overlay')?.remove();
|
||
}
|
||
|
||
function updateCellAssignments() {
|
||
// Legacy function - no longer needed but kept for compatibility
|
||
return;
|
||
}
|
||
|
||
async function startGridSlideshow() {
|
||
console.log('🟢 Starting grid slideshow...');
|
||
console.log('Grid cells:', gridSlideshow.cells.length);
|
||
|
||
if (gridSlideshow.cells.length === 0) {
|
||
alert('No grid cells configured');
|
||
return;
|
||
}
|
||
|
||
// Enter fullscreen grid mode
|
||
enterGridFullscreen();
|
||
|
||
gridSlideshow.isPlaying = true;
|
||
gridSlideshow.isPaused = false;
|
||
|
||
let activeCellCount = 0;
|
||
gridSlideshow.cells.forEach((cell, index) => {
|
||
console.log(`Cell ${index}: ${cell.images.length} images`);
|
||
if (cell.images.length > 0) {
|
||
console.log(`Starting cell ${index}`);
|
||
cell.isPlaying = true;
|
||
cell.startTimer();
|
||
activeCellCount++;
|
||
} else {
|
||
console.log(`Cell ${index} has no images, skipping`);
|
||
}
|
||
});
|
||
|
||
// Update fullscreen info
|
||
document.getElementById('gridModeDisplay').textContent = gridSlideshow.mode;
|
||
document.getElementById('activeCells').textContent = activeCellCount;
|
||
document.getElementById('gridSlideshowStatus').textContent = 'Playing';
|
||
|
||
updateGridControls();
|
||
console.log('✅ Grid slideshow start complete');
|
||
}
|
||
|
||
function pauseGridSlideshow() {
|
||
if (gridSlideshow.isPaused) {
|
||
resumeGridSlideshow();
|
||
return;
|
||
}
|
||
|
||
gridSlideshow.isPaused = true;
|
||
gridSlideshow.cells.forEach(cell => {
|
||
cell.pause();
|
||
});
|
||
|
||
updateGridControls();
|
||
}
|
||
|
||
function resumeGridSlideshow() {
|
||
gridSlideshow.isPaused = false;
|
||
gridSlideshow.cells.forEach(cell => {
|
||
if (cell.images.length > 0) {
|
||
cell.resume();
|
||
}
|
||
});
|
||
|
||
updateGridControls();
|
||
}
|
||
|
||
function stopGridSlideshow() {
|
||
gridSlideshow.isPlaying = false;
|
||
gridSlideshow.isPaused = false;
|
||
gridSlideshow.cells.forEach(cell => {
|
||
cell.stop();
|
||
});
|
||
|
||
// Exit fullscreen mode
|
||
exitGridFullscreen();
|
||
|
||
// Update status
|
||
const gridStatus = document.getElementById('gridSlideshowStatus');
|
||
if (gridStatus) {
|
||
gridStatus.textContent = 'Stopped';
|
||
}
|
||
|
||
updateGridControls();
|
||
console.log('🔲 Grid slideshow stopped');
|
||
}
|
||
|
||
// Fullscreen grid control functions
|
||
function pauseGridSlideshow() {
|
||
if (!gridSlideshow.isPlaying) return;
|
||
|
||
gridSlideshow.isPaused = !gridSlideshow.isPaused;
|
||
|
||
gridSlideshow.cells.forEach(cell => {
|
||
if (gridSlideshow.isPaused) {
|
||
cell.pause();
|
||
} else {
|
||
cell.resume();
|
||
}
|
||
});
|
||
|
||
// Update status
|
||
const gridStatus = document.getElementById('gridSlideshowStatus');
|
||
if (gridStatus) {
|
||
gridStatus.textContent = gridSlideshow.isPaused ? 'Paused' : 'Playing';
|
||
}
|
||
|
||
updateGridControls();
|
||
console.log('⏸️ Grid slideshow', gridSlideshow.isPaused ? 'paused' : 'resumed');
|
||
}
|
||
|
||
function nextGridAll() {
|
||
if (!gridSlideshow.isPlaying) return;
|
||
|
||
gridSlideshow.cells.forEach(cell => {
|
||
if (cell.images.length > 0) {
|
||
cell.next();
|
||
}
|
||
});
|
||
|
||
console.log('⏭️ All grid cells advanced to next image');
|
||
}
|
||
|
||
function nextGridAll() {
|
||
gridSlideshow.cells.forEach(cell => {
|
||
if (cell.images.length > 0) {
|
||
cell.nextImage();
|
||
}
|
||
});
|
||
}
|
||
|
||
function pauseGridCell(cellIndex) {
|
||
const cell = gridSlideshow.cells[cellIndex];
|
||
if (cell) {
|
||
if (cell.isPlaying) {
|
||
cell.pause();
|
||
} else {
|
||
cell.resume();
|
||
}
|
||
}
|
||
}
|
||
|
||
function nextGridCell(cellIndex) {
|
||
const cell = gridSlideshow.cells[cellIndex];
|
||
if (cell && cell.images.length > 0) {
|
||
cell.nextImage();
|
||
}
|
||
}
|
||
|
||
function updateGridControls() {
|
||
const startBtn = document.getElementById('startGridBtn');
|
||
const pauseBtn = document.getElementById('pauseGridBtn');
|
||
const stopBtn = document.getElementById('stopGridBtn');
|
||
|
||
if (startBtn && pauseBtn && stopBtn) {
|
||
if (gridSlideshow.isPlaying) {
|
||
startBtn.disabled = true;
|
||
pauseBtn.disabled = false;
|
||
pauseBtn.textContent = gridSlideshow.isPaused ? '▶️ Resume Grid' : '⏸️ Pause Grid';
|
||
stopBtn.disabled = false;
|
||
} else {
|
||
startBtn.disabled = false;
|
||
pauseBtn.disabled = true;
|
||
pauseBtn.textContent = '⏸️ Pause Grid';
|
||
stopBtn.disabled = true;
|
||
}
|
||
}
|
||
|
||
// Update fullscreen controls if visible
|
||
const gridPauseBtn = document.getElementById('gridPauseBtn');
|
||
const gridStopBtn = document.getElementById('gridStopBtn');
|
||
|
||
if (gridPauseBtn && gridStopBtn) {
|
||
if (gridSlideshow.isPlaying) {
|
||
gridPauseBtn.disabled = false;
|
||
gridPauseBtn.textContent = gridSlideshow.isPaused ? '▶️ Resume All' : '⏸️ Pause All';
|
||
gridStopBtn.disabled = false;
|
||
} else {
|
||
gridPauseBtn.disabled = true;
|
||
gridPauseBtn.textContent = '⏸️ Pause All';
|
||
gridStopBtn.disabled = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
function enterGridFullscreen() {
|
||
console.log('🔲 Entering grid fullscreen mode');
|
||
|
||
// Hide main content
|
||
document.querySelector('.main-content').style.display = 'none';
|
||
|
||
// Show grid fullscreen container
|
||
document.getElementById('gridSlideshowContainer').style.display = 'flex';
|
||
|
||
// Move grid container to fullscreen display
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
const fullscreenDisplay = document.getElementById('gridSlideshowDisplay');
|
||
|
||
if (gridContainer && fullscreenDisplay) {
|
||
fullscreenDisplay.appendChild(gridContainer);
|
||
}
|
||
}
|
||
|
||
function exitGridFullscreen() {
|
||
console.log('🔲 Exiting grid fullscreen mode');
|
||
|
||
// Hide grid fullscreen container
|
||
document.getElementById('gridSlideshowContainer').style.display = 'none';
|
||
|
||
// Show main content
|
||
document.querySelector('.main-content').style.display = 'block';
|
||
|
||
// Move grid container back to settings
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
const gridView = document.getElementById('gridView');
|
||
|
||
if (gridContainer && gridView) {
|
||
// Find the original position (after the controls)
|
||
const gridControls = gridView.querySelector('.grid-controls');
|
||
if (gridControls && gridControls.nextElementSibling) {
|
||
gridView.insertBefore(gridContainer, gridControls.nextElementSibling);
|
||
} else {
|
||
gridView.appendChild(gridContainer);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
console.log('💾 Loaded directory preferences:', currentSettings.selectedDirectories);
|
||
|
||
// Populate directory list
|
||
// populateDirectoryList(linkedDirectories); // UI element removed
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing directory selection:', error);
|
||
}
|
||
}
|
||
|
||
// Populate Directory List
|
||
function populateDirectoryList(directories) {
|
||
const directoriesList = document.getElementById('linkedDirectoriesList');
|
||
if (!directoriesList) {
|
||
console.log('📁 Directory list UI element not found (removed), skipping population');
|
||
return;
|
||
}
|
||
|
||
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('transitionType').addEventListener('change', (e) => {
|
||
currentSettings.transitionType = e.target.value;
|
||
updateTransitionSettings(e.target.value);
|
||
});
|
||
|
||
// Include captured photos checkbox (removed from UI)
|
||
const capturedPhotosCheckbox = document.getElementById('includeCapturedPhotos');
|
||
if (capturedPhotosCheckbox) {
|
||
capturedPhotosCheckbox.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 (removed from UI)
|
||
const refreshButton = document.getElementById('refreshDirectories');
|
||
if (refreshButton) {
|
||
refreshButton.addEventListener('click', async () => {
|
||
console.log('🔄 Refreshing directories...');
|
||
await initializeDirectorySelection();
|
||
await initializeImageLibrary();
|
||
});
|
||
}
|
||
|
||
// Background settings
|
||
document.getElementById('backgroundType').addEventListener('change', (e) => {
|
||
currentSettings.backgroundType = e.target.value;
|
||
updateBackgroundOptions(e.target.value);
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
});
|
||
|
||
// Color pickers and hex inputs
|
||
setupColorControl('backgroundColor');
|
||
setupColorControl('gradientColor1');
|
||
setupColorControl('gradientColor2');
|
||
|
||
document.getElementById('gradientDirection').addEventListener('change', (e) => {
|
||
currentSettings.gradientDirection = e.target.value;
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
});
|
||
|
||
// Blur controls
|
||
document.getElementById('blurAmount').addEventListener('input', (e) => {
|
||
const value = parseInt(e.target.value);
|
||
currentSettings.blurAmount = value;
|
||
document.getElementById('blurAmountValue').textContent = value + 'px';
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
});
|
||
|
||
document.getElementById('blurOpacity').addEventListener('input', (e) => {
|
||
const value = parseInt(e.target.value);
|
||
currentSettings.blurOpacity = value;
|
||
document.getElementById('blurOpacityValue').textContent = value + '%';
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
});
|
||
|
||
// Keyboard controls
|
||
document.addEventListener('keydown', handleKeyPress);
|
||
|
||
// Mouse movement controls for auto-hiding UI
|
||
setupMouseControls();
|
||
}
|
||
|
||
// 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
|
||
async function startSlideshow() {
|
||
console.log('🌀 Starting Hypno Gallery slideshow...');
|
||
|
||
// Get slideshow-specific images
|
||
const slideshowImages = await getSlideshowImages();
|
||
|
||
if (slideshowImages.length === 0) {
|
||
alert('No images available for slideshow. Please configure slideshow directories or add images to your library first.');
|
||
return;
|
||
}
|
||
|
||
// Prepare image array
|
||
slideshow.images = [...slideshowImages];
|
||
|
||
// 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}`;
|
||
}
|
||
|
||
// Update background for blurred mode
|
||
if (currentSettings.backgroundType === 'blurred') {
|
||
// Wait for image to load before updating background
|
||
imgElement.onload = () => {
|
||
setTimeout(() => applyBackground(), 100); // Small delay to ensure image is rendered
|
||
};
|
||
}
|
||
|
||
// 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;
|
||
|
||
// Cancel current timing to restart timer
|
||
if (slideshow.nextTimeout) {
|
||
clearTimeout(slideshow.nextTimeout);
|
||
}
|
||
|
||
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) {
|
||
// Allow keyboard controls even when slideshow is paused
|
||
if (!slideshow.isPlaying && !slideshow.isPaused) return;
|
||
|
||
switch (event.key) {
|
||
case ' ':
|
||
case 'p':
|
||
case 'k': // Video player convention
|
||
event.preventDefault();
|
||
pauseSlideshow();
|
||
break;
|
||
case 'ArrowRight':
|
||
case 'n':
|
||
case 'l': // Right arrow alternative
|
||
case 'j': // Vim-like forward
|
||
event.preventDefault();
|
||
nextImage();
|
||
break;
|
||
case 'ArrowLeft':
|
||
case 'b':
|
||
case 'h': // Left arrow alternative
|
||
case 'k': // Vim-like backward (if not pausing)
|
||
event.preventDefault();
|
||
if (event.key === 'k' && slideshow.isPlaying) {
|
||
pauseSlideshow(); // k pauses when playing
|
||
} else {
|
||
previousImage();
|
||
}
|
||
break;
|
||
case 'Escape':
|
||
case 'q':
|
||
case 'x':
|
||
event.preventDefault();
|
||
stopSlideshow();
|
||
break;
|
||
case 'f':
|
||
case 'F11':
|
||
event.preventDefault();
|
||
toggleFullscreen();
|
||
break;
|
||
case 'r':
|
||
event.preventDefault();
|
||
// Restart slideshow from beginning
|
||
slideshow.currentIndex = 0;
|
||
displayCurrentImage();
|
||
break;
|
||
case '?':
|
||
case 'h':
|
||
event.preventDefault();
|
||
showKeyboardHelp();
|
||
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]];
|
||
}
|
||
}
|
||
|
||
// Mouse control setup for auto-hiding UI
|
||
let mouseTimeout;
|
||
let controlsVisible = false;
|
||
|
||
function setupMouseControls() {
|
||
const slideshowContainer = document.getElementById('slideshowContainer');
|
||
if (!slideshowContainer) return;
|
||
|
||
// Show controls on mouse movement
|
||
slideshowContainer.addEventListener('mousemove', () => {
|
||
if (!slideshow.isPlaying && !slideshow.isPaused) return;
|
||
|
||
showControls();
|
||
|
||
// Clear existing timeout
|
||
if (mouseTimeout) {
|
||
clearTimeout(mouseTimeout);
|
||
}
|
||
|
||
// Hide controls after 3 seconds of no movement
|
||
mouseTimeout = setTimeout(() => {
|
||
hideControls();
|
||
}, 3000);
|
||
});
|
||
|
||
// Keep controls visible when hovering over them
|
||
const controls = slideshowContainer.querySelector('.slideshow-controls');
|
||
const info = slideshowContainer.querySelector('.slideshow-info');
|
||
|
||
[controls, info].forEach(element => {
|
||
if (element) {
|
||
element.addEventListener('mouseenter', () => {
|
||
if (mouseTimeout) {
|
||
clearTimeout(mouseTimeout);
|
||
}
|
||
showControls();
|
||
});
|
||
|
||
element.addEventListener('mouseleave', () => {
|
||
mouseTimeout = setTimeout(() => {
|
||
hideControls();
|
||
}, 1000);
|
||
});
|
||
}
|
||
});
|
||
|
||
// Controls start hidden and only show on mouse hover
|
||
// No initial hideControls() call needed as CSS handles this
|
||
}
|
||
|
||
function showControls() {
|
||
controlsVisible = true;
|
||
const slideshowContainer = document.getElementById('slideshowContainer');
|
||
if (!slideshowContainer) return;
|
||
|
||
const controls = slideshowContainer.querySelector('.slideshow-controls');
|
||
const info = slideshowContainer.querySelector('.slideshow-info');
|
||
const progress = slideshowContainer.querySelector('.progress-container');
|
||
|
||
if (controls) controls.classList.add('visible');
|
||
if (info) info.classList.add('visible');
|
||
if (progress && progress.style.display !== 'none') progress.classList.add('visible');
|
||
}
|
||
|
||
function hideControls() {
|
||
controlsVisible = false;
|
||
const slideshowContainer = document.getElementById('slideshowContainer');
|
||
if (!slideshowContainer) return;
|
||
|
||
const controls = slideshowContainer.querySelector('.slideshow-controls');
|
||
const info = slideshowContainer.querySelector('.slideshow-info');
|
||
const progress = slideshowContainer.querySelector('.progress-container');
|
||
|
||
if (controls) controls.classList.remove('visible');
|
||
if (info) info.classList.remove('visible');
|
||
if (progress) progress.classList.remove('visible');
|
||
}
|
||
|
||
function showControlsTemporarily() {
|
||
// Keyboard controls no longer show UI - only mouse hover does
|
||
// This function kept for potential future use
|
||
return;
|
||
}
|
||
|
||
function toggleFullscreen() {
|
||
if (!document.fullscreenElement) {
|
||
document.documentElement.requestFullscreen().catch(err => {
|
||
console.log('Error attempting to enable fullscreen:', err);
|
||
});
|
||
} else {
|
||
document.exitFullscreen().catch(err => {
|
||
console.log('Error attempting to exit fullscreen:', err);
|
||
});
|
||
}
|
||
}
|
||
|
||
function showKeyboardHelp() {
|
||
const helpText = `
|
||
🎮 KEYBOARD CONTROLS:
|
||
|
||
▶️ PLAYBACK:
|
||
Space, P, K - Pause/Resume
|
||
→, N, L, J - Next image
|
||
←, B, H - Previous image
|
||
R - Restart from beginning
|
||
|
||
🎯 NAVIGATION:
|
||
Esc, Q, X - Stop slideshow
|
||
F, F11 - Toggle fullscreen
|
||
|
||
❓ HELP:
|
||
?, H - Show this help
|
||
|
||
ℹ️ Controls auto-hide after 3 seconds of no mouse movement.
|
||
Move mouse or use keyboard to show them again.
|
||
`;
|
||
|
||
alert(helpText);
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
|
||
// Slideshow Management Functions
|
||
function loadSavedSlideshows() {
|
||
try {
|
||
const saved = localStorage.getItem('hypnoGallerySlideshows');
|
||
if (saved) {
|
||
savedSlideshows = JSON.parse(saved);
|
||
console.log('📋 Loaded slideshows:', Object.keys(savedSlideshows).length);
|
||
} else {
|
||
savedSlideshows = {};
|
||
console.log('📋 No saved slideshows found, initialized empty object');
|
||
}
|
||
// Refresh grid cell assignments if in grid mode
|
||
if (gridSlideshow.mode !== 'single' && gridSlideshow.cells.length > 0) {
|
||
updateCellAssignments();
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error loading slideshows:', error);
|
||
savedSlideshows = {};
|
||
}
|
||
}
|
||
|
||
function saveSlideshowsToStorage() {
|
||
try {
|
||
localStorage.setItem('hypnoGallerySlideshows', JSON.stringify(savedSlideshows));
|
||
console.log('💾 Slideshows saved to storage');
|
||
// Refresh grid cell assignments if in grid mode
|
||
if (gridSlideshow.mode !== 'single' && gridSlideshow.cells.length > 0) {
|
||
updateCellAssignments();
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error saving slideshows:', error);
|
||
}
|
||
}
|
||
|
||
function updateSlideshowsList() {
|
||
// This function is kept for compatibility but no longer manages dropdown
|
||
console.log('📋 Slideshows available:', Object.keys(savedSlideshows || {}).length);
|
||
|
||
// Update slideshow details if current slideshow is loaded
|
||
updateSlideshowDetails();
|
||
}
|
||
|
||
function updateSlideshowDetails() {
|
||
const details = document.getElementById('slideshowDetails');
|
||
const directoryList = document.getElementById('slideshowDirectoryList');
|
||
const addBtn = document.getElementById('addDirectoryBtn');
|
||
const removeBtn = document.getElementById('removeDirectoryBtn');
|
||
const testBtn = document.getElementById('testDirectoriesBtn');
|
||
|
||
if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
|
||
const slideshow = savedSlideshows[currentSlideshowName];
|
||
const directories = slideshow.directories || [];
|
||
const sourcesCount = directories.length + (slideshow.includeCapturedPhotos ? 1 : 0);
|
||
|
||
details.innerHTML = `
|
||
<div style="font-size: 1.1em; font-weight: bold; color: #667eea; margin-bottom: 0.5rem; border-bottom: 1px solid rgba(102, 126, 234, 0.3); padding-bottom: 0.3rem;">
|
||
🎬 ${currentSlideshowName}
|
||
</div>
|
||
<div>📁 Directories: ${directories.length} folders</div>
|
||
<div>📷 Captured Photos: ${slideshow.includeCapturedPhotos ? 'Included' : 'Excluded'}</div>
|
||
<div>⏱️ Timing: ${slideshow.settings.timingMode} (${slideshow.settings.duration}ms)</div>
|
||
<div>✨ Transition: ${slideshow.settings.transitionType} (${slideshow.settings.transitionDuration}ms)</div>
|
||
<div>📅 Created: ${slideshow.created ? new Date(slideshow.created).toLocaleDateString() : 'Unknown'}</div>
|
||
`;
|
||
|
||
// Update directory list
|
||
if (directories.length > 0) {
|
||
directoryList.innerHTML = directories.map((dir, index) => `
|
||
<div onclick="selectSlideshowDirectory(${index})" class="slideshow-directory-item" data-index="${index}"
|
||
style="padding: 0.25rem 0.5rem; margin: 0.2rem 0; background: rgba(102, 126, 234, 0.1); border-radius: 3px; cursor: pointer; border: 1px solid transparent; color: #ccc; font-size: 0.9rem;">
|
||
📁 ${dir.name || dir}
|
||
</div>
|
||
`).join('');
|
||
} else {
|
||
directoryList.innerHTML = '<div style="color: #999; font-style: italic;">No directories added</div>';
|
||
}
|
||
|
||
// Enable buttons
|
||
addBtn.disabled = false;
|
||
document.getElementById('addFromLibraryBtn').disabled = false;
|
||
removeBtn.disabled = true; // Enable when directory is selected
|
||
testBtn.disabled = directories.length === 0;
|
||
|
||
} else {
|
||
details.innerHTML = '<div>Select a slideshow to view details</div>';
|
||
directoryList.innerHTML = '<div style="color: #999; font-style: italic;">Select a slideshow to manage directories</div>';
|
||
|
||
// Disable buttons
|
||
addBtn.disabled = true;
|
||
document.getElementById('addFromLibraryBtn').disabled = true;
|
||
removeBtn.disabled = true;
|
||
testBtn.disabled = true;
|
||
}
|
||
}
|
||
|
||
function showCreateSlideshowDialog() {
|
||
const dialog = document.createElement('div');
|
||
dialog.id = 'create-slideshow-dialog';
|
||
dialog.innerHTML = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000;">
|
||
<div style="background: linear-gradient(145deg, #1a0d2e, #2d1b3d); border: 2px solid #667eea; border-radius: 15px; padding: 2rem; max-width: 400px; width: 90%; box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);">
|
||
<h3 style="color: #667eea; margin: 0 0 1.5rem 0; text-align: center;">🎬 Create New Slideshow</h3>
|
||
|
||
<div style="margin-bottom: 1.5rem;">
|
||
<label style="color: #ccc; display: block; margin-bottom: 0.5rem;">Slideshow Name:</label>
|
||
<input type="text" id="dialogSlideshowName" placeholder="Enter slideshow name..."
|
||
style="width: 100%; box-sizing: border-box; padding: 0.75rem; background: rgba(0,0,0,0.3); border: 1px solid #667eea; border-radius: 8px; color: white; font-size: 1rem; outline: none; position: relative; z-index: 10001;"
|
||
autocomplete="off" spellcheck="false">
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 1rem; justify-content: center;">
|
||
<button onclick="createSlideshowFromDialog()"
|
||
style="background: #667eea; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
✅ Create
|
||
</button>
|
||
<button onclick="closeSlideshowDialog()"
|
||
style="background: #666; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
❌ Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(dialog);
|
||
|
||
// Focus the input and select all text with multiple attempts
|
||
const focusInput = () => {
|
||
const input = document.getElementById('dialogSlideshowName');
|
||
if (input) {
|
||
input.focus();
|
||
input.select();
|
||
|
||
// Add click handler to ensure input can be clicked
|
||
input.addEventListener('click', () => {
|
||
input.focus();
|
||
});
|
||
|
||
// Add a safety focus on any dialog click
|
||
dialog.addEventListener('click', (e) => {
|
||
if (e.target === input) {
|
||
input.focus();
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
// Try multiple times to ensure focus works
|
||
setTimeout(focusInput, 50);
|
||
setTimeout(focusInput, 150);
|
||
setTimeout(focusInput, 300);
|
||
|
||
// Handle Enter key
|
||
document.getElementById('dialogSlideshowName').addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
createSlideshowFromDialog();
|
||
} else if (e.key === 'Escape') {
|
||
closeSlideshowDialog();
|
||
}
|
||
});
|
||
}
|
||
|
||
function showLoadSlideshowDialog() {
|
||
console.log('🔍 Debug - savedSlideshows:', savedSlideshows);
|
||
console.log('🔍 Debug - slideshow count:', savedSlideshows ? Object.keys(savedSlideshows).length : 'savedSlideshows is null/undefined');
|
||
console.log('🔍 Debug - localStorage check:', localStorage.getItem('hypnoGallerySlideshows'));
|
||
|
||
// Reload slideshows if empty but localStorage has data
|
||
if ((!savedSlideshows || Object.keys(savedSlideshows).length === 0)) {
|
||
const saved = localStorage.getItem('hypnoGallerySlideshows');
|
||
if (saved) {
|
||
try {
|
||
savedSlideshows = JSON.parse(saved);
|
||
console.log('🔄 Reloaded slideshows from localStorage:', Object.keys(savedSlideshows).length);
|
||
} catch (e) {
|
||
console.error('❌ Error parsing saved slideshows:', e);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!savedSlideshows || Object.keys(savedSlideshows).length === 0) {
|
||
alert('No saved slideshows available. Create one first!');
|
||
return;
|
||
}
|
||
|
||
const dialog = document.createElement('div');
|
||
dialog.id = 'load-slideshow-dialog';
|
||
dialog.innerHTML = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000;">
|
||
<div style="background: linear-gradient(145deg, #1a0d2e, #2d1b3d); border: 2px solid #4CAF50; border-radius: 15px; padding: 2rem; max-width: 400px; width: 90%; box-shadow: 0 20px 40px rgba(76, 175, 80, 0.3);">
|
||
<h3 style="color: #4CAF50; margin: 0 0 1.5rem 0; text-align: center;">📂 Load Slideshow</h3>
|
||
|
||
<div style="margin-bottom: 1.5rem;">
|
||
<label style="color: #ccc; display: block; margin-bottom: 0.5rem;">Select Slideshow:</label>
|
||
<select id="dialogSlideshowSelect"
|
||
style="width: 100%; padding: 0.75rem; background: rgba(0,0,0,0.3); border: 1px solid #4CAF50; border-radius: 8px; color: white; font-size: 1rem;">
|
||
<option value="">Choose a slideshow...</option>
|
||
${Object.keys(savedSlideshows).map(name =>
|
||
`<option value="${name}">${name}</option>`
|
||
).join('')}
|
||
</select>
|
||
</div>
|
||
|
||
<div id="dialogSlideshowInfo" style="margin-bottom: 1.5rem; color: #ccc; font-size: 0.9rem; min-height: 60px;">
|
||
Select a slideshow to view details
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 1rem; justify-content: center;">
|
||
<button onclick="loadSlideshowFromDialog()" id="dialogLoadBtn" disabled
|
||
style="background: #4CAF50; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
📂 Load
|
||
</button>
|
||
<button onclick="closeSlideshowDialog()"
|
||
style="background: #666; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
❌ Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(dialog);
|
||
|
||
// Add change listener for slideshow selection
|
||
document.getElementById('dialogSlideshowSelect').addEventListener('change', updateDialogSlideshowInfo);
|
||
}
|
||
|
||
function updateDialogSlideshowInfo() {
|
||
const select = document.getElementById('dialogSlideshowSelect');
|
||
const info = document.getElementById('dialogSlideshowInfo');
|
||
const loadBtn = document.getElementById('dialogLoadBtn');
|
||
|
||
if (select.value && savedSlideshows[select.value]) {
|
||
const slideshow = savedSlideshows[select.value];
|
||
const directories = slideshow.directories || [];
|
||
|
||
info.innerHTML = `
|
||
<div>📁 Directories: ${directories.length} folders</div>
|
||
<div>📷 Captured Photos: ${slideshow.includeCapturedPhotos ? 'Included' : 'Excluded'}</div>
|
||
<div>⏱️ Timing: ${slideshow.settings.timingMode}</div>
|
||
<div>📅 Created: ${slideshow.created ? new Date(slideshow.created).toLocaleDateString() : 'Unknown'}</div>
|
||
`;
|
||
loadBtn.disabled = false;
|
||
} else {
|
||
info.textContent = 'Select a slideshow to view details';
|
||
loadBtn.disabled = true;
|
||
}
|
||
}
|
||
|
||
function createSlideshowFromDialog() {
|
||
const nameInput = document.getElementById('dialogSlideshowName');
|
||
const name = nameInput.value.trim();
|
||
|
||
if (!name) {
|
||
alert('Please enter a slideshow name');
|
||
nameInput.focus();
|
||
return;
|
||
}
|
||
|
||
if (savedSlideshows[name]) {
|
||
if (!confirm(`Slideshow "${name}" already exists. Replace it?`)) {
|
||
nameInput.focus();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Create slideshow with current settings and empty directories
|
||
savedSlideshows[name] = {
|
||
name: name,
|
||
settings: { ...currentSettings },
|
||
directories: [], // Slideshow-specific directories
|
||
includeCapturedPhotos: true, // Default to including captured photos
|
||
created: Date.now(),
|
||
modified: Date.now()
|
||
};
|
||
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowsList();
|
||
|
||
// Load the new slideshow
|
||
loadSlideshow(name);
|
||
|
||
closeSlideshowDialog();
|
||
|
||
console.log('✅ Created slideshow:', name);
|
||
alert(`Slideshow "${name}" created successfully!`);
|
||
}
|
||
|
||
function loadSlideshowFromDialog() {
|
||
const select = document.getElementById('dialogSlideshowSelect');
|
||
const name = select.value;
|
||
|
||
if (!name || !savedSlideshows[name]) {
|
||
alert('Please select a slideshow to load');
|
||
return;
|
||
}
|
||
|
||
// Load the slideshow directly
|
||
loadSlideshow(name);
|
||
|
||
closeSlideshowDialog();
|
||
|
||
console.log('📂 Loaded slideshow from dialog:', name);
|
||
}
|
||
|
||
function closeSlideshowDialog() {
|
||
const createDialog = document.getElementById('create-slideshow-dialog');
|
||
const loadDialog = document.getElementById('load-slideshow-dialog');
|
||
|
||
if (createDialog) createDialog.remove();
|
||
if (loadDialog) loadDialog.remove();
|
||
}
|
||
|
||
function loadSlideshow(slideshowName = null) {
|
||
// If no name provided and no current slideshow, return
|
||
if (!slideshowName && !currentSlideshowName) {
|
||
currentSlideshowName = null;
|
||
updateCurrentSessionDisplay();
|
||
return;
|
||
}
|
||
|
||
const name = slideshowName || currentSlideshowName;
|
||
|
||
if (!name || !savedSlideshows[name]) {
|
||
// Clear settings if no slideshow selected
|
||
currentSlideshowName = null;
|
||
updateCurrentSessionDisplay();
|
||
return;
|
||
}
|
||
|
||
const slideshow = savedSlideshows[name];
|
||
|
||
// Load settings
|
||
Object.assign(currentSettings, slideshow.settings);
|
||
currentSlideshowName = name;
|
||
|
||
// Update UI controls
|
||
updateUIFromSettings();
|
||
updateCurrentSessionDisplay();
|
||
updateSlideshowDetails();
|
||
|
||
// Refresh grid cell assignments if in grid mode
|
||
if (gridSlideshow.mode !== 'single' && gridSlideshow.cells.length > 0) {
|
||
updateCellAssignments();
|
||
}
|
||
|
||
console.log('📂 Auto-loaded slideshow:', name);
|
||
}
|
||
|
||
function saveCurrentAsSlideshow() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
alert('Please load a slideshow first, or create a new one');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`Update slideshow "${currentSlideshowName}" with current settings?`)) {
|
||
return;
|
||
}
|
||
|
||
savedSlideshows[currentSlideshowName].settings = { ...currentSettings };
|
||
savedSlideshows[currentSlideshowName].modified = Date.now();
|
||
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowDetails();
|
||
|
||
console.log('💾 Updated slideshow:', currentSlideshowName);
|
||
alert(`Slideshow "${currentSlideshowName}" updated successfully!`);
|
||
}
|
||
|
||
function duplicateSlideshow() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
alert('Please load a slideshow first');
|
||
return;
|
||
}
|
||
|
||
showDuplicateSlideshowDialog(currentSlideshowName);
|
||
}
|
||
|
||
function showDuplicateSlideshowDialog(originalName) {
|
||
const dialog = document.createElement('div');
|
||
dialog.id = 'duplicate-slideshow-dialog';
|
||
dialog.innerHTML = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000;">
|
||
<div style="background: linear-gradient(145deg, #1a0d2e, #2d1b3d); border: 2px solid #2196F3; border-radius: 15px; padding: 2rem; max-width: 400px; width: 90%; box-shadow: 0 20px 40px rgba(33, 150, 243, 0.3);">
|
||
<h3 style="color: #2196F3; margin: 0 0 1.5rem 0; text-align: center;">📋 Duplicate Slideshow</h3>
|
||
|
||
<div style="margin-bottom: 1rem; color: #ccc; text-align: center;">
|
||
Duplicating: <span style="color: #2196F3; font-weight: bold;">${originalName}</span>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 1.5rem;">
|
||
<label style="color: #ccc; display: block; margin-bottom: 0.5rem;">New Slideshow Name:</label>
|
||
<input type="text" id="dialogDuplicateName" placeholder="Enter new name..." value="${originalName} Copy"
|
||
style="width: 100%; box-sizing: border-box; padding: 0.75rem; background: rgba(0,0,0,0.3); border: 1px solid #2196F3; border-radius: 8px; color: white; font-size: 1rem; outline: none; position: relative; z-index: 10001;"
|
||
autocomplete="off" spellcheck="false">
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 1rem; justify-content: center;">
|
||
<button onclick="executeDuplicate()"
|
||
style="background: #2196F3; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
✅ Duplicate
|
||
</button>
|
||
<button onclick="closeDuplicateDialog()"
|
||
style="background: #666; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
❌ Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(dialog);
|
||
|
||
// Focus the input and select all text
|
||
const focusInput = () => {
|
||
const input = document.getElementById('dialogDuplicateName');
|
||
if (input) {
|
||
input.focus();
|
||
input.select();
|
||
}
|
||
};
|
||
|
||
setTimeout(focusInput, 50);
|
||
setTimeout(focusInput, 150);
|
||
|
||
// Handle Enter key
|
||
document.getElementById('dialogDuplicateName').addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
executeDuplicate();
|
||
} else if (e.key === 'Escape') {
|
||
closeDuplicateDialog();
|
||
}
|
||
});
|
||
}
|
||
|
||
function executeDuplicate() {
|
||
const input = document.getElementById('dialogDuplicateName');
|
||
const newName = input.value.trim();
|
||
const originalName = currentSlideshowName;
|
||
|
||
if (!newName) {
|
||
alert('Please enter a name for the duplicate slideshow');
|
||
input.focus();
|
||
return;
|
||
}
|
||
|
||
if (savedSlideshows[newName]) {
|
||
if (!confirm(`Slideshow "${newName}" already exists. Replace it?`)) {
|
||
input.focus();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Perform the duplication
|
||
savedSlideshows[newName] = {
|
||
...savedSlideshows[originalName],
|
||
name: newName,
|
||
created: Date.now(),
|
||
modified: Date.now()
|
||
};
|
||
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowsList();
|
||
|
||
// Load the new slideshow
|
||
loadSlideshow(newName);
|
||
|
||
closeDuplicateDialog();
|
||
|
||
console.log('📋 Duplicated slideshow:', originalName, '→', newName);
|
||
alert(`Slideshow duplicated as "${newName}"!`);
|
||
}
|
||
|
||
function closeDuplicateDialog() {
|
||
const dialog = document.getElementById('duplicate-slideshow-dialog');
|
||
if (dialog) dialog.remove();
|
||
}
|
||
|
||
function showLibrarySelectionDialog() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
alert('Please load a slideshow first');
|
||
return;
|
||
}
|
||
|
||
const dialog = document.createElement('div');
|
||
dialog.id = 'library-selection-dialog';
|
||
dialog.innerHTML = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000;">
|
||
<div style="background: linear-gradient(145deg, #1a0d2e, #2d1b3d); border: 2px solid #FF9800; border-radius: 15px; padding: 2rem; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 20px 40px rgba(255, 152, 0, 0.3);">
|
||
<h3 style="color: #FF9800; margin: 0 0 1.5rem 0; text-align: center;">📚 Add from Library</h3>
|
||
|
||
<div style="margin-bottom: 1rem;">
|
||
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
|
||
<input type="checkbox" id="libraryCapturedPhotos" style="margin: 0;">
|
||
<label for="libraryCapturedPhotos" style="color: #ccc; margin: 0;">📷 Include Captured Photos</label>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 1rem;">
|
||
<label style="color: #ccc; display: block; margin-bottom: 0.5rem;">📁 Available Directories:</label>
|
||
<div id="libraryDirectoriesList" style="max-height: 300px; overflow-y: auto; background: rgba(0,0,0,0.3); border: 1px solid #FF9800; border-radius: 8px; padding: 0.5rem;">
|
||
Loading directories...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 1rem; justify-content: center;">
|
||
<button onclick="addSelectedFromLibrary()"
|
||
style="background: #FF9800; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
✅ Add Selected
|
||
</button>
|
||
<button onclick="closeLibraryDialog()"
|
||
style="background: #666; color: white; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem;">
|
||
❌ Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(dialog);
|
||
|
||
// Load available directories
|
||
loadLibraryDirectories();
|
||
}
|
||
|
||
async function loadLibraryDirectories() {
|
||
const container = document.getElementById('libraryDirectoriesList');
|
||
|
||
try {
|
||
// Get linked directories from localStorage
|
||
const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||
console.log('📚 Loading library directories:', linkedDirectories);
|
||
|
||
if (linkedDirectories.length === 0) {
|
||
container.innerHTML = '<div style="color: #999; font-style: italic; text-align: center; padding: 1rem;">No directories in library. Add some through the main game interface first.</div>';
|
||
return;
|
||
}
|
||
|
||
// Create checkboxes for each directory
|
||
container.innerHTML = linkedDirectories.map((dir, index) => {
|
||
const dirPath = dir.path || dir;
|
||
const dirName = typeof dir === 'string' ? dir.split(/[\\\\/]/).pop() : (dir.name || dirPath.split(/[\\\\/]/).pop() || 'Unknown Directory');
|
||
const imageCount = dir.imageCount || dir.images?.length || 0;
|
||
|
||
return `
|
||
<div style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; margin: 0.2rem 0; background: rgba(255, 152, 0, 0.1); border-radius: 5px; border: 1px solid transparent; cursor: pointer;"
|
||
onclick="toggleLibraryDirectory(${index})">
|
||
<input type="checkbox" id="libDir_${index}" style="margin: 0;">
|
||
<label for="libDir_${index}" style="color: #ccc; flex: 1; cursor: pointer; margin: 0;">
|
||
📁 ${dirName} <span style="color: #FF9800;">(${imageCount} images)</span>
|
||
</label>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error loading library directories:', error);
|
||
container.innerHTML = '<div style="color: #f44336; text-align: center; padding: 1rem;">Error loading directories</div>';
|
||
}
|
||
}
|
||
|
||
function toggleLibraryDirectory(index) {
|
||
const checkbox = document.getElementById(`libDir_${index}`);
|
||
if (checkbox) {
|
||
checkbox.checked = !checkbox.checked;
|
||
}
|
||
}
|
||
|
||
function addSelectedFromLibrary() {
|
||
const includeCapturedPhotos = document.getElementById('libraryCapturedPhotos').checked;
|
||
const selectedDirectories = [];
|
||
|
||
// Get selected directories from localStorage
|
||
const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||
linkedDirectories.forEach((dir, index) => {
|
||
const checkbox = document.getElementById(`libDir_${index}`);
|
||
if (checkbox && checkbox.checked) {
|
||
const dirPath = dir.path || dir;
|
||
selectedDirectories.push({
|
||
path: dirPath,
|
||
name: typeof dir === 'string' ? dir.split(/[\\\\/]/).pop() : (dir.name || dirPath.split(/[\\\\/]/).pop() || 'Unknown Directory')
|
||
});
|
||
}
|
||
});
|
||
|
||
// Allow adding nothing (user might want slideshow-specific directories only)
|
||
if (selectedDirectories.length === 0 && !includeCapturedPhotos) {
|
||
if (!confirm('No library directories or captured photos will be added. Continue anyway?')) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Add to current slideshow
|
||
const slideshow = savedSlideshows[currentSlideshowName];
|
||
if (!slideshow.directories) {
|
||
slideshow.directories = [];
|
||
}
|
||
|
||
// Add directories (check for duplicates)
|
||
let addedCount = 0;
|
||
selectedDirectories.forEach(newDir => {
|
||
const exists = slideshow.directories.find(existingDir =>
|
||
(typeof existingDir === 'string' ? existingDir : existingDir.path) === newDir.path
|
||
);
|
||
|
||
if (!exists) {
|
||
slideshow.directories.push(newDir);
|
||
addedCount++;
|
||
}
|
||
});
|
||
|
||
// Update captured photos setting only if it changed
|
||
const currentCapturedPhotos = slideshow.includeCapturedPhotos || false;
|
||
if (includeCapturedPhotos !== currentCapturedPhotos) {
|
||
slideshow.includeCapturedPhotos = includeCapturedPhotos;
|
||
}
|
||
|
||
// Save and update
|
||
slideshow.modified = Date.now();
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowDetails();
|
||
|
||
closeLibraryDialog();
|
||
|
||
// Better messaging
|
||
let message = '';
|
||
if (addedCount > 0) {
|
||
message = `Added ${addedCount} director${addedCount === 1 ? 'y' : 'ies'} to slideshow!`;
|
||
} else if (selectedDirectories.length > 0) {
|
||
message = 'No new directories were added (duplicates found)';
|
||
} else {
|
||
message = 'No directories selected.';
|
||
}
|
||
|
||
if (includeCapturedPhotos !== currentCapturedPhotos) {
|
||
const capturedAction = includeCapturedPhotos ? 'enabled' : 'disabled';
|
||
message += ` Captured photos ${capturedAction}.`;
|
||
}
|
||
|
||
if (message.trim()) {
|
||
alert(message);
|
||
}
|
||
|
||
console.log('📚 Added from library:', { directories: addedCount, capturedPhotos: includeCapturedPhotos });
|
||
}
|
||
|
||
function closeLibraryDialog() {
|
||
const dialog = document.getElementById('library-selection-dialog');
|
||
if (dialog) dialog.remove();
|
||
}
|
||
|
||
// Background Management Functions
|
||
function setupColorControl(colorId) {
|
||
const colorPicker = document.getElementById(colorId);
|
||
const hexInput = document.getElementById(colorId + 'Hex');
|
||
|
||
colorPicker.addEventListener('change', (e) => {
|
||
const color = e.target.value;
|
||
hexInput.value = color;
|
||
currentSettings[colorId] = color;
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
});
|
||
|
||
hexInput.addEventListener('input', (e) => {
|
||
const color = e.target.value;
|
||
if (/^#[0-9A-F]{6}$/i.test(color)) {
|
||
colorPicker.value = color;
|
||
currentSettings[colorId] = color;
|
||
applyBackground();
|
||
saveHypnoGallerySettings();
|
||
}
|
||
});
|
||
}
|
||
|
||
function updateBackgroundOptions(type) {
|
||
const solidOptions = document.getElementById('solidColorOptions');
|
||
const gradientOptions = document.getElementById('gradientOptions');
|
||
const blurredOptions = document.getElementById('blurredOptions');
|
||
|
||
// Hide all options first
|
||
solidOptions.style.display = 'none';
|
||
gradientOptions.style.display = 'none';
|
||
blurredOptions.style.display = 'none';
|
||
|
||
// Show relevant options
|
||
switch (type) {
|
||
case 'solid':
|
||
solidOptions.style.display = 'flex';
|
||
break;
|
||
case 'gradient':
|
||
gradientOptions.style.display = 'block';
|
||
break;
|
||
case 'blurred':
|
||
blurredOptions.style.display = 'block';
|
||
break;
|
||
}
|
||
}
|
||
|
||
function applyBackground() {
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
if (!gridContainer) return;
|
||
|
||
const { backgroundType, backgroundColor, gradientColor1, gradientColor2, gradientDirection, blurAmount, blurOpacity } = currentSettings;
|
||
|
||
// Ensure container has proper positioning for backgrounds
|
||
if (gridContainer.style.position !== 'relative') {
|
||
gridContainer.style.position = 'relative';
|
||
}
|
||
|
||
// Remove existing background elements
|
||
const existingBg = gridContainer.querySelector('.slideshow-background');
|
||
if (existingBg) existingBg.remove();
|
||
|
||
// Reset any previous background styles
|
||
gridContainer.style.background = '';
|
||
gridContainer.style.backgroundColor = '';
|
||
|
||
switch (backgroundType) {
|
||
case 'solid':
|
||
gridContainer.style.backgroundColor = backgroundColor;
|
||
break;
|
||
|
||
case 'gradient':
|
||
const gradientBg = `linear-gradient(${gradientDirection}, ${gradientColor1}, ${gradientColor2})`;
|
||
gridContainer.style.background = gradientBg;
|
||
break;
|
||
|
||
case 'blurred':
|
||
gridContainer.style.backgroundColor = '#000000'; // Fallback color
|
||
createBlurredBackground();
|
||
break;
|
||
}
|
||
}
|
||
|
||
function createBlurredBackground() {
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
// Use the first grid cell's image for the blur effect
|
||
const firstCell = gridCells.length > 0 ? gridCells[0] : null;
|
||
const currentImg = firstCell ? firstCell.element.querySelector('.grid-image') : null;
|
||
|
||
if (!currentImg || !currentImg.src || !gridContainer) {
|
||
console.log('🫧 Cannot create blurred background:', {
|
||
hasContainer: !!gridContainer,
|
||
hasImg: !!currentImg,
|
||
hasSrc: !!currentImg?.src
|
||
});
|
||
return;
|
||
}
|
||
|
||
console.log('🫧 Creating blurred background with:', currentImg.src);
|
||
|
||
// Create background element
|
||
const backgroundDiv = document.createElement('div');
|
||
backgroundDiv.className = 'slideshow-background';
|
||
backgroundDiv.style.cssText = `
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-image: url("${currentImg.src}");
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
filter: blur(${currentSettings.blurAmount}px);
|
||
opacity: ${currentSettings.blurOpacity / 100};
|
||
z-index: 1;
|
||
pointer-events: none;
|
||
`;
|
||
|
||
// Insert background behind the grid
|
||
gridContainer.insertBefore(backgroundDiv, gridContainer.firstChild);
|
||
|
||
// Ensure grid cells are above the background
|
||
gridCells.forEach(cell => {
|
||
if (cell.element) {
|
||
cell.element.style.position = 'relative';
|
||
cell.element.style.zIndex = '2';
|
||
}
|
||
});
|
||
|
||
console.log('✅ Blurred background created successfully');
|
||
}
|
||
|
||
function deleteSlideshow() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
alert('Please load a slideshow first');
|
||
return;
|
||
}
|
||
|
||
const name = currentSlideshowName;
|
||
|
||
if (!confirm(`Delete slideshow "${name}"? This cannot be undone.`)) {
|
||
return;
|
||
}
|
||
|
||
delete savedSlideshows[name];
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowsList();
|
||
|
||
// Clear current slideshow since it was deleted
|
||
currentSlideshowName = null;
|
||
updateCurrentSessionDisplay();
|
||
|
||
console.log('🗑️ Deleted slideshow:', name);
|
||
alert(`Slideshow "${name}" deleted successfully!`);
|
||
}
|
||
|
||
function updateUIFromSettings() {
|
||
// Timing settings
|
||
document.getElementById('timingMode').value = currentSettings.timingMode;
|
||
document.getElementById('durationSlider').value = currentSettings.duration;
|
||
document.getElementById('minDurationSlider').value = currentSettings.minDuration;
|
||
document.getElementById('maxDurationSlider').value = currentSettings.maxDuration;
|
||
document.getElementById('waveMinSlider').value = currentSettings.waveMin;
|
||
document.getElementById('waveMaxSlider').value = currentSettings.waveMax;
|
||
document.getElementById('waveRateSlider').value = currentSettings.waveRate;
|
||
|
||
// Transition settings
|
||
document.getElementById('transitionType').value = currentSettings.transitionType;
|
||
document.getElementById('transitionDuration').value = currentSettings.transitionDuration;
|
||
|
||
// Directory settings (checkbox removed)
|
||
|
||
// Background settings
|
||
document.getElementById('backgroundType').value = currentSettings.backgroundType;
|
||
document.getElementById('backgroundColor').value = currentSettings.backgroundColor;
|
||
document.getElementById('backgroundColorHex').value = currentSettings.backgroundColor;
|
||
document.getElementById('gradientColor1').value = currentSettings.gradientColor1;
|
||
document.getElementById('gradientColor1Hex').value = currentSettings.gradientColor1;
|
||
document.getElementById('gradientColor2').value = currentSettings.gradientColor2;
|
||
document.getElementById('gradientColor2Hex').value = currentSettings.gradientColor2;
|
||
document.getElementById('gradientDirection').value = currentSettings.gradientDirection;
|
||
document.getElementById('blurAmount').value = currentSettings.blurAmount;
|
||
document.getElementById('blurAmountValue').textContent = currentSettings.blurAmount + 'px';
|
||
document.getElementById('blurOpacity').value = currentSettings.blurOpacity;
|
||
document.getElementById('blurOpacityValue').textContent = currentSettings.blurOpacity + '%';
|
||
|
||
// Update displays
|
||
updateTimingSettings(currentSettings.timingMode);
|
||
updateTransitionSettings(currentSettings.transitionType);
|
||
updateBackgroundOptions(currentSettings.backgroundType);
|
||
|
||
// Refresh directory display (if function exists)
|
||
if (typeof updateLinkedDirectories === 'function') {
|
||
updateLinkedDirectories();
|
||
}
|
||
}
|
||
|
||
function updateCurrentSessionDisplay() {
|
||
let sourcesCount = 0;
|
||
|
||
if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
|
||
const slideshow = savedSlideshows[currentSlideshowName];
|
||
sourcesCount = (slideshow.directories || []).length + (slideshow.includeCapturedPhotos ? 1 : 0);
|
||
} else {
|
||
sourcesCount = currentSettings.selectedDirectories.length +
|
||
(currentSettings.includeCapturedPhotos ? 1 : 0);
|
||
}
|
||
|
||
document.getElementById('currentSources').textContent =
|
||
sourcesCount > 0 ? `${sourcesCount} sources selected` : 'No sources selected';
|
||
|
||
let timingText = currentSettings.timingMode;
|
||
if (currentSettings.timingMode === 'constant') {
|
||
timingText += ` (${(currentSettings.duration/1000).toFixed(1)}s)`;
|
||
} else if (currentSettings.timingMode === 'random') {
|
||
timingText += ` (${(currentSettings.minDuration/1000).toFixed(1)}-${(currentSettings.maxDuration/1000).toFixed(1)}s)`;
|
||
} else if (currentSettings.timingMode === 'wave') {
|
||
timingText += ` (${(currentSettings.waveMin/1000).toFixed(1)}-${(currentSettings.waveMax/1000).toFixed(1)}s)`;
|
||
}
|
||
document.getElementById('currentTiming').textContent = timingText;
|
||
|
||
document.getElementById('currentTransitions').textContent =
|
||
`${currentSettings.transitionType} (${(currentSettings.transitionDuration/1000).toFixed(1)}s)`;
|
||
}
|
||
|
||
// Slideshow Directory Management Functions
|
||
let selectedDirectoryIndex = -1;
|
||
|
||
function selectSlideshowDirectory(index) {
|
||
// Remove previous selection
|
||
document.querySelectorAll('.slideshow-directory-item').forEach(item => {
|
||
item.style.border = '1px solid transparent';
|
||
item.style.background = 'rgba(102, 126, 234, 0.1)';
|
||
});
|
||
|
||
// Select new item
|
||
const items = document.querySelectorAll('.slideshow-directory-item');
|
||
if (items[index]) {
|
||
items[index].style.border = '1px solid #667eea';
|
||
items[index].style.background = 'rgba(102, 126, 234, 0.3)';
|
||
selectedDirectoryIndex = index;
|
||
document.getElementById('removeDirectoryBtn').disabled = false;
|
||
}
|
||
}
|
||
|
||
async function addDirectoryToSlideshow() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
alert('Please load a slideshow first');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Check if we're in Electron environment
|
||
if (!window.electronAPI || !window.electronAPI.selectDirectory) {
|
||
alert('Directory selection is only available in the desktop version of the application.');
|
||
return;
|
||
}
|
||
|
||
// Use the existing selectDirectory method
|
||
const directoryPath = await window.electronAPI.selectDirectory();
|
||
|
||
if (directoryPath) {
|
||
const directoryName = directoryPath.split(/[\\/]/).pop();
|
||
|
||
// Initialize directories array if it doesn't exist
|
||
if (!savedSlideshows[currentSlideshowName].directories) {
|
||
savedSlideshows[currentSlideshowName].directories = [];
|
||
}
|
||
|
||
// Check if directory already exists
|
||
const existingDir = savedSlideshows[currentSlideshowName].directories.find(dir =>
|
||
(typeof dir === 'string' ? dir : dir.path) === directoryPath
|
||
);
|
||
|
||
if (existingDir) {
|
||
alert('This directory is already added to the slideshow');
|
||
return;
|
||
}
|
||
|
||
// Add directory to slideshow
|
||
savedSlideshows[currentSlideshowName].directories.push({
|
||
path: directoryPath,
|
||
name: directoryName
|
||
});
|
||
|
||
savedSlideshows[currentSlideshowName].modified = Date.now();
|
||
saveSlideshowsToStorage();
|
||
updateSlideshowDetails();
|
||
updateCurrentSessionDisplay();
|
||
|
||
console.log('📁 Added directory to slideshow:', directoryName);
|
||
alert(`Added directory: ${directoryName}`);
|
||
} else {
|
||
console.log('Directory selection cancelled');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error adding directory:', error);
|
||
alert('Error adding directory: ' + error.message);
|
||
}
|
||
}
|
||
|
||
function removeDirectoryFromSlideshow() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName] || selectedDirectoryIndex === -1) {
|
||
return;
|
||
}
|
||
|
||
const directories = savedSlideshows[currentSlideshowName].directories || [];
|
||
if (selectedDirectoryIndex >= 0 && selectedDirectoryIndex < directories.length) {
|
||
const removedDir = directories[selectedDirectoryIndex];
|
||
directories.splice(selectedDirectoryIndex, 1);
|
||
|
||
savedSlideshows[currentSlideshowName].modified = Date.now();
|
||
saveSlideshowsToStorage();
|
||
|
||
selectedDirectoryIndex = -1;
|
||
updateSlideshowDetails();
|
||
updateCurrentSessionDisplay();
|
||
|
||
console.log('🗑️ Removed directory from slideshow:', removedDir.name || removedDir);
|
||
}
|
||
}
|
||
|
||
async function testSlideshowDirectories() {
|
||
if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
|
||
return;
|
||
}
|
||
|
||
const slideshow = savedSlideshows[currentSlideshowName];
|
||
const directories = slideshow.directories || [];
|
||
|
||
if (directories.length === 0) {
|
||
alert('No directories to test');
|
||
return;
|
||
}
|
||
|
||
let totalImages = 0;
|
||
let validDirs = 0;
|
||
let invalidDirs = [];
|
||
|
||
// Test each directory
|
||
for (const dir of directories) {
|
||
const dirPath = typeof dir === 'string' ? dir : dir.path;
|
||
const dirName = typeof dir === 'string' ? dirPath.split(/[\\/]/).pop() : dir.name;
|
||
|
||
try {
|
||
// For electron environments, we could use fs to check directory
|
||
// For now, just assume they're valid and count as 1 image each for demo
|
||
validDirs++;
|
||
totalImages += 10; // Placeholder count
|
||
} catch (error) {
|
||
invalidDirs.push(dirName);
|
||
}
|
||
}
|
||
|
||
const message = `
|
||
📁 Directory Test Results:
|
||
✅ Valid directories: ${validDirs}
|
||
❌ Invalid directories: ${invalidDirs.length}
|
||
🖼️ Estimated images: ${totalImages}
|
||
|
||
${invalidDirs.length > 0 ? `\nInvalid directories:\n${invalidDirs.join('\n')}` : ''}
|
||
`;
|
||
|
||
alert(message.trim());
|
||
}
|
||
|
||
async function getSlideshowImages() {
|
||
let images = [];
|
||
|
||
// If a slideshow is loaded, use its directories
|
||
if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
|
||
const slideshow = savedSlideshows[currentSlideshowName];
|
||
const directories = slideshow.directories || [];
|
||
|
||
console.log('📁 Loading images from slideshow directories:', directories.length);
|
||
|
||
// Load images from slideshow-specific directories
|
||
for (const dir of directories) {
|
||
const dirPath = typeof dir === 'string' ? dir : dir.path;
|
||
try {
|
||
const dirImages = await loadImagesFromDirectory(dirPath);
|
||
images.push(...dirImages);
|
||
console.log(`📁 Loaded ${dirImages.length} images from: ${dirPath}`);
|
||
} catch (error) {
|
||
console.error(`❌ Error loading directory ${dirPath}:`, error);
|
||
}
|
||
}
|
||
|
||
// Include captured photos if enabled
|
||
if (slideshow.includeCapturedPhotos) {
|
||
const capturedPhotos = getCapturedPhotos();
|
||
images.push(...capturedPhotos);
|
||
console.log(`📷 Added ${capturedPhotos.length} captured photos`);
|
||
}
|
||
} else {
|
||
// Fallback to main library if no slideshow selected
|
||
console.log('📚 Using main image library');
|
||
images = [...imageLibrary];
|
||
}
|
||
|
||
console.log(`🖼️ Total slideshow images: ${images.length}`);
|
||
return images;
|
||
}
|
||
|
||
// Grid-specific image loading function for individual cells
|
||
async function getGridCellImages(slideshow) {
|
||
let images = [];
|
||
|
||
console.log(`🔲 Loading images for grid cell slideshow: ${slideshow.name}`);
|
||
|
||
// Load images from slideshow-specific directories
|
||
const directories = slideshow.directories || [];
|
||
console.log(`📁 Slideshow directories: ${directories.length}`);
|
||
|
||
for (const dir of directories) {
|
||
const dirPath = typeof dir === 'string' ? dir : dir.path;
|
||
try {
|
||
const dirImages = await loadImagesFromDirectory(dirPath);
|
||
images.push(...dirImages);
|
||
console.log(`📁 Grid cell loaded ${dirImages.length} images from: ${dirPath}`);
|
||
} catch (error) {
|
||
console.error(`❌ Grid cell error loading directory ${dirPath}:`, error);
|
||
}
|
||
}
|
||
|
||
// Include captured photos if enabled
|
||
if (slideshow.includeCapturedPhotos) {
|
||
const capturedPhotos = getCapturedPhotos();
|
||
images.push(...capturedPhotos);
|
||
console.log(`📷 Grid cell added ${capturedPhotos.length} captured photos`);
|
||
}
|
||
|
||
// Fallback to main library if no directories configured
|
||
if (images.length === 0) {
|
||
console.log('📚 Grid cell using main image library as fallback');
|
||
images = [...imageLibrary];
|
||
}
|
||
|
||
console.log(`🖼️ Grid cell total images: ${images.length}`);
|
||
return images;
|
||
}
|
||
|
||
async function loadImagesFromDirectory(directoryPath) {
|
||
// Load images from a directory recursively using the main process function
|
||
console.log(`🔍 Starting recursive image scan of: ${directoryPath}`);
|
||
|
||
if (typeof window !== 'undefined' && window.electronAPI && window.electronAPI.readImageDirectoryRecursive) {
|
||
try {
|
||
// Use the proper main process recursive function
|
||
const files = await window.electronAPI.readImageDirectoryRecursive(directoryPath);
|
||
console.log(`✅ Found ${files.length} images recursively`);
|
||
|
||
// Convert to the expected format for the slideshow system
|
||
const processedFiles = files.map(file => ({
|
||
name: file.name,
|
||
fullPath: file.path,
|
||
isWebcamCapture: false,
|
||
source: 'slideshow-directory'
|
||
}));
|
||
|
||
return processedFiles;
|
||
} catch (error) {
|
||
console.error('❌ Error reading directory recursively:', error);
|
||
return [];
|
||
}
|
||
} else {
|
||
console.warn('⚠️ Directory loading not available in this environment');
|
||
return [];
|
||
}
|
||
}
|
||
|
||
|
||
|
||
function getCapturedPhotos() {
|
||
// Get captured photos from localStorage or game library
|
||
try {
|
||
const captured = localStorage.getItem('capturedPhotos');
|
||
if (captured) {
|
||
const photos = JSON.parse(captured);
|
||
return photos.map(photo => ({
|
||
name: photo.name || 'Captured Photo',
|
||
path: photo.dataURL || photo.path,
|
||
fullPath: photo.dataURL || photo.path,
|
||
isWebcamCapture: true,
|
||
source: 'captured-photos'
|
||
}));
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error loading captured photos:', error);
|
||
}
|
||
return [];
|
||
}
|
||
|
||
// Initialize when DOM is loaded
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
console.log('🌀 Hypno Gallery DOM loaded');
|
||
|
||
// 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');
|
||
}
|
||
}
|
||
|
||
setTimeout(() => {
|
||
initializeHypnoGallery();
|
||
loadSavedSlideshows();
|
||
|
||
// Initialize grid layout with default mode (after slideshows are loaded)
|
||
setTimeout(() => {
|
||
const displayMode = document.getElementById('displayMode');
|
||
if (displayMode && displayMode.value) {
|
||
updateDisplayMode();
|
||
}
|
||
}, 100);
|
||
|
||
// Dropdown removed - slideshow loading now handled by dialog system
|
||
}, 1000);
|
||
});
|
||
|
||
// Initialize timing settings display
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
updateTimingSettings('constant');
|
||
updateTransitionSettings('fade');
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |