vumichien's picture
change view image
cd441fa
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Uploader</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon/logo-svg.png">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--accent-color: #4cc9f0;
--success-color: #22cc88;
--light-bg: #f8f9fa;
--dark-text: #212529;
--card-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
--hover-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
body {
background-color: #f5f7fa;
color: var(--dark-text);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
padding-bottom: 40px;
}
.navbar {
background-color: white;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);
padding: 15px 0;
margin-bottom: 30px;
}
.navbar-brand {
font-weight: 600;
color: var(--primary-color);
display: flex;
align-items: center;
gap: 10px;
}
.navbar-brand i {
font-size: 1.5em;
}
.container {
max-width: 1200px;
}
.card {
border: none;
border-radius: 12px;
box-shadow: var(--card-shadow);
transition: all 0.3s ease;
}
.section-title {
margin-bottom: 20px;
font-weight: 600;
color: var(--dark-text);
border-left: 4px solid var(--primary-color);
padding-left: 12px;
}
.upload-container {
background-color: white;
padding: 25px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: var(--card-shadow);
}
.gallery-container {
background-color: white;
padding: 25px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: var(--card-shadow);
}
.search-container {
background-color: #f5f7fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.image-card {
margin-bottom: 25px;
border-radius: 12px;
overflow: hidden;
position: relative;
cursor: pointer;
transition: all 0.3s ease;
}
.image-card:hover {
transform: translateY(-5px);
box-shadow: var(--hover-shadow);
}
.image-preview {
max-height: 200px;
object-fit: cover;
width: 100%;
height: 200px;
}
.card-body {
padding: 20px;
}
.card-title {
font-weight: 600;
margin-bottom: 15px;
}
.hidden {
display: none;
}
#uploadProgress {
margin-top: 15px;
height: 10px;
border-radius: 5px;
}
.progress-bar {
background-color: var(--primary-color);
}
.form-control, .form-select {
border-radius: 8px;
padding: 10px 15px;
border: 1px solid #e0e0e0;
}
.form-control:focus, .form-select:focus {
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15);
border-color: var(--primary-color);
}
.btn {
border-radius: 8px;
padding: 10px 20px;
font-weight: 500;
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover, .btn-primary:focus {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
}
.btn-outline-primary {
color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-outline-primary:hover, .btn-outline-primary:focus,
.btn-check:checked + .btn-outline-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.btn-danger {
background-color: #e63946;
border-color: #e63946;
}
.btn-danger:hover, .btn-danger:focus {
background-color: #d00000;
border-color: #d00000;
}
.hashtag {
display: inline-block;
background-color: rgba(67, 97, 238, 0.1);
padding: 5px 12px;
border-radius: 20px;
font-size: 0.8rem;
margin-right: 5px;
margin-bottom: 5px;
color: var(--primary-color);
transition: all 0.2s ease;
font-weight: 500;
text-decoration: none;
}
.hashtag:hover {
background-color: rgba(67, 97, 238, 0.2);
color: var(--primary-color);
text-decoration: none;
}
.new-badge {
position: absolute;
top: 15px;
left: 15px;
background-color: var(--success-color);
color: white;
padding: 5px 10px;
border-radius: 20px;
font-size: 0.7rem;
font-weight: 600;
z-index: 2;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.delete-icon {
position: absolute;
top: 15px;
right: 15px;
background-color: rgba(255, 255, 255, 0.9);
color: var(--danger-color);
border-radius: 50%;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
cursor: pointer;
transition: all 0.2s ease;
}
.delete-icon:hover {
background-color: var(--danger-color);
color: white;
transform: scale(1.1);
}
.card-link {
color: inherit;
text-decoration: none;
}
.card-link:hover {
color: inherit;
text-decoration: none;
}
.layout-controls {
margin-bottom: 15px;
}
/* Custom 5-column layout */
.col-5-layout {
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
}
@media (min-width: 992px) {
.col-5-layout {
flex: 0 0 20%;
max-width: 20%;
}
}
@media (min-width: 768px) and (max-width: 991.98px) {
.col-5-layout {
flex: 0 0 25%;
max-width: 25%;
}
}
@media (max-width: 767.98px) {
.col-5-layout {
flex: 0 0 50%;
max-width: 50%;
}
}
.card-img-overlay {
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 50%);
transition: all 0.3s ease;
}
.toast-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
}
.toast {
min-width: 250px;
background-color: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
border: none;
border-radius: 8px;
}
.toast-header {
border-radius: 8px 8px 0 0;
}
.action-buttons {
display: flex;
gap: 8px;
}
.btn-sm {
padding: 5px 10px;
font-size: 0.8rem;
}
.empty-state {
text-align: center;
padding: 60px 0;
}
.empty-state i {
font-size: 3rem;
color: #dee2e6;
margin-bottom: 20px;
}
.empty-state p {
color: #6c757d;
font-size: 1.1rem;
}
.gallery-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.site-footer {
background-color: white;
padding: 20px 0;
text-align: center;
margin-top: 40px;
border-top: 1px solid #eaeaea;
color: #6c757d;
font-size: 0.9rem;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-photo-film"></i>
Image Uploader
</a>
<div class="ms-auto">
<a href="/logout" class="btn btn-outline-danger">
<i class="fas fa-sign-out-alt me-2"></i>Logout
</a>
</div>
</div>
</nav>
<div class="container">
<!-- Upload Section (Top) -->
<div class="upload-container">
<h3 class="section-title">Upload Images</h3>
<form id="uploadForm" enctype="multipart/form-data">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label for="files" class="form-label">
<i class="fas fa-images me-2"></i>Select images
</label>
<input class="form-control" type="file" id="files" name="files" accept="image/*" multiple>
<small class="text-muted">You can select multiple images</small>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="hashtags" class="form-label">
<i class="fas fa-hashtag me-2"></i>Add hashtags
</label>
<input type="text" class="form-control" id="hashtags" name="hashtags" placeholder="nature travel photography">
<small class="text-muted">Separate with spaces or commas</small>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-cloud-upload-alt me-2"></i>Upload Images
</button>
</form>
<div id="uploadProgress" class="progress hidden">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
</div>
<!-- Gallery Section (Bottom with integrated search) -->
<div class="gallery-container">
<div class="gallery-header">
<h3 class="section-title mb-0">Your Gallery</h3>
<div class="layout-controls btn-group" role="group">
<input type="radio" class="btn-check" name="layout" id="layout3" autocomplete="off" checked>
<label class="btn btn-outline-primary" for="layout3"><i class="fas fa-th-large"></i></label>
<input type="radio" class="btn-check" name="layout" id="layout4" autocomplete="off">
<label class="btn btn-outline-primary" for="layout4"><i class="fas fa-th"></i></label>
<input type="radio" class="btn-check" name="layout" id="layout5" autocomplete="off">
<label class="btn btn-outline-primary" for="layout5"><i class="fas fa-grip-horizontal"></i></label>
</div>
</div>
<!-- Search and Filter (Inside Gallery) -->
<div class="search-container">
<form id="searchForm" method="get" action="/" class="row g-3">
<div class="col-md-6">
<label for="search" class="form-label">
<i class="fas fa-search me-2"></i>Search by name or hashtag
</label>
<input type="text" class="form-control" id="search" name="search" value="{{ current_search or '' }}" placeholder="Search...">
</div>
<div class="col-md-6">
<label for="tag" class="form-label">
<i class="fas fa-filter me-2"></i>Filter by hashtag
</label>
<select class="form-select" id="tag" name="tag">
<option value="">All hashtags</option>
{% for tag in all_hashtags %}
<option value="{{ tag }}" {% if current_tag == tag %}selected{% endif %}>{{ tag }}</option>
{% endfor %}
</select>
</div>
</form>
</div>
<!-- Image Gallery -->
<div id="gallery">
{% if not uploaded_images %}
<div class="empty-state">
<i class="fas fa-images"></i>
<h4>No images yet</h4>
<p>Upload your first image to get started!</p>
</div>
{% else %}
<div class="row" id="imageGrid">
{# First, render new images #}
{% for image in uploaded_images %}
{% if image.is_new %}
<div class="image-item col-md-4">
<div class="card image-card">
<a href="/view/{{ image.name }}" class="card-link">
<span class="new-badge">NEW</span>
<button class="delete-icon delete-btn" data-filename="{{ image.name }}" title="Delete image">
<i class="fas fa-trash-alt"></i>
</button>
<img src="{{ image.url }}" class="card-img-top image-preview" alt="{{ image.original_filename }}">
<div class="card-body">
<h5 class="card-title text-truncate" title="{{ image.original_filename }}">{{ image.original_filename }}</h5>
<div class="hashtags mb-3">
{% for tag in image.hashtags %}
<a href="/?tag={{ tag }}" class="hashtag">#{{ tag }}</a>
{% endfor %}
</div>
</div>
</a>
</div>
</div>
{% endif %}
{% endfor %}
{# Then, render viewed images #}
{% for image in uploaded_images %}
{% if not image.is_new %}
<div class="image-item col-md-4">
<div class="card image-card">
<a href="/view/{{ image.name }}" class="card-link">
<button class="delete-icon delete-btn" data-filename="{{ image.name }}" title="Delete image">
<i class="fas fa-trash-alt"></i>
</button>
<img src="{{ image.url }}" class="card-img-top image-preview" alt="{{ image.original_filename }}">
<div class="card-body">
<h5 class="card-title text-truncate" title="{{ image.original_filename }}">{{ image.original_filename }}</h5>
<div class="hashtags mb-3">
{% for tag in image.hashtags %}
<a href="/?tag={{ tag }}" class="hashtag">#{{ tag }}</a>
{% endfor %}
</div>
</div>
</a>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<!-- Footer -->
<footer class="site-footer">
<div class="container">
<p class="mb-0">©2025 Detomo. All rights reserved</p>
</div>
</footer>
<!-- Toast container for notifications -->
<div class="toast-container">
<div id="uploadSuccessToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-success text-white">
<i class="fas fa-check-circle me-2"></i>
<strong class="me-auto">Success</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="toastMessage">
Images uploaded successfully!
</div>
</div>
</div>
<!-- Modal for Delete Confirmation -->
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="deleteConfirmModalLabel">Confirm Deletion</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center py-4">
<div class="mb-4">
<i class="fas fa-exclamation-triangle text-danger" style="font-size: 3.5rem;"></i>
</div>
<h5 class="mb-3">Are you sure you want to delete this image?</h5>
<p class="text-muted mb-0">This action cannot be undone.</p>
</div>
<div class="modal-footer border-0 justify-content-center">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancel
</button>
<button type="button" class="btn btn-danger px-4" id="confirmDeleteBtn">
<i class="fas fa-trash-alt me-2"></i>Delete
</button>
</div>
</div>
</div>
</div>
<!-- Modal for No Files Selected -->
<div class="modal fade" id="noFilesModal" tabindex="-1" aria-labelledby="noFilesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="noFilesModalLabel">No Files Selected</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center py-4">
<div class="mb-4">
<i class="fas fa-images text-warning" style="font-size: 3.5rem;"></i>
</div>
<h5 class="mb-2">Please select at least one image file</h5>
<p class="text-muted">You need to browse and select images before uploading.</p>
</div>
<div class="modal-footer border-0 justify-content-center">
<button type="button" class="btn btn-primary px-4" data-bs-dismiss="modal">
<i class="fas fa-check me-2"></i>OK
</button>
</div>
</div>
</div>
</div>
<!-- Modal for Duplicate Filename Confirmation -->
<div class="modal fade" id="duplicateFilesModal" tabindex="-1" aria-labelledby="duplicateFilesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="duplicateFilesModalLabel">Duplicate Filenames Detected</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body py-4">
<div class="text-center mb-4">
<i class="fas fa-exclamation-circle text-warning" style="font-size: 3.5rem;"></i>
<h5 class="mt-3 mb-4">Some files have the same names as existing images</h5>
<p class="text-muted mb-4">Please confirm if you want to replace the existing images with the new ones</p>
</div>
<div class="table-responsive">
<table class="table table-borderless align-middle" id="duplicateFilesTable">
<thead class="table-light">
<tr>
<th>Replace</th>
<th>New Image</th>
<th>Will Replace</th>
</tr>
</thead>
<tbody>
<!-- Populated dynamically -->
</tbody>
</table>
</div>
</div>
<div class="modal-footer border-0 justify-content-center">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancel Upload
</button>
<button type="button" class="btn btn-primary px-4" id="confirmReplaceBtn">
<i class="fas fa-check me-2"></i>Continue With Selected Replacements
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const uploadForm = document.getElementById('uploadForm');
const uploadProgress = document.getElementById('uploadProgress');
const progressBar = uploadProgress.querySelector('.progress-bar');
const toast = new bootstrap.Toast(document.getElementById('uploadSuccessToast'), {
delay: 3000
});
// Store files and hashtags for potential reuse if duplicates are found
let currentFiles = null;
let currentHashtags = '';
let duplicateFiles = [];
// Instant search functionality
const searchInput = document.getElementById('search');
const tagSelect = document.getElementById('tag');
const searchForm = document.getElementById('searchForm');
// Handle search input changes
searchInput.addEventListener('input', function() {
// Add small delay to prevent too many requests while typing
clearTimeout(searchInput.timer);
searchInput.timer = setTimeout(() => {
searchForm.submit();
}, 500); // Wait 500ms after typing stops
});
// Handle tag selection changes
tagSelect.addEventListener('change', function() {
searchForm.submit();
});
// Handle layout controls
const layoutControls = document.querySelectorAll('input[name="layout"]');
const imageGrid = document.getElementById('imageGrid');
const imageItems = document.querySelectorAll('.image-item');
function updateLayout(columns) {
// First, remove all column classes from all items
imageItems.forEach(item => {
item.classList.remove('col-md-3', 'col-md-4', 'col-md-6', 'col-5-layout');
});
// Then apply the new column class
imageItems.forEach(item => {
if (columns === 3) {
item.classList.add('col-md-4'); // 3 per row (4/12 width)
} else if (columns === 4) {
item.classList.add('col-md-3'); // 4 per row (3/12 width)
} else if (columns === 5) {
item.classList.add('col-5-layout'); // Custom 5 per row
}
});
// Save the preference to localStorage
localStorage.setItem('preferredLayout', columns);
}
// Initialize layout based on localStorage or default to 3 columns
const savedLayout = localStorage.getItem('preferredLayout') || '3';
const initialLayout = parseInt(savedLayout);
// Make sure the correct radio button is checked
const layoutButton = document.getElementById(`layout${initialLayout}`);
if (layoutButton) {
layoutButton.checked = true;
updateLayout(initialLayout);
} else {
// Fallback to layout3 if saved layout is invalid
document.getElementById('layout3').checked = true;
updateLayout(3);
}
// Add event listeners to layout controls
layoutControls.forEach(control => {
control.addEventListener('change', function() {
let columns = 3; // Default
if (this.id === 'layout3') columns = 3;
else if (this.id === 'layout4') columns = 4;
else if (this.id === 'layout5') columns = 5;
updateLayout(columns);
});
});
// Handle form submission
uploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const filesInput = document.getElementById('files');
if (!filesInput.files.length) {
// Show the no files modal instead of alert
const noFilesModal = new bootstrap.Modal(document.getElementById('noFilesModal'));
noFilesModal.show();
return;
}
// Store current files and hashtags in case we need to handle duplicates
currentFiles = filesInput.files;
currentHashtags = document.getElementById('hashtags').value;
const formData = new FormData();
// Add all files
for (let i = 0; i < filesInput.files.length; i++) {
formData.append('files', filesInput.files[i]);
}
// Add hashtags
formData.append('hashtags', currentHashtags);
// Show progress
uploadProgress.classList.remove('hidden');
progressBar.style.width = '0%';
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + '%';
}
});
xhr.addEventListener('load', function() {
// Hide progress bar
uploadProgress.classList.add('hidden');
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
// Check if we need to handle duplicates
if (response.success === false && response.action_required === 'confirm_replace') {
// Store the duplicates
duplicateFiles = response.duplicates;
// Populate the duplicate files table
const tableBody = document.querySelector('#duplicateFilesTable tbody');
tableBody.innerHTML = '';
duplicateFiles.forEach(function(file, index) {
const row = document.createElement('tr');
row.innerHTML = `
<td>
<div class="form-check">
<input class="form-check-input replace-checkbox" type="checkbox"
value="${file.existing_file}"
id="replace-check-${index}"
data-original="${file.original_name}" checked>
</div>
</td>
<td><strong>${file.new_file}</strong></td>
<td>${file.existing_file}</td>
`;
tableBody.appendChild(row);
});
// Show the duplicate files modal
const duplicateModal = new bootstrap.Modal(document.getElementById('duplicateFilesModal'));
duplicateModal.show();
return;
}
// Normal successful upload
handleSuccessfulUpload(response);
} else {
alert('Upload failed. Please try again.');
}
});
xhr.addEventListener('error', function() {
alert('Upload failed. Please try again.');
uploadProgress.classList.add('hidden');
});
xhr.open('POST', '/upload/');
xhr.send(formData);
});
// Handle confirmation of file replacements
document.getElementById('confirmReplaceBtn').addEventListener('click', function() {
// Get selected replacements
const checkboxes = document.querySelectorAll('.replace-checkbox:checked');
const filesToReplace = Array.from(checkboxes).map(function(checkbox) {
return {
existing_file: checkbox.value,
original_name: checkbox.dataset.original
};
});
// Hide the modal
const duplicateModal = bootstrap.Modal.getInstance(document.getElementById('duplicateFilesModal'));
duplicateModal.hide();
// Create a new FormData with the current files
const formData = new FormData();
// Add current files (those that we stored earlier)
for (let i = 0; i < currentFiles.length; i++) {
formData.append('files', currentFiles[i]);
}
// Add hashtags
formData.append('hashtags', currentHashtags);
// Add replacement information
formData.append('replace_files', JSON.stringify(filesToReplace));
// Show progress again
uploadProgress.classList.remove('hidden');
progressBar.style.width = '0%';
// Send the request to the replacement endpoint
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + '%';
}
});
xhr.addEventListener('load', function() {
// Hide progress bar
uploadProgress.classList.add('hidden');
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
handleSuccessfulUpload(response);
} else {
alert('Upload failed. Please try again.');
}
});
xhr.addEventListener('error', function() {
alert('Upload failed. Please try again.');
uploadProgress.classList.add('hidden');
});
xhr.open('POST', '/upload-with-replace/');
xhr.send(formData);
});
// Function to handle successful upload
function handleSuccessfulUpload(response) {
// Check if multiple files or single file
let uploadCount = 1;
if (response.files) {
uploadCount = response.uploaded_count;
}
// Show success toast
document.getElementById('toastMessage').textContent =
`Successfully uploaded ${uploadCount} image${uploadCount > 1 ? 's' : ''}!`;
toast.show();
// Reset form for next upload
uploadForm.reset();
currentFiles = null;
currentHashtags = '';
// Scroll to gallery section
document.getElementById('gallery').scrollIntoView({ behavior: 'smooth' });
// Refresh the page after a short delay to show the new images
setTimeout(() => {
window.location.reload();
}, 1000);
}
// Handle delete buttons to stop event propagation
document.querySelectorAll('.delete-btn').forEach(function(btn) {
btn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
const filename = this.dataset.filename;
// Store the filename for later use
document.getElementById('confirmDeleteBtn').dataset.filename = filename;
// Show the delete confirmation modal
const deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
deleteModal.show();
});
});
// Handle delete confirmation
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
const filename = this.dataset.filename;
const deleteModal = bootstrap.Modal.getInstance(document.getElementById('deleteConfirmModal'));
fetch(`/delete/${filename}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
deleteModal.hide();
window.location.reload();
} else {
alert('Failed to delete the image.');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the image.');
});
});
});
</script>
</body>
</html>