// 1. Logger-Klasse class Logger { static isDebugMode = false; static debug(message, data = null) { if (this.isDebugMode) { console.log(`[Debug] ${message}`, data || ''); } } static error(message, error = null) { console.error(`[Error] ${message}`, error || ''); } static initializeDebugMode() { try { const urlParams = new URLSearchParams(window.location.search); this.isDebugMode = urlParams.has('debug'); } catch (error) { console.error('Fehler beim Initialisieren des Debug-Modus:', error); this.isDebugMode = false; } } } // 2. Utils const utils = { showLoading() { if (document.body) { document.body.classList.add('loading'); Logger.debug('Loading-Status aktiviert'); } else { Logger.error('document.body nicht verfügbar'); } }, hideLoading() { if (document.body) { document.body.classList.remove('loading'); Logger.debug('Loading-Status deaktiviert'); } else { Logger.error('document.body nicht verfügbar'); } }, safeGetElement(id) { const element = document.getElementById(id); if (!element) { Logger.error(`Element mit ID '${id}' nicht gefunden`); return null; } return element; }, async withLoading(asyncFn) { try { this.showLoading(); await asyncFn(); } finally { this.hideLoading(); } } }; // In der ImageModal-Klasse: class ImageModal { constructor() { this.modal = null; this.modalImg = null; this.currentImageIndex = 0; this.selectedImages = []; this.initialize(); } initialize() { if (typeof bootstrap === 'undefined') { Logger.error('Bootstrap ist nicht verfügbar'); return; } const modalElement = utils.safeGetElement('imageModal'); if (!modalElement) return; this.modal = new bootstrap.Modal(modalElement); this.modalImg = utils.safeGetElement('modalImage'); // Event-Listener für Modal-Schließen modalElement.addEventListener('hidden.bs.modal', () => { this.cleanupModal(); }); // Klick-Handler für Modal-Container const imageContainer = document.querySelector('.image-container'); if (imageContainer && this.modalImg) { imageContainer.addEventListener('click', (e) => { if (e.target === this.modalImg) { this.hide(); } }); } // Event-Listener für Tastendruck (Pfeiltasten) document.addEventListener('keydown', (event) => { if (this.modal && this.modal._isShown) { // Modal muss geöffnet sein if (event.key === 'ArrowLeft') { event.stopPropagation(); // Verhindere Standardverhalten und Bubbling this.showPreviousImage(); } else if (event.key === 'ArrowRight') { event.stopPropagation(); // Verhindere Standardverhalten und Bubbling this.showNextImage(); } } }); // Download-Button Handler const downloadBtn = utils.safeGetElement('modalDownloadBtn'); if (downloadBtn) { downloadBtn.addEventListener('click', async () => { const filename = this.modalImg?.dataset?.filename; if (filename) { await this.downloadImage(filename); } else { Logger.error('Kein Dateiname für Download verfügbar'); } }); } } async downloadImage(filename) { await utils.withLoading(async () => { try { const response = await fetch(`/flux-pics/${filename}`); // Direkt über StaticFiles if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { Logger.error('Download-Fehler:', error); alert('Ein Fehler ist beim Download aufgetreten: ' + error.message); } }); } open(img, selectedImages = []) { if (!this.modal || !this.modalImg) { Logger.error('Modal nicht korrekt initialisiert'); return; } Logger.debug('Öffne Bild-Modal', img); // Setze die ausgewählten Bilder und den Index des aktuellen Bildes this.selectedImages = selectedImages; this.currentImageIndex = this.selectedImages.indexOf(img.dataset.filename); this.modalImg.src = img.src; this.modalImg.dataset.filename = img.dataset.filename; const metadataFields = ['format', 'timestamp', 'album', 'category', 'prompt', 'optimized_prompt']; metadataFields.forEach(field => { const element = utils.safeGetElement(`modal${field.charAt(0).toUpperCase() + field.slice(1)}`); if (element) { element.textContent = img.dataset[field] || 'Nicht verfügbar'; } }); // Füge einen Event-Listener hinzu, um den Fokus auf das Modal zu setzen, // nachdem es vollständig angezeigt wurde (shown.bs.modal) this.modal._element.addEventListener('shown.bs.modal', () => { this.modal._element.focus(); }); this.modal.show(); } hide() { this.modal?.hide(); } cleanupModal() { document.body.classList.remove('modal-open'); const backdrop = document.querySelector('.modal-backdrop'); if (backdrop) { backdrop.remove(); } document.body.style.overflow = ''; document.body.style.paddingRight = ''; } showPreviousImage() { if (this.currentImageIndex > 0) { this.currentImageIndex--; this.updateModalImage(); } } showNextImage() { if (this.currentImageIndex < this.selectedImages.length - 1) { this.currentImageIndex++; this.updateModalImage(); } } updateModalImage() { const filename = this.selectedImages[this.currentImageIndex]; const imgElement = document.querySelector(`.image-thumbnail[data-filename="${filename}"]`); if (imgElement) { this.modalImg.src = imgElement.src; this.modalImg.dataset.filename = filename; // Metadaten aktualisieren const metadataFields = ['format', 'timestamp', 'album', 'category', 'prompt', 'optimized_prompt']; metadataFields.forEach(field => { const element = utils.safeGetElement(`modal${field.charAt(0).toUpperCase() + field.slice(1)}`); if (element) { element.textContent = imgElement.dataset[field] || 'Nicht verfügbar'; } }); } else { Logger.error('Bild-Element für Dateiname nicht gefunden:', filename); } } } class GalleryManager { constructor() { this.selectedImages = new Set(); this.imageModal = new ImageModal(); this.initialize(); } initialize() { this.initializeSelectionHandling(); this.initializeGalleryViews(); this.initializeDownloadHandling(); } initializeSelectionHandling() { // "Alle auswählen" Funktionalität const selectAllCheckbox = utils.safeGetElement('selectAll'); if (selectAllCheckbox) { selectAllCheckbox.addEventListener('change', () => { const itemCheckboxes = document.querySelectorAll('.select-item'); itemCheckboxes.forEach(checkbox => { checkbox.checked = selectAllCheckbox.checked; this.updateSelectedImages(checkbox); }); }); } // Einzelne Bildauswahl document.querySelectorAll('.select-item').forEach(checkbox => { checkbox.addEventListener('change', () => this.updateSelectedImages(checkbox)); }); } updateSelectedImages(checkbox) { const card = checkbox.closest('.card'); if (!card) return; const img = card.querySelector('img'); if (!img || !img.dataset.filename) { Logger.error('Ungültiges Bild-Element in der Karte'); return; } if (checkbox.checked) { this.selectedImages.add(img.dataset.filename); } else { this.selectedImages.delete(img.dataset.filename); } Logger.debug(`Ausgewählte Bilder aktualisiert: ${this.selectedImages.size} Bilder`); } getSelectedImages() { return Array.from(this.selectedImages); } initializeGalleryViews() { // Thumbnail-Galerie const thumbGalleryBtn = utils.safeGetElement('thumbgalleryBtn'); if (thumbGalleryBtn) { thumbGalleryBtn.addEventListener('click', () => this.openThumbnailGallery()); } // Grid Layout const gridLayout = utils.safeGetElement('gridLayout'); if (gridLayout) { gridLayout.addEventListener('change', () => this.updateGridLayout(gridLayout.value)); } // Bild-Thumbnails document.querySelectorAll('.image-thumbnail').forEach(img => { img.addEventListener('click', () => this.imageModal.open(img)); }); } async openThumbnailGallery() { const selectedImages = this.getSelectedImages(); if (selectedImages.length === 0) { alert('Keine Bilder ausgewählt.'); return; } const galleryModal = new bootstrap.Modal(utils.safeGetElement('thumbGalleryModal')); const container = utils.safeGetElement('thumbGalleryContainer'); if (!container) return; container.innerHTML = ''; selectedImages.forEach(filename => { const thumbContainer = this.createThumbnailElement(filename); if (thumbContainer) { container.appendChild(thumbContainer); } }); galleryModal.show(); } createThumbnailElement(filename) { const container = document.createElement('div'); container.className = 'thumb-container m-2'; const img = document.createElement('img'); img.src = `/flux-pics/${filename}`; img.className = 'img-thumbnail thumbnail-img'; img.dataset.filename = filename; img.style.maxWidth = '150px'; img.style.cursor = 'pointer'; const downloadBtn = document.createElement('button'); downloadBtn.className = 'btn btn-sm btn-primary download-thumb'; downloadBtn.innerHTML = ''; // Event-Listener img.addEventListener('click', () => this.imageModal.open(img)); downloadBtn.addEventListener('click', async () => { await this.imageModal.downloadImage(filename); }); container.appendChild(img); container.appendChild(downloadBtn); return container; } updateGridLayout(columns) { const imageGrid = utils.safeGetElement('imageGrid'); if (imageGrid) { const validColumns = Math.max(1, Math.min(6, parseInt(columns) || 3)); imageGrid.className = `row row-cols-1 row-cols-md-${validColumns}`; Logger.debug(`Grid-Layout aktualisiert: ${validColumns} Spalten`); } } initializeDownloadHandling() { const downloadBtn = utils.safeGetElement('downloadSelected'); if (downloadBtn) { downloadBtn.addEventListener('click', () => this.handleBulkDownload()); } } async handleBulkDownload() { const selectedImages = this.getSelectedImages(); if (selectedImages.length === 0) { alert('Keine Bilder ausgewählt.'); return; } const useZip = selectedImages.length > 1 && confirm('Möchten Sie die Bilder als ZIP-Datei herunterladen?\nKlicken Sie "OK" für ZIP oder "Abbrechen" für Einzeldownloads.'); await utils.withLoading(async () => { try { if (useZip) { await this.downloadAsZip(selectedImages); } else { await this.downloadIndividually(selectedImages); } /*alert('Download erfolgreich abgeschlossen.');*/ } catch (error) { Logger.error('Bulk-Download Fehler:', error); alert('Ein Fehler ist aufgetreten: ' + error.message); } }); } async downloadAsZip(files) { const response = await fetch('/flux-pics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selectedImages: files }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = 'images.zip'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } async downloadIndividually(files) { for (const filename of files) { await this.imageModal.downloadImage(filename); // Kleine Pause zwischen Downloads await new Promise(resolve => setTimeout(resolve, 500)); } } } // Slideshow-Verwaltung // In der SlideshowManager-Klasse: class SlideshowManager { constructor(gallery) { if (!gallery) { Logger.error('GalleryManager ist erforderlich'); throw new Error('GalleryManager ist erforderlich'); } this.gallery = gallery; this.slideInterval = 3000; this.currentSlideIndex = 0; this.slideshowInterval = null; this.carousel = null; this.slideshowModal = null; // Initialisierung direkt im Konstruktor const slideshowBtn = utils.safeGetElement('slideshowBtn'); if (slideshowBtn) { slideshowBtn.addEventListener('click', () => this.openSlideshow()); } } async openSlideshow() { const selectedImages = this.gallery.getSelectedImages(); if (selectedImages.length === 0) { alert('Keine Bilder ausgewählt.'); return; } const modalElement = utils.safeGetElement('slideshowModal'); if (!modalElement) return; this.slideshowModal = new bootstrap.Modal(modalElement); const container = utils.safeGetElement('slideshowContainer'); if (!container) return; container.innerHTML = ''; this.createSlides(container, selectedImages); const carouselElement = utils.safeGetElement('carouselExampleControls'); if (carouselElement) { this.carousel = new bootstrap.Carousel(carouselElement, { interval: false }); } // Event-Listener für Modal-Schließen modalElement.addEventListener('hidden.bs.modal', () => { this.cleanupSlideshow(); }); this.setupSlideshowControls(); this.slideshowModal.show(); } createSlides(container, images) { images.forEach((filename, index) => { const div = document.createElement('div'); div.classList.add('carousel-item'); if (index === 0) div.classList.add('active'); const img = document.createElement('img'); img.src = `/flux-pics/${filename}`; img.classList.add('d-block', 'w-100'); img.dataset.filename = filename; img.onerror = () => { Logger.error(`Fehler beim Laden des Bildes: ${filename}`); // Optional: Anstelle des Bildes ein Placeholder-Element anzeigen const errorPlaceholder = document.createElement('div'); errorPlaceholder.classList.add('error-placeholder'); errorPlaceholder.textContent = `Fehler beim Laden des Bildes: ${filename}`; div.replaceChild(errorPlaceholder, img); }; div.appendChild(img); container.appendChild(div); }); } setupSlideshowControls() { const playBtn = utils.safeGetElement('playSlideshow'); const pauseBtn = utils.safeGetElement('pauseSlideshow'); if (playBtn && pauseBtn) { playBtn.addEventListener('click', () => this.startSlideshow()); pauseBtn.addEventListener('click', () => this.pauseSlideshow()); } const fullscreenBtn = utils.safeGetElement('fullscreenBtn'); if (fullscreenBtn) { fullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); } const downloadBtn = utils.safeGetElement('downloadCurrentSlide'); if (downloadBtn) { downloadBtn.addEventListener('click', () => this.downloadCurrentSlide()); } } startSlideshow() { if (!this.carousel) return; this.slideshowInterval = setInterval(() => { this.carousel.next(); }, this.slideInterval); const playBtn = utils.safeGetElement('playSlideshow'); const pauseBtn = utils.safeGetElement('pauseSlideshow'); if (playBtn && pauseBtn) { playBtn.style.display = 'none'; pauseBtn.style.display = 'block'; } } pauseSlideshow() { if (this.slideshowInterval) { clearInterval(this.slideshowInterval); this.slideshowInterval = null; } const playBtn = utils.safeGetElement('playSlideshow'); const pauseBtn = utils.safeGetElement('pauseSlideshow'); if (playBtn && pauseBtn) { pauseBtn.style.display = 'none'; playBtn.style.display = 'block'; } } async toggleFullscreen() { const modalElement = utils.safeGetElement('slideshowModal'); if (!modalElement) return; try { if (!document.fullscreenElement) { if (modalElement.requestFullscreen) { await modalElement.requestFullscreen(); } else if (modalElement.webkitRequestFullscreen) { await modalElement.webkitRequestFullscreen(); } else if (modalElement.msRequestFullscreen) { await modalElement.msRequestFullscreen(); } } else { if (document.exitFullscreen) { await document.exitFullscreen(); } } } catch (error) { Logger.error('Vollbild-Fehler:', error); } } async downloadCurrentSlide() { const activeSlide = document.querySelector('.carousel-item.active img'); if (activeSlide?.dataset?.filename) { try { const filename = activeSlide.dataset.filename; const response = await fetch(`/flux-pics/${filename}`); if (!response.ok) { throw new Error(`Fehler beim Herunterladen des Bildes: ${response.status} ${response.statusText}`); } const blob = await response.blob(); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); } catch (error) { Logger.error('Fehler beim Herunterladen des Bildes:', error); } } else { Logger.error('Kein aktives Bild gefunden'); } } cleanupSlideshow() { this.pauseSlideshow(); document.body.classList.remove('modal-open'); const backdrop = document.querySelector('.modal-backdrop'); if (backdrop) { backdrop.remove(); } document.body.style.overflow = ''; document.body.style.paddingRight = ''; if (document.fullscreenElement) { document.exitFullscreen().catch(err => { Logger.error('Fehler beim Beenden des Vollbildmodus:', err); }); } } } // 3. AppInitializer-Klasse class AppInitializer { constructor() { this.gallery = null; this.slideshow = null; } initialize() { try { Logger.initializeDebugMode(); document.addEventListener('DOMContentLoaded', () => { try { this.initializeComponents(); this.setupGlobalEventListeners(); Logger.debug('Anwendung erfolgreich initialisiert'); // >>> Ab hier Deine zusätzlichen Funktionen: // ----------------------------------------------------- // 1) "Alles auswählen" (nur falls du es separat brauchst): const selectAllCheckbox = document.getElementById('selectAll'); if (selectAllCheckbox) { selectAllCheckbox.addEventListener('change', function() { const checkboxes = document.querySelectorAll('.select-item'); checkboxes.forEach(cb => { cb.checked = selectAllCheckbox.checked; }); }); } // 2) Items-per-page-Select: Bei Änderung URL manipulieren und Seite neuladen const itemsPerPageSelect = document.getElementById('itemsPerPageSelect'); if (itemsPerPageSelect) { itemsPerPageSelect.addEventListener('change', function() { const newVal = this.value; // Aktuelle URL analysieren const url = new URL(window.location.href); // items_per_page setzen url.searchParams.set('items_per_page', newVal); // Page zurücksetzen (falls "page" existiert) url.searchParams.delete('page'); // Seite neuladen window.location.href = url.toString(); }); } // ----------------------------------------------------- // >>> Ende deiner zusätzlichen Funktionen } catch (error) { Logger.error('Fehler bei der Initialisierung:', error); alert('Es gab ein Problem beim Laden der Anwendung. Bitte laden Sie die Seite neu.'); } }); } catch (error) { console.error('Kritischer Fehler bei der Initialisierung:', error); } } initializeComponents() { try { this.gallery = new GalleryManager(); this.slideshow = new SlideshowManager(this.gallery); Logger.debug('Komponenten initialisiert'); } catch (error) { Logger.error('Fehler bei der Komponenten-Initialisierung:', error); throw error; } } setupGlobalEventListeners() { try { this.setupScrollToTop(); this.setupKeyboardNavigation(); Logger.debug('Globale Event-Listener eingerichtet'); } catch (error) { Logger.error('Fehler beim Einrichten der Event-Listener:', error); throw error; } } setupScrollToTop() { const scrollTopBtn = utils.safeGetElement('scrollTopBtn'); if (scrollTopBtn) { window.addEventListener('scroll', () => { scrollTopBtn.style.display = window.scrollY > 300 ? 'block' : 'none'; }); scrollTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } } setupKeyboardNavigation() { document.addEventListener('keydown', (e) => this.handleKeyboardNavigation(e)); } handleKeyboardNavigation(e) { if (e.key === 'Escape') { this.closeAllModals(); } } checkBootstrapAvailability() { if (typeof bootstrap === 'undefined') { Logger.error('Bootstrap ist nicht verfügbar'); return false; } return true; } } // 4. Anwendung starten try { const app = new AppInitializer(); app.initialize(); } catch (error) { console.error('Kritischer Fehler beim Erstellen der Anwendung:', error); }