diff --git a/src/py/webui/webui.py b/src/py/webui/webui.py index 35ddae5..ea0bcb6 100644 --- a/src/py/webui/webui.py +++ b/src/py/webui/webui.py @@ -96,6 +96,38 @@ def delete_hero_photo(): return {"status": "ok"} return {"error": "File not found"}, 404 +@app.route("/api/gallery/delete_all", methods=["POST"]) +def delete_all_gallery_photos(): + """Delete all gallery photos from disk and YAML.""" + gallery_dir = PHOTOS_DIR / "gallery" + deleted = 0 + # Remove all files in gallery folder + for file in gallery_dir.glob("*"): + if file.is_file(): + file.unlink() + deleted += 1 + # Clear YAML gallery images + data = load_yaml(GALLERY_YAML) + data["gallery"]["images"] = [] + save_yaml(data, GALLERY_YAML) + return jsonify({"status": "ok", "deleted": deleted}) + +@app.route("/api/hero/delete_all", methods=["POST"]) +def delete_all_hero_photos(): + """Delete all hero photos from disk and YAML.""" + hero_dir = PHOTOS_DIR / "hero" + deleted = 0 + # Remove all files in hero folder + for file in hero_dir.glob("*"): + if file.is_file(): + file.unlink() + deleted += 1 + # Clear YAML hero images + data = load_yaml(GALLERY_YAML) + data["hero"]["images"] = [] + save_yaml(data, GALLERY_YAML) + return jsonify({"status": "ok", "deleted": deleted}) + @app.route("/photos/
/") def photos(section, filename): """Serve uploaded photos from disk.""" diff --git a/src/webui/index.html b/src/webui/index.html index e7f2406..1c0d7f4 100644 --- a/src/webui/index.html +++ b/src/webui/index.html @@ -3,46 +3,40 @@ Photo WebUI - - - +
@@ -53,9 +47,12 @@

Title Carrousel

Select photos to display in the Title Carrousel

- +
+ + +
@@ -64,14 +61,16 @@

Gallery

Select and tags photos to display in the Gallery

- +
+ + +
-
@@ -88,4 +87,4 @@ - + \ No newline at end of file diff --git a/src/webui/js/main.js b/src/webui/js/main.js index bb23d95..005aaf2 100644 --- a/src/webui/js/main.js +++ b/src/webui/js/main.js @@ -43,7 +43,7 @@ function renderGallery() {
-
+
@@ -53,6 +53,12 @@ function renderGallery() { renderTags(i, img.tags || []); }); + + // Show/hide Remove All button + const removeAllBtn = document.getElementById('remove-all-gallery'); + if (removeAllBtn) { + removeAllBtn.style.display = galleryImages.length > 0 ? 'inline-block' : 'none'; + } } // --- Render tags for a single image --- @@ -60,11 +66,9 @@ function renderTags(imgIndex, tags) { const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`); const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`); - // vider tagsDisplay.innerHTML = ''; inputContainer.innerHTML = ''; - // --- rendre les tags (en haut) --- tags.forEach(tag => { const span = document.createElement('span'); span.className = 'tag'; @@ -83,13 +87,11 @@ function renderTags(imgIndex, tags) { tagsDisplay.appendChild(span); }); - // --- input (en bas) --- const input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Add tag...'; inputContainer.appendChild(input); - // suggestion box const suggestionBox = document.createElement('ul'); suggestionBox.className = 'suggestions'; inputContainer.appendChild(suggestionBox); @@ -208,13 +210,19 @@ function renderHero() {
- + +
`; container.appendChild(div); }); -} + // Show/hide Remove All button + const removeAllBtn = document.getElementById('remove-all-hero'); + if (removeAllBtn) { + removeAllBtn.style.display = heroImages.length > 0 ? 'inline-block' : 'none'; + } +} // --- Save gallery to server --- async function saveGallery() { @@ -273,11 +281,19 @@ function showToast(message, type = "success", duration = 3000) { }, duration); } -let pendingDelete = null; // { type: 'gallery'|'hero', index: number } +let pendingDelete = null; // { type: 'gallery'|'hero'|'gallery-all'|'hero-all', index: number|null } // --- Show delete confirmation modal --- -function showDeleteModal(type, index) { +function showDeleteModal(type, index = null) { pendingDelete = { type, index }; + const modalText = document.getElementById('delete-modal-text'); + if (type === 'gallery-all') { + modalText.textContent = "Are you sure you want to delete ALL gallery images?"; + } else if (type === 'hero-all') { + modalText.textContent = "Are you sure you want to delete ALL hero images?"; + } else { + modalText.textContent = "Are you sure you want to delete this image?"; + } document.getElementById('delete-modal').style.display = 'flex'; } @@ -294,18 +310,14 @@ async function confirmDelete() { await actuallyDeleteGalleryImage(pendingDelete.index); } else if (pendingDelete.type === 'hero') { await actuallyDeleteHeroImage(pendingDelete.index); + } else if (pendingDelete.type === 'gallery-all') { + await actuallyDeleteAllGalleryImages(); + } else if (pendingDelete.type === 'hero-all') { + await actuallyDeleteAllHeroImages(); } hideDeleteModal(); } - -// --- Modal event listeners --- -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('delete-modal-close').onclick = hideDeleteModal; - document.getElementById('delete-modal-cancel').onclick = hideDeleteModal; - document.getElementById('delete-modal-confirm').onclick = confirmDelete; -}); - // --- Actual delete functions --- async function actuallyDeleteGalleryImage(index) { const img = galleryImages[index]; @@ -349,14 +361,51 @@ async function actuallyDeleteHeroImage(index) { } } -// --- Modal event listeners --- +// --- Bulk delete functions --- +async function actuallyDeleteAllGalleryImages() { + try { + const res = await fetch('/api/gallery/delete_all', { method: 'POST' }); + const data = await res.json(); + if (res.ok) { + galleryImages = []; + renderGallery(); + await saveGallery(); + showToast("✅ All gallery images removed!", "success"); + } else showToast("Error: " + data.error, "error"); + } catch(err) { + console.error(err); + showToast("Server error!", "error"); + } +} + +async function actuallyDeleteAllHeroImages() { + try { + const res = await fetch('/api/hero/delete_all', { method: 'POST' }); + const data = await res.json(); + if (res.ok) { + heroImages = []; + renderHero(); + await saveHero(); + showToast("✅ All hero images removed!", "success"); + } else showToast("Error: " + data.error, "error"); + } catch(err) { + console.error(err); + showToast("Server error!", "error"); + } +} + +// --- Modal event listeners and bulk delete buttons --- document.addEventListener('DOMContentLoaded', () => { document.getElementById('delete-modal-close').onclick = hideDeleteModal; document.getElementById('delete-modal-cancel').onclick = hideDeleteModal; document.getElementById('delete-modal-confirm').onclick = confirmDelete; + + // Bulk delete buttons + const removeAllGalleryBtn = document.getElementById('remove-all-gallery'); + const removeAllHeroBtn = document.getElementById('remove-all-hero'); + if (removeAllGalleryBtn) removeAllGalleryBtn.onclick = () => showDeleteModal('gallery-all'); + if (removeAllHeroBtn) removeAllHeroBtn.onclick = () => showDeleteModal('hero-all'); }); - - // --- Initialize --- -loadData(); +loadData(); \ No newline at end of file diff --git a/src/webui/style/style.css b/src/webui/style/style.css index 417b64b..127197f 100644 --- a/src/webui/style/style.css +++ b/src/webui/style/style.css @@ -39,7 +39,6 @@ h1, h2 { } .upload-section label { - margin-right: 20px; cursor: pointer; } @@ -108,7 +107,7 @@ h1, h2 { /* Toast notifications */ #toast-container { position: fixed; - top: 1rem; + bottom: 1rem; right: 1rem; display: flex; flex-direction: column; @@ -120,12 +119,13 @@ h1, h2 { background: rgba(0,0,0,0.85); color: white; padding: 0.75rem 1.25rem; - border-radius: 0.5rem; + border-radius: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); opacity: 0; - transform: translateY(-20px); + transform: translateY(20px); transition: opacity 0.5s ease, transform 0.5s ease; pointer-events: none; + backdrop-filter: blur(20px); } .toast.show { @@ -133,7 +133,7 @@ h1, h2 { transform: translateY(0); } -.toast.success { background-color: #28a745; } +.toast.success { background-color: #28a7468c; } .toast.error { background-color: #dc3545; } /* Tags */ @@ -351,7 +351,7 @@ h1, h2 { } /* Custom upload buttons */ -.custom-upload-btn { +.up-btn { display: inline-block; background: #09A0C1; color: #fff; @@ -363,9 +363,11 @@ h1, h2 { transition: all 0.1s ease; user-select: none; box-shadow: 0 4px 10px rgba(0,0,0,0.25); + font-size: 14px; + border: none; } -.custom-upload-btn:hover { +.up-btn:hover { background: #55c3ec; } @@ -425,4 +427,32 @@ h1, h2 { } .modal-btn.danger:hover { background: #d32f2f; +} + +/* Add this for horizontal alignment of upload and remove-all buttons */ +.upload-actions-row { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 10px; +} + +/* Style for Remove All buttons to match upload button look */ + + +#remove-all-hero, #remove-all-gallery { + background: rgb(121, 26, 19); +} +#remove-all-gallery:hover, +#remove-all-hero:hover { + background: #d32f2f; +} + +/* Responsive: stack buttons vertically on small screens */ +@media (max-width: 500px) { + .upload-actions-row { + flex-direction: column; + align-items: stretch; + gap: 8px; + } } \ No newline at end of file