FluxInator / templates /archive.html
Scalino84
Initial commit
1e7308f
raw
history blame
22.3 kB
{% extends "base.html" %}
{% block title %}Flux Bildarchiv{% endblock %}
{% block content %}
<div class="container-fluid bg-light p-4 rounded shadow-sm">
<!-- Suche -->
<div class="mb-3 search-container">
<form id="searchForm" action="/archive" method="get" class="d-flex flex-column">
<div class="mb-3 w-100">
<label for="search" class="form-label">Suche:</label>
<input
type="text"
class="form-control"
id="search"
name="search"
value="{{ search_query }}"
>
</div>
<div class="d-flex flex-wrap mt-3">
<button type="submit" class="btn btn-primary flex-fill" style="width: 33.33%;">Suchen</button>
<button type="reset" class="btn btn-secondary flex-fill ms-2" style="width: 33.33%;">Zurücksetzen</button>
</div>
</form>
</div>
<!-- Filter Accordion -->
<!-- Archiv Anzeige -->
<!-- Archiv Anzeige -->
<div id="archive" class="container-fluid"><div id="archive" class="container">
<div class="accordion" id="filterAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
Filter- und Anzeigeoptionen
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne">
<div class="accordion-body">
<!-- Filterformular -->
<form id="filterForm" action="/archive" method="get" class="d-flex flex-wrap gap-3 align-items-center">
<div class="mb-3 flex-grow-1">
<label for="album_filter" class="form-label">Album:</label>
<select class="form-control" id="album_filter" name="album">
<option value="">Alle</option>
{% for album in albums %}
<option value="{{ album[0] }}" {% if album[0] == selected_album %}selected{% endif %}>{{ album[1] }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3 flex-grow-1">
<label for="category_filter" class="form-label">Kategorie:</label>
<select class="form-control" id="category_filter" name="category" multiple>
<option value="">Alle</option>
{% for category in categories %}
<option value="{{ category[0] }}" {% if category[0] in selected_categories %}selected{% endif %}>{{ category[1] }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary flex-fill">Filtern</button>
</form>
</div>
</div>
</div>
</div>
<div id="tollbarAll" class="toolbar">
<button id="thumbgalleryBtn" class="btn btn-secondary">Thumbgallery</button>
<button id="slideshowBtn" class="btn btn-secondary">Slideshow</button>
<select id="gridLayout" class="form-select">
<option value="2">2 Bilder</option>
<option value="3" selected>3 Bilder</option>
<option value="4">4 Bilder</option>
<option value="5">5 Bilder</option>
<option value="6">6 Bilder</option>
</select>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="actionMenu" data-bs-toggle="dropdown" aria-expanded="false">
Optionen
</button>
<ul class="dropdown-menu" aria-labelledby="actionMenu">
<li><a class="dropdown-item" href="#" id="deleteSelected">Löschen</a></li>
<li><a class="dropdown-item" href="#" id="addToCategory">Zu Kategorie hinzufügen</a></li>
<li><a class="dropdown-item" href="#" id="addToAlbum">Zu Album hinzufügen</a></li>
<li><a class="dropdown-item" href="#" id="downloadSelected">Aktuelle Auswahl downloaden</a></li>
</ul>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3 mt-3">
<!-- Links: Checkbox "Alles auswählen" -->
<div class="d-flex align-items-center">
<input type="checkbox" id="selectAll" />
<label for="selectAll" class="ms-1 mb-0">Alles auswählen</label>
</div>
<!-- Rechts: Items-per-page-Auswahl -->
<div class="d-flex align-items-center">
<label for="itemsPerPageSelect" class="me-2 mb-0">Bilder pro Seite:</label>
<select id="itemsPerPageSelect" class="form-select form-select-sm" style="width: auto;">
<option value="15" {% if items_per_page == 15 %}selected{% endif %}>15</option>
<option value="30" {% if items_per_page == 30 %}selected{% endif %}>30</option>
<option value="50" {% if items_per_page == 50 %}selected{% endif %}>50</option>
<option value="75" {% if items_per_page == 75 %}selected{% endif %}>75</option>
<option value="100" {% if items_per_page == 100 %}selected{% endif %}>100</option>
</select>
</div>
</div>
</div>
<!-- Bildgrid -->
<div class="row row-cols-3" id="imageGrid">
{% for log in logs %}
<div class="col mb-3">
<div class="card custom-bg">
<div class="card-body p-0 position-relative">
<img src="{{ log.output_file }}"
class="img-fluid image-thumbnail"
alt="Generiertes Bild"
data-id="{{ log.id }}"
data-filename="{{ log.output_file.split('/')[-1] }}"
data-format="{{ log.output_file.split('.')[-1] }}"
data-timestamp="{{ log.timestamp }}"
data-album="{{ log.album }}"
data-category="{{ log.category }}"
data-prompt="{{ log.prompt }}"
data-optimized_prompt="{{ log.optimized_prompt }}">
<input type="checkbox" class="form-check-input select-item position-absolute top-0 end-0 m-2">
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Paginierung -->
<div class="d-flex justify-content-center mt-4">
{% if page > 1 %}
<a class="btn btn-secondary me-2" href="?page={{ page - 1 }}&items_per_page={{ items_per_page }}{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_album %}&album={{ selected_album }}{% endif %}{% if selected_categories %}&category={{ selected_categories | join(',') }}{% endif %}">Vorherige Seite</a>
{% endif %}
{% if logs|length == items_per_page %}
<a class="btn btn-secondary" href="?page={{ page + 1 }}&items_per_page={{ items_per_page }}{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_album %}&album={{ selected_album }}{% endif %}{% if selected_categories %}&category={{ selected_categories | join(',') }}{% endif %}">Nächste Seite</a>
{% endif %}
</div>
<button id="scrollTopBtn" style="display: none; position: fixed; bottom: 20px; right: 20px; z-index: 99; border: none; background: transparent;">
<img src="/static/arrow-up1.png" alt="Nach oben" style="width: 50px; height: 50px;">
</button>
<!-- "Nach oben"-Button -->
<!-- <button id="scrollTopBtn" class="btn btn-primary" style="display: none; position: fixed; bottom: 20px; right: 20px; width: 100px; z-index: 99;">
Nach oben
</button> -->
<!-- Bild-Detail Modal -->
<div id="imageModal" class="modal fade" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageModalLabel">Bilddetails</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="image-container" style="cursor: pointer;">
<img id="modalImage" src="" class="img-fluid mb-3" alt="Bild">
</div>
<div class="image-details">
<p><strong>Dateiname:</strong> <span id="modalFilename"></span></p>
<p><strong>Bildformat:</strong> <span id="modalFormat"></span></p>
<p><strong>Datum:</strong> <span id="modalTimestamp"></span></p>
<p><strong>Album:</strong> <span id="modalAlbum"></span></p>
<p><strong>Kategorie:</strong> <span id="modalCategory"></span></p>
<p><strong>Eingabeaufforderung:</strong> <span id="modalPrompt"></span></p>
<p><strong>Optimierte Eingabeaufforderung:</strong> <span id="modalOptimizedPrompt"></span></p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="modalDownloadBtn">
<i class="fas fa-download"></i> Download
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<!-- Thumbnail-Galerie Modal -->
<div id="thumbGalleryModal" class="modal fade" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Thumbnail-Galerie</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="thumbGalleryContainer" class="d-flex flex-wrap justify-content-center">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<!-- Slideshow Modal -->
<div id="slideshowModal" class="modal fade" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Diashow</h5>
<div class="btn-group ms-auto me-2">
<button class="btn btn-primary btn-sm" id="playSlideshow">
<i class="fas fa-play"></i>
</button>
<button class="btn btn-primary btn-sm" id="pauseSlideshow" style="display: none;">
<i class="fas fa-pause"></i>
</button>
<button class="btn btn-primary btn-sm" id="fullscreenBtn">
<i class="fas fa-expand"></i>
</button>
<button class="btn btn-primary btn-sm" id="downloadCurrentSlide">
<i class="fas fa-download"></i>
</button>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="carouselExampleControls" class="carousel slide" data-bs-interval="false">
<div id="slideshowContainer" class="carousel-inner"></div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Vorherige</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Nächste</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal für Zuweisung zu Album/Kategorie -->
<div id="assignAlbumModal" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Zu Album hinzufügen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<select class="form-control" id="albumSelect">
{% for album in albums %}
<option value="{{ album[0] }}">{{ album[1] }}</option>
{% endfor %}
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" id="assignAlbumBtn">Hinzufügen</button>
</div>
</div>
</div>
</div>
<div id="assignCategoryModal" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Zu Kategorie hinzufügen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<select class="form-control" id="categorySelect" multiple>
{% for category in categories %}
<option value="{{ category[0] }}">{{ category[1] }}</option>
{% endfor %}
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" id="assignCategoryBtn">Hinzufügen</button>
</div>
</div>
</div>
</div>
<!-- CSS Styles -->
<style>
.image-container {
position: relative;
text-align: center;
max-height: 80vh;
overflow: auto;
}
.image-container img {
max-width: 100%;
height: auto;
transition: transform 0.2s;
}
.image-container img:hover {
transform: scale(1.02);
}
.thumb-container {
position: relative;
display: inline-block;
}
.download-thumb {
position: absolute;
bottom: 5px;
right: 5px;
opacity: 0;
transition: opacity 0.3s;
}
.thumb-container:hover .download-thumb {
opacity: 1;
}
.carousel-item img {
max-height: 80vh;
object-fit: contain;
}
#slideshowModal.fullscreen .modal-dialog {
max-width: 100%;
margin: 0;
height: 100vh;
}
#slideshowModal.fullscreen .modal-content {
height: 100%;
border: none;
border-radius: 0;
}
.carousel-control-prev,
.carousel-control-next {
width: 10%;
opacity: 0;
transition: opacity 0.3s;
}
.carousel:hover .carousel-control-prev,
.carousel:hover .carousel-control-next {
opacity: 0.5;
}
.modal-dialog {
max-width: 90vw;
margin: 1.75rem auto;
}
.image-details {
margin-top: 1rem;
padding: 1rem;
background-color: rgba(0,0,0,0.02);
border-radius: 4px;
}
.modal-footer {
justify-content: space-between;
}
.container-fluid.bg-light {
background-color: rgba(248, 249, 250, 0.8) !important; /* 50% Transparenz */
}
/* Für Geräte mit einer maximalen Breite von 768px (Tablets und kleiner) */
@media (max-width: 768px) {
#tollbarAll {
display: none;
}
#imageGrid {
display: grid;
grid-template-columns: 1fr; /* 1 Bild pro Reihe */
gap: 15px; /* Abstand zwischen Bildern */
}
/* Bildkarten auf volle Breite skalieren */
.card {
width: 100%; /* Volle Breite */
margin: 0 auto;
}
.card img {
width: 100%; /* Bild nimmt gesamte Breite der Karte ein */
height: auto;
}
}
</style>
<!-- {% block scripts %}
<script src="/static/script.js"></script>
{% endblock %} -->
<script>
document.addEventListener('DOMContentLoaded', function () {
// Hilfsfunktion für Einzelbild-Download
async function downloadSingleImage(filename) {
try {
const response = await fetch(`/flux-pics/${filename}`);
if (!response.ok) {
const errorText = await response.text();
console.error('Server Error:', errorText);
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);
} catch (error) {
console.error('Fehler beim Download:', error);
alert('Ein Fehler ist aufgetreten: ' + error.message);
}
}
// Funktion zur automatischen Anpassung des Layouts für mobile Geräte
function updateLayoutForMobile() {
const gridLayout = document.getElementById('gridLayout');
const imageGrid = document.getElementById('imageGrid');
if (window.innerWidth <= 768) {
gridLayout.value = 1; // Ein Bild pro Zeile auf mobilen Geräten
imageGrid.className = 'row row-cols-1';
} else {
const columns = parseInt(gridLayout.value);
imageGrid.className = `row row-cols-1 row-cols-md-${columns}`;
}
}
// Initiale Layout-Anpassung
updateLayoutForMobile();
// Event Listener für Fenstergrößenänderung (Responsive Verhalten)
window.addEventListener('resize', updateLayoutForMobile);
// Event Listener für Grid Layout
document.getElementById('gridLayout').addEventListener('change', function () {
const columns = parseInt(this.value);
const imageGrid = document.getElementById('imageGrid');
imageGrid.className = `row row-cols-1 row-cols-md-${columns}`;
});
// Funktion zur Öffnung des Bilddetails-Modals
function openImageModal(img) {
const modal = new bootstrap.Modal(document.getElementById('imageModal'));
const modalImg = document.getElementById('modalImage');
const filename = img.dataset.filename;
modalImg.src = img.src;
document.getElementById('modalFilename').textContent = filename;
document.getElementById('modalFormat').textContent = img.dataset.format;
document.getElementById('modalTimestamp').textContent = img.dataset.timestamp;
document.getElementById('modalAlbum').textContent = img.dataset.album;
document.getElementById('modalCategory').textContent = img.dataset.category;
document.getElementById('modalPrompt').textContent = img.dataset.prompt;
document.getElementById('modalOptimizedPrompt').textContent = img.dataset.optimized_prompt;
modal.show();
}
document.querySelectorAll('.image-thumbnail').forEach(function (img) {
img.addEventListener('click', function () {
openImageModal(this);
});
});
// Funktion für die "Alle auswählen"-Checkbox
const selectAllCheckbox = document.getElementById('selectAll');
const itemCheckboxes = document.querySelectorAll('.select-item');
selectAllCheckbox.addEventListener('change', function () {
itemCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
});
// Funktion zum Sammeln ausgewählter Bilder
function getSelectedImages() {
const selectedImages = [];
document.querySelectorAll('.select-item:checked').forEach(checkbox => {
const img = checkbox.closest('.card').querySelector('img');
if (img && img.dataset.filename) {
selectedImages.push(img.dataset.filename);
}
});
return selectedImages;
}
// Download der ausgewählten Bilder
document.getElementById('downloadSelected').addEventListener('click', async function () {
const selectedImages = getSelectedImages();
if (selectedImages.length === 0) {
alert('Keine Bilder ausgewählt.');
return;
}
let downloadType = 'single';
if (selectedImages.length > 1) {
const choice = confirm('Möchten Sie die Bilder als ZIP-Datei herunterladen? Klicken Sie "OK" für ZIP oder "Abbrechen" für Einzeldownloads.');
if (choice) {
downloadType = 'zip';
}
}
try {
if (downloadType === 'zip') {
const response = await fetch('/flux-pics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ selectedImages })
});
if (!response.ok) {
const errorText = await response.text();
console.error('Server Error:', errorText);
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);
} else {
for (const filename of selectedImages) {
await downloadSingleImage(filename);
}
}
alert('Download erfolgreich abgeschlossen.');
} catch (error) {
console.error('Fehler beim Downloaden:', error);
alert('Ein Fehler ist aufgetreten: ' + error.message);
}
});
// Automatisches Aktualisieren der Bilder pro Seite
const itemsPerPageSelect = document.getElementById('itemsPerPageSelect');
if (itemsPerPageSelect) {
itemsPerPageSelect.addEventListener('change', function () {
const form = document.getElementById('generateForm');
if (form) {
form.submit();
}
});
}
// "Nach oben"-Button
const scrollTopBtn = document.getElementById('scrollTopBtn');
window.addEventListener('scroll', function () {
scrollTopBtn.style.display = window.scrollY > 300 ? 'block' : 'none';
});
scrollTopBtn.addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
</script>
{% endblock %}