Spaces:
Runtime error
Runtime error
// 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 = '<i class="fas fa-download"></i>'; | |
// 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); | |
} | |