Remove all button
This commit is contained in:
@ -96,6 +96,38 @@ def delete_hero_photo():
|
|||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
return {"error": "File not found"}, 404
|
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/<section>/<path:filename>")
|
@app.route("/photos/<section>/<path:filename>")
|
||||||
def photos(section, filename):
|
def photos(section, filename):
|
||||||
"""Serve uploaded photos from disk."""
|
"""Serve uploaded photos from disk."""
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Photo WebUI</title>
|
<title>Photo WebUI</title>
|
||||||
|
|
||||||
<!-- Link to your CSS in the package -->
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -12,10 +10,10 @@
|
|||||||
<div class="nav-bar">
|
<div class="nav-bar">
|
||||||
<div class="content-inner nav">
|
<div class="content-inner nav">
|
||||||
<div class="nav-cta">
|
<div class="nav-cta">
|
||||||
<div class="arrow">
|
<div class="arrow">→</div>
|
||||||
→
|
<a class="button" href="#" target="_blank">
|
||||||
</div>
|
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
|
||||||
<a class="button" href="#" target="_blank"><span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span></a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" id="nav-check">
|
<input type="checkbox" id="nav-check">
|
||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
@ -23,7 +21,6 @@
|
|||||||
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-btn">
|
<div class="nav-btn">
|
||||||
<label for="nav-check">
|
<label for="nav-check">
|
||||||
<span></span>
|
<span></span>
|
||||||
@ -31,16 +28,13 @@
|
|||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<ul class="nav-list">
|
<ul class="nav-list">
|
||||||
<li class="nav-item appear2"><a href="#">Site info</a>
|
<li class="nav-item appear2"><a href="#">Site info</a>
|
||||||
<li class="nav-item appear2"><a href="#qui-suis-je">Theme info</a>
|
<li class="nav-item appear2"><a href="#qui-suis-je">Theme info</a>
|
||||||
<li class="nav-item appear2"><a href="#mariages">Gallery</a>
|
<li class="nav-item appear2"><a href="#mariages">Gallery</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Toast container for notifications -->
|
<!-- Toast container for notifications -->
|
||||||
@ -53,9 +47,12 @@
|
|||||||
<div class="upload-section">
|
<div class="upload-section">
|
||||||
<h2>Title Carrousel</h2>
|
<h2>Title Carrousel</h2>
|
||||||
<p> Select photos to display in the Title Carrousel</p>
|
<p> Select photos to display in the Title Carrousel</p>
|
||||||
<label for="upload-hero" class="custom-upload-btn">
|
<div class="upload-actions-row">
|
||||||
|
<label for="upload-hero" class="up-btn">
|
||||||
📸 Upload photos
|
📸 Upload photos
|
||||||
</label>
|
</label>
|
||||||
|
<button id="remove-all-hero" class="up-btn">🗑 Remove All</button>
|
||||||
|
</div>
|
||||||
<input type="file" id="upload-hero" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
<input type="file" id="upload-hero" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||||
<div id="hero"></div>
|
<div id="hero"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -64,14 +61,16 @@
|
|||||||
<div class="upload-section">
|
<div class="upload-section">
|
||||||
<h2>Gallery</h2>
|
<h2>Gallery</h2>
|
||||||
<p> Select and tags photos to display in the Gallery</p>
|
<p> Select and tags photos to display in the Gallery</p>
|
||||||
<label for="upload-gallery" class="custom-upload-btn">
|
<div class="upload-actions-row">
|
||||||
|
<label for="upload-gallery" class="up-btn">
|
||||||
📸 Upload photos
|
📸 Upload photos
|
||||||
</label>
|
</label>
|
||||||
|
<button id="remove-all-gallery" class="up-btn">🗑 Remove All</button>
|
||||||
|
</div>
|
||||||
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||||
<div id="gallery"></div>
|
<div id="gallery"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- JS files for rendering, uploading, and actions -->
|
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,6 +53,12 @@ function renderGallery() {
|
|||||||
|
|
||||||
renderTags(i, img.tags || []);
|
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 ---
|
// --- Render tags for a single image ---
|
||||||
@ -60,11 +66,9 @@ function renderTags(imgIndex, tags) {
|
|||||||
const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
|
const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
|
||||||
const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
|
const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
|
||||||
|
|
||||||
// vider
|
|
||||||
tagsDisplay.innerHTML = '';
|
tagsDisplay.innerHTML = '';
|
||||||
inputContainer.innerHTML = '';
|
inputContainer.innerHTML = '';
|
||||||
|
|
||||||
// --- rendre les tags (en haut) ---
|
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.className = 'tag';
|
span.className = 'tag';
|
||||||
@ -83,13 +87,11 @@ function renderTags(imgIndex, tags) {
|
|||||||
tagsDisplay.appendChild(span);
|
tagsDisplay.appendChild(span);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- input (en bas) ---
|
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
input.placeholder = 'Add tag...';
|
input.placeholder = 'Add tag...';
|
||||||
inputContainer.appendChild(input);
|
inputContainer.appendChild(input);
|
||||||
|
|
||||||
// suggestion box
|
|
||||||
const suggestionBox = document.createElement('ul');
|
const suggestionBox = document.createElement('ul');
|
||||||
suggestionBox.className = 'suggestions';
|
suggestionBox.className = 'suggestions';
|
||||||
inputContainer.appendChild(suggestionBox);
|
inputContainer.appendChild(suggestionBox);
|
||||||
@ -210,11 +212,17 @@ function renderHero() {
|
|||||||
<div class="flex-item flex-end">
|
<div class="flex-item flex-end">
|
||||||
<button onclick="showDeleteModal('hero', ${i})">🗑 Delete</button>
|
<button onclick="showDeleteModal('hero', ${i})">🗑 Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(div);
|
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 ---
|
// --- Save gallery to server ---
|
||||||
async function saveGallery() {
|
async function saveGallery() {
|
||||||
@ -273,11 +281,19 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, 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 ---
|
// --- Show delete confirmation modal ---
|
||||||
function showDeleteModal(type, index) {
|
function showDeleteModal(type, index = null) {
|
||||||
pendingDelete = { type, index };
|
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';
|
document.getElementById('delete-modal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,18 +310,14 @@ async function confirmDelete() {
|
|||||||
await actuallyDeleteGalleryImage(pendingDelete.index);
|
await actuallyDeleteGalleryImage(pendingDelete.index);
|
||||||
} else if (pendingDelete.type === 'hero') {
|
} else if (pendingDelete.type === 'hero') {
|
||||||
await actuallyDeleteHeroImage(pendingDelete.index);
|
await actuallyDeleteHeroImage(pendingDelete.index);
|
||||||
|
} else if (pendingDelete.type === 'gallery-all') {
|
||||||
|
await actuallyDeleteAllGalleryImages();
|
||||||
|
} else if (pendingDelete.type === 'hero-all') {
|
||||||
|
await actuallyDeleteAllHeroImages();
|
||||||
}
|
}
|
||||||
hideDeleteModal();
|
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 ---
|
// --- Actual delete functions ---
|
||||||
async function actuallyDeleteGalleryImage(index) {
|
async function actuallyDeleteGalleryImage(index) {
|
||||||
const img = galleryImages[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.addEventListener('DOMContentLoaded', () => {
|
||||||
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
|
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
|
||||||
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
|
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
|
||||||
document.getElementById('delete-modal-confirm').onclick = confirmDelete;
|
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 ---
|
// --- Initialize ---
|
||||||
loadData();
|
loadData();
|
@ -39,7 +39,6 @@ h1, h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.upload-section label {
|
.upload-section label {
|
||||||
margin-right: 20px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ h1, h2 {
|
|||||||
/* Toast notifications */
|
/* Toast notifications */
|
||||||
#toast-container {
|
#toast-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1rem;
|
bottom: 1rem;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -120,12 +119,13 @@ h1, h2 {
|
|||||||
background: rgba(0,0,0,0.85);
|
background: rgba(0,0,0,0.85);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 30px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-20px);
|
transform: translateY(20px);
|
||||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.show {
|
.toast.show {
|
||||||
@ -133,7 +133,7 @@ h1, h2 {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.success { background-color: #28a745; }
|
.toast.success { background-color: #28a7468c; }
|
||||||
.toast.error { background-color: #dc3545; }
|
.toast.error { background-color: #dc3545; }
|
||||||
|
|
||||||
/* Tags */
|
/* Tags */
|
||||||
@ -351,7 +351,7 @@ h1, h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Custom upload buttons */
|
/* Custom upload buttons */
|
||||||
.custom-upload-btn {
|
.up-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #09A0C1;
|
background: #09A0C1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -363,9 +363,11 @@ h1, h2 {
|
|||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.25);
|
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;
|
background: #55c3ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,3 +428,31 @@ h1, h2 {
|
|||||||
.modal-btn.danger:hover {
|
.modal-btn.danger:hover {
|
||||||
background: #d32f2f;
|
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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user