FluxInator / static /script.js
Scalino84
Initial commit
1e7308f
raw
history blame
25.2 kB
// 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);
}