Add extra prompts, endless generation, random character default, and small fixes
- Add extra positive/negative prompt textareas to all 9 detail pages with session persistence - Add Endless generation button to all detail pages (continuous preview generation until stopped) - Default character selector to "Random Character" on all secondary detail pages - Fix queue clear endpoint (remove spurious auth check) - Refactor app.py into routes/ and services/ modules - Update CLAUDE.md with new architecture documentation - Various data file updates and cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
<select name="sort" class="form-select form-select-sm" onchange="this.form.submit()">
|
||||
<option value="newest" {% if sort == 'newest' %}selected{% endif %}>Newest first</option>
|
||||
<option value="oldest" {% if sort == 'oldest' %}selected{% endif %}>Oldest first</option>
|
||||
<option value="random" {% if sort == 'random' %}selected{% endif %}>🎲 Random</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -76,6 +77,133 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Quick Resource Filters -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<span class="text-muted small me-2">Quick filters:</span>
|
||||
<a href="{{ url_for('gallery', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'all' %}bg-secondary{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
All
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='characters', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'characters' %}bg-primary{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Characters
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='looks', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'looks' %}bg-primary{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Looks
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='outfits', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'outfits' %}bg-success{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Outfits
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='actions', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'actions' %}bg-danger{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Actions
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='scenes', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'scenes' %}bg-info{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Scenes
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='styles', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'styles' %}bg-warning{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Styles
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='detailers', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'detailers' %}bg-secondary{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Detailers
|
||||
</a>
|
||||
<a href="{{ url_for('gallery', category='checkpoints', sort=sort, per_page=per_page) }}"
|
||||
class="badge {% if category == 'checkpoints' %}bg-dark{% else %}bg-light text-dark{% endif %} text-decoration-none px-3 py-2">
|
||||
Checkpoints
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery View Mode Controls -->
|
||||
<div class="gallery-controls" id="gallery-controls">
|
||||
<div class="gallery-view-modes">
|
||||
<button class="gallery-view-btn active" data-mode="grid" title="Info view">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="14" width="7" height="7"></rect>
|
||||
<rect x="3" y="14" width="7" height="7"></rect>
|
||||
</svg>
|
||||
Info
|
||||
</button>
|
||||
<button class="gallery-view-btn" data-mode="mosaic" title="Gallery view">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="6" height="6"></rect>
|
||||
<rect x="11" y="3" width="6" height="6"></rect>
|
||||
<rect x="3" y="11" width="6" height="6"></rect>
|
||||
<rect x="11" y="11" width="6" height="6"></rect>
|
||||
</svg>
|
||||
Gallery
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gallery-selection-controls">
|
||||
<button class="gallery-view-btn" id="selection-mode-toggle" title="Multi-select mode">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
|
||||
<path d="M9 11l3 3 6-6"></path>
|
||||
</svg>
|
||||
Select
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger d-none" id="delete-selected-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="me-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
Delete Selected (<span id="selected-count">0</span>)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gallery-slideshow-dropdown">
|
||||
<button class="gallery-slideshow-btn" id="slideshow-menu-btn" aria-haspopup="true" aria-expanded="false">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||
</svg>
|
||||
Slideshow ▾
|
||||
</button>
|
||||
<div class="gallery-slideshow-menu" id="slideshow-menu">
|
||||
<div class="gallery-slideshow-menu-item" data-mode="cinema">
|
||||
<span>🎬</span>
|
||||
<div>
|
||||
<div class="fw-medium">Cinema</div>
|
||||
<small class="text-muted">Ambient glow & Ken Burns</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-slideshow-menu-item" data-mode="classic">
|
||||
<span>⏯</span>
|
||||
<div>
|
||||
<div class="fw-medium">Classic</div>
|
||||
<small class="text-muted">Clean transitions</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-slideshow-menu-item" data-mode="showcase">
|
||||
<span>🖼</span>
|
||||
<div>
|
||||
<div class="fw-medium">Showcase</div>
|
||||
<small class="text-muted">Digital frame style</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-slideshow-menu-item" data-mode="ambient">
|
||||
<span>🌌</span>
|
||||
<div>
|
||||
<div class="fw-medium">Ambient</div>
|
||||
<small class="text-muted">Screensaver with particles</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ms-auto small text-muted d-none d-md-block">
|
||||
<kbd>G</kbd> Info · <kbd>A</kbd> Gallery · <kbd>S</kbd> Slideshow
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Showing X–Y of N -->
|
||||
{% if total > 0 %}
|
||||
<p class="text-muted small mb-2">
|
||||
@@ -101,19 +229,47 @@
|
||||
data-slug="{{ img.slug }}"
|
||||
data-name="{{ img.item_name }}"
|
||||
data-path="{{ img.path }}"
|
||||
data-src="{{ url_for('static', filename='uploads/' + img.path) }}"
|
||||
onclick="openLightbox(this)">
|
||||
data-src="{{ url_for('static', filename='uploads/' + img.path) }}">
|
||||
<input type="checkbox" class="gallery-card-checkbox d-none" data-path="{{ img.path }}" onclick="event.stopPropagation()">
|
||||
<img src="{{ url_for('static', filename='uploads/' + img.path) }}"
|
||||
alt="{{ img.item_name }}"
|
||||
loading="lazy">
|
||||
<span class="cat-badge badge bg-{{ cat_colors.get(img.category, 'secondary') }}">
|
||||
{{ img.category[:-1] if img.category.endswith('s') else img.category }}
|
||||
</span>
|
||||
|
||||
<!-- Info View Additional Metadata -->
|
||||
<div class="info-meta">
|
||||
{% if img.meta %}
|
||||
{% if img.meta.checkpoint %}
|
||||
<span class="badge bg-dark mb-1 d-inline-block text-truncate w-100" style="font-size: 0.65rem;" title="{{ img.meta.checkpoint }}">{{ img.meta.checkpoint.split('/')[-1] }}</span>
|
||||
{% endif %}
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
{% for lora in img.meta.loras %}
|
||||
{% set lora_name = lora.name.split('/')[-1].replace('.safetensors', '') %}
|
||||
{% set subfolder = lora.name.split('/')[1] if lora.name.startswith('Illustrious/') else '' %}
|
||||
|
||||
{% if subfolder == 'Characters' %}{% set color = 'primary' %}
|
||||
{% elif subfolder == 'Looks' %}{% set color = 'primary' %}
|
||||
{% elif subfolder == 'Clothing' %}{% set color = 'success' %}
|
||||
{% elif subfolder == 'Actions' %}{% set color = 'danger' %}
|
||||
{% elif subfolder == 'Scenes' %}{% set color = 'info' %}
|
||||
{% elif subfolder == 'Styles' %}{% set color = 'warning' %}
|
||||
{% elif subfolder == 'Detailers' %}{% set color = 'secondary' %}
|
||||
{% else %}{% set color = 'light text-dark' %}
|
||||
{% endif %}
|
||||
|
||||
<span class="badge bg-{{ color }}" style="font-size: 0.6rem;" title="{{ lora.name }}">{{ lora_name }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="overlay">
|
||||
<div class="text-white small fw-semibold text-truncate">{{ img.item_name }}</div>
|
||||
<div class="d-flex gap-1 mt-1">
|
||||
<button class="btn btn-sm btn-light py-0 px-2 flex-grow-1"
|
||||
onclick="event.stopPropagation(); showPrompt({{ img.path | tojson }}, {{ img.item_name | tojson }}, {{ img.category | tojson }}, {{ img.slug | tojson }})">
|
||||
onclick='event.stopPropagation(); showPrompt({{ img.path | tojson }}, {{ img.item_name | tojson }}, {{ img.category | tojson }}, {{ img.slug | tojson }})'>
|
||||
Prompt
|
||||
</button>
|
||||
{% if img.category == 'characters' %}
|
||||
@@ -131,7 +287,7 @@
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-outline-danger py-0 px-2"
|
||||
title="Delete"
|
||||
onclick="event.stopPropagation(); openDeleteModal({{ img.path | tojson }}, {{ img.item_name | tojson }})">
|
||||
onclick='event.stopPropagation(); openDeleteModal({{ img.path | tojson }}, {{ img.item_name | tojson }})'>
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
@@ -176,19 +332,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Lightbox -->
|
||||
<div id="lightbox" onclick="closeLightbox()">
|
||||
<span id="lightbox-close" onclick="closeLightbox()">×</span>
|
||||
<div id="lightbox-img-wrap" onclick="event.stopPropagation()">
|
||||
<img id="lightbox-img" src="" alt="" onclick="openFullSize()">
|
||||
<div id="lightbox-meta"></div>
|
||||
<div id="lightbox-hint">Click image to open full size · Esc to close</div>
|
||||
</div>
|
||||
<div id="lightbox-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-sm btn-light" onclick="lightboxShowPrompt()">View Prompt</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="openDeleteModal(_lightboxPath, _lightboxName); closeLightbox()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompt modal -->
|
||||
<div class="modal fade" id="promptModal" tabindex="-1">
|
||||
@@ -267,38 +410,6 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// ---- Lightbox state ----
|
||||
let _lightboxSrc = '';
|
||||
let _lightboxPath = '';
|
||||
let _lightboxCategory = '';
|
||||
let _lightboxSlug = '';
|
||||
let _lightboxName = '';
|
||||
|
||||
function openLightbox(card) {
|
||||
_lightboxSrc = card.dataset.src;
|
||||
_lightboxPath = card.dataset.path;
|
||||
_lightboxCategory = card.dataset.category;
|
||||
_lightboxSlug = card.dataset.slug;
|
||||
_lightboxName = card.dataset.name;
|
||||
|
||||
document.getElementById('lightbox-img').src = _lightboxSrc;
|
||||
document.getElementById('lightbox-meta').textContent = _lightboxName + ' · ' + _lightboxCategory;
|
||||
document.getElementById('lightbox').classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
function openFullSize() {
|
||||
window.open(_lightboxSrc, '_blank');
|
||||
}
|
||||
function lightboxShowPrompt() {
|
||||
closeLightbox();
|
||||
showPrompt(_lightboxPath, _lightboxName, _lightboxCategory, _lightboxSlug);
|
||||
}
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLightbox(); });
|
||||
|
||||
// ---- Prompt modal ----
|
||||
let promptModal;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -428,5 +539,240 @@ async function confirmDelete() {
|
||||
alert('Delete failed: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GAZE Gallery Enhancement - View Mode Controls
|
||||
// ============================================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize GalleryCore if available
|
||||
if (window.GalleryCore) {
|
||||
GalleryCore.init('.gallery-grid');
|
||||
console.log('GalleryCore initialized');
|
||||
}
|
||||
|
||||
// View mode buttons
|
||||
const viewButtons = document.querySelectorAll('.gallery-view-btn');
|
||||
viewButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const mode = btn.dataset.mode;
|
||||
|
||||
// Update active state
|
||||
viewButtons.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// Apply layout
|
||||
if (window.GalleryCore) {
|
||||
GalleryCore.setLayout(mode);
|
||||
}
|
||||
|
||||
// Save preference
|
||||
localStorage.setItem('gaze-gallery-view', mode);
|
||||
});
|
||||
});
|
||||
|
||||
// Restore saved view mode
|
||||
const savedMode = localStorage.getItem('gaze-gallery-view');
|
||||
if (savedMode) {
|
||||
const btn = document.querySelector(`.gallery-view-btn[data-mode="${savedMode}"]`);
|
||||
if (btn) {
|
||||
viewButtons.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Slideshow dropdown
|
||||
const slideshowBtn = document.getElementById('slideshow-menu-btn');
|
||||
const slideshowMenu = document.getElementById('slideshow-menu');
|
||||
|
||||
if (slideshowBtn && slideshowMenu) {
|
||||
slideshowBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
slideshowMenu.classList.toggle('open');
|
||||
slideshowBtn.setAttribute('aria-expanded', slideshowMenu.classList.contains('open'));
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!slideshowMenu.contains(e.target) && e.target !== slideshowBtn) {
|
||||
slideshowMenu.classList.remove('open');
|
||||
slideshowBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// Slideshow mode selection
|
||||
slideshowMenu.querySelectorAll('.gallery-slideshow-menu-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const mode = item.dataset.mode;
|
||||
slideshowMenu.classList.remove('open');
|
||||
slideshowBtn.setAttribute('aria-expanded', 'false');
|
||||
|
||||
// Start slideshow
|
||||
if (window.GalleryCore) {
|
||||
GalleryCore.startSlideshow(mode);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Multi-Selection Mode
|
||||
// ============================================================
|
||||
let selectionMode = false;
|
||||
const selectedPaths = new Set();
|
||||
|
||||
const selectionToggleBtn = document.getElementById('selection-mode-toggle');
|
||||
const deleteSelectedBtn = document.getElementById('delete-selected-btn');
|
||||
const selectedCountEl = document.getElementById('selected-count');
|
||||
const allCheckboxes = document.querySelectorAll('.gallery-card-checkbox');
|
||||
const galleryGrid = document.querySelector('.gallery-grid');
|
||||
|
||||
// Toggle selection mode
|
||||
selectionToggleBtn.addEventListener('click', () => {
|
||||
selectionMode = !selectionMode;
|
||||
|
||||
if (selectionMode) {
|
||||
// Enter selection mode
|
||||
selectionToggleBtn.classList.add('active');
|
||||
galleryGrid.classList.add('selection-mode');
|
||||
allCheckboxes.forEach(cb => cb.classList.remove('d-none'));
|
||||
|
||||
// Modify card click behavior
|
||||
document.querySelectorAll('.gallery-card').forEach(card => {
|
||||
card.style.cursor = 'pointer';
|
||||
card.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// In selection mode, clicking ANYWHERE on the card toggles checkbox
|
||||
const checkbox = this.querySelector('.gallery-card-checkbox');
|
||||
checkbox.checked = !checkbox.checked;
|
||||
handleCheckboxChange(checkbox);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Exit selection mode
|
||||
selectionToggleBtn.classList.remove('active');
|
||||
galleryGrid.classList.remove('selection-mode');
|
||||
allCheckboxes.forEach(cb => {
|
||||
cb.classList.add('d-none');
|
||||
cb.checked = false;
|
||||
});
|
||||
deleteSelectedBtn.classList.add('d-none');
|
||||
selectedPaths.clear();
|
||||
|
||||
// Restore original click behavior
|
||||
document.querySelectorAll('.gallery-card').forEach(card => {
|
||||
card.style.cursor = '';
|
||||
card.onclick = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle checkbox changes
|
||||
function handleCheckboxChange(checkbox) {
|
||||
const path = checkbox.dataset.path;
|
||||
|
||||
if (checkbox.checked) {
|
||||
selectedPaths.add(path);
|
||||
checkbox.closest('.gallery-card').classList.add('selected');
|
||||
} else {
|
||||
selectedPaths.delete(path);
|
||||
checkbox.closest('.gallery-card').classList.remove('selected');
|
||||
}
|
||||
|
||||
// Update UI
|
||||
selectedCountEl.textContent = selectedPaths.size;
|
||||
if (selectedPaths.size > 0) {
|
||||
deleteSelectedBtn.classList.remove('d-none');
|
||||
} else {
|
||||
deleteSelectedBtn.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
// Attach checkbox listeners
|
||||
allCheckboxes.forEach(cb => {
|
||||
cb.addEventListener('change', () => handleCheckboxChange(cb));
|
||||
});
|
||||
|
||||
// Delete selected images
|
||||
deleteSelectedBtn.addEventListener('click', async () => {
|
||||
if (selectedPaths.size === 0) return;
|
||||
|
||||
const count = selectedPaths.size;
|
||||
if (!confirm(`Delete ${count} selected image${count !== 1 ? 's' : ''}? This cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
deleteSelectedBtn.disabled = true;
|
||||
deleteSelectedBtn.innerHTML = `<span class="spinner-border spinner-border-sm me-1"></span> Deleting...`;
|
||||
|
||||
// Delete all selected images
|
||||
const pathsToDelete = Array.from(selectedPaths);
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const path of pathsToDelete) {
|
||||
try {
|
||||
const res = await fetch('/gallery/delete', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({path}),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'ok') {
|
||||
// Remove card from DOM
|
||||
const card = document.querySelector(`.gallery-card[data-path="${CSS.escape(path)}"]`);
|
||||
if (card) card.remove();
|
||||
selectedPaths.delete(path);
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to delete ${path}:`, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update count in header
|
||||
const countEl = document.querySelector('h4 .text-muted');
|
||||
if (countEl) {
|
||||
const m = countEl.textContent.match(/(\d+)/);
|
||||
if (m) {
|
||||
const n = parseInt(m[1]) - successCount;
|
||||
countEl.textContent = ` ${n} image${n !== 1 ? 's' : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset button state
|
||||
deleteSelectedBtn.disabled = false;
|
||||
deleteSelectedBtn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="me-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
Delete Selected (<span id="selected-count">0</span>)
|
||||
`;
|
||||
|
||||
// Update selected count reference
|
||||
selectedCountEl = document.getElementById('selected-count');
|
||||
selectedCountEl.textContent = selectedPaths.size;
|
||||
|
||||
if (selectedPaths.size === 0) {
|
||||
deleteSelectedBtn.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Show result message
|
||||
if (successCount > 0) {
|
||||
window.location.reload();
|
||||
} else if (failCount > 0) {
|
||||
alert(`Failed to delete ${failCount} image${failCount !== 1 ? 's' : ''}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Load Gallery Enhancement Script -->
|
||||
<script src="{{ url_for('static', filename='js/gallery/gallery-core.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user