613 lines
24 KiB
Plaintext
613 lines
24 KiB
Plaintext
|
|
@model List<string>
|
||
|
|
@{
|
||
|
|
Layout = "~/Views/Shared/_AdminLayout.cshtml";
|
||
|
|
ViewData["Title"] = "Media Upload";
|
||
|
|
}
|
||
|
|
|
||
|
|
<div class="mb-4">
|
||
|
|
<h2>Media Upload</h2>
|
||
|
|
<p class="text-muted">Upload and manage your images</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card mb-4">
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
|
|
<h5 class="card-title mb-0">Upload New Images</h5>
|
||
|
|
<button type="button" class="btn btn-primary" onclick="showUploadModal()">
|
||
|
|
<i class="bi bi-cloud-upload"></i> Choose Files
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div id="uploadResult" class="mt-3"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
|
|
<h5 class="card-title mb-0">Media Library (@Model.Count images)</h5>
|
||
|
|
<div>
|
||
|
|
<button type="button" class="btn btn-sm btn-primary me-2" onclick="showCreateFolderModal()">
|
||
|
|
<i class="bi bi-folder-plus"></i> New Folder
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Bulk Actions Toolbar -->
|
||
|
|
<div id="bulkActionsBar" class="alert alert-info d-none mb-3">
|
||
|
|
<div class="d-flex justify-content-between align-items-center">
|
||
|
|
<div>
|
||
|
|
<input type="checkbox" class="form-check-input me-2" id="selectAllImages" onchange="toggleSelectAll(this)">
|
||
|
|
<strong><span id="selectedCount">0</span> images selected</strong>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelectedImages()">
|
||
|
|
<i class="bi bi-trash"></i> Delete Selected
|
||
|
|
</button>
|
||
|
|
<button type="button" class="btn btn-sm btn-secondary" onclick="clearSelection()">
|
||
|
|
<i class="bi bi-x-lg"></i> Clear Selection
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Folders Section -->
|
||
|
|
<div id="foldersSection" class="mb-4">
|
||
|
|
<h6 class="text-muted">Folders</h6>
|
||
|
|
<div id="foldersList" class="row g-3 mb-3">
|
||
|
|
<div class="col-12 text-center text-muted">
|
||
|
|
<small>Loading folders...</small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Images Section -->
|
||
|
|
<h6 class="text-muted">All Images</h6>
|
||
|
|
@if (Model.Any())
|
||
|
|
{
|
||
|
|
<div class="row g-3" id="imagesGrid">
|
||
|
|
@foreach (var image in Model)
|
||
|
|
{
|
||
|
|
<div class="col-md-3 image-item" data-image="@image">
|
||
|
|
<div class="card position-relative">
|
||
|
|
<div class="position-absolute top-0 start-0 p-2">
|
||
|
|
<input type="checkbox" class="form-check-input image-checkbox" data-image="@image" onchange="updateSelection()">
|
||
|
|
</div>
|
||
|
|
<img src="@image" class="card-img-top" alt="Uploaded image" style="height: 200px; object-fit: cover; cursor: pointer;" onclick="toggleImageSelection(this)">
|
||
|
|
<div class="card-body p-2">
|
||
|
|
<div class="input-group input-group-sm">
|
||
|
|
<input type="text" class="form-control" value="@image" readonly onclick="this.select()">
|
||
|
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('@image')">
|
||
|
|
<i class="bi bi-clipboard"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<button class="btn btn-sm btn-danger w-100 mt-2" onclick="deleteImage('@image', this)">
|
||
|
|
<i class="bi bi-trash"></i> Delete
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
<p class="text-muted">No images uploaded yet.</p>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@section Scripts {
|
||
|
|
<script>
|
||
|
|
// Load folders on page load
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
loadFolders();
|
||
|
|
});
|
||
|
|
|
||
|
|
function loadFolders() {
|
||
|
|
fetch('/admin/upload/list-folders')
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(folders => {
|
||
|
|
const foldersList = document.getElementById('foldersList');
|
||
|
|
|
||
|
|
if (folders.length === 0) {
|
||
|
|
foldersList.innerHTML = '<div class="col-12 text-center text-muted"><small>No folders yet</small></div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
foldersList.innerHTML = '';
|
||
|
|
folders.forEach(folder => {
|
||
|
|
const col = document.createElement('div');
|
||
|
|
col.className = 'col-md-3';
|
||
|
|
col.innerHTML = `
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="d-flex align-items-center mb-2">
|
||
|
|
<i class="bi bi-folder-fill text-warning" style="font-size: 2rem;"></i>
|
||
|
|
<div class="ms-2">
|
||
|
|
<h6 class="mb-0">${folder.name}</h6>
|
||
|
|
<small class="text-muted">${folder.fileCount} files</small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<button class="btn btn-sm btn-danger w-100" onclick="deleteFolder('${folder.path}', this)">
|
||
|
|
<i class="bi bi-trash"></i> Delete Folder
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
foldersList.appendChild(col);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('Error loading folders:', error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function showCreateFolderModal() {
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('createFolderModal'));
|
||
|
|
document.getElementById('newFolderName').value = '';
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function createFolder() {
|
||
|
|
const folderName = document.getElementById('newFolderName').value.trim();
|
||
|
|
|
||
|
|
if (!folderName) {
|
||
|
|
alert('Please enter a folder name');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
fetch('/admin/upload/create-folder', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify(folderName)
|
||
|
|
})
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(result => {
|
||
|
|
if (result.success) {
|
||
|
|
bootstrap.Modal.getInstance(document.getElementById('createFolderModal')).hide();
|
||
|
|
loadFolders();
|
||
|
|
} else {
|
||
|
|
alert('Failed to create folder: ' + result.message);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
alert('Failed to create folder: ' + error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
let deleteFolderTarget = null;
|
||
|
|
let deleteFolderButton = null;
|
||
|
|
|
||
|
|
function deleteFolder(folderPath, button) {
|
||
|
|
deleteFolderTarget = folderPath;
|
||
|
|
deleteFolderButton = button;
|
||
|
|
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteFolderConfirmModal'));
|
||
|
|
document.getElementById('deleteFolderName').textContent = folderPath;
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function confirmDeleteFolder() {
|
||
|
|
if (!deleteFolderTarget) return;
|
||
|
|
|
||
|
|
// Show loading state
|
||
|
|
const modal = document.getElementById('deleteFolderConfirmModal');
|
||
|
|
const modalBody = modal.querySelector('.modal-body');
|
||
|
|
const originalContent = modalBody.innerHTML;
|
||
|
|
const confirmBtn = modal.querySelector('.btn-danger');
|
||
|
|
confirmBtn.disabled = true;
|
||
|
|
confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Deleting...';
|
||
|
|
|
||
|
|
fetch('/admin/upload/delete-folder', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Accept': 'application/json'
|
||
|
|
},
|
||
|
|
body: JSON.stringify(deleteFolderTarget),
|
||
|
|
credentials: 'same-origin',
|
||
|
|
redirect: 'manual'
|
||
|
|
})
|
||
|
|
.then(response => {
|
||
|
|
// Check if redirected to login
|
||
|
|
if (response.type === 'opaqueredirect' || response.status === 302) {
|
||
|
|
throw new Error('Session expired. Please refresh the page and log in again.');
|
||
|
|
}
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
|
|
}
|
||
|
|
return response.json();
|
||
|
|
})
|
||
|
|
.then(result => {
|
||
|
|
if (result.success) {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-success mb-0">
|
||
|
|
<i class="bi bi-check-circle-fill"></i> Folder deleted successfully!
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
setTimeout(() => {
|
||
|
|
bootstrap.Modal.getInstance(modal).hide();
|
||
|
|
loadFolders();
|
||
|
|
modalBody.innerHTML = originalContent;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Yes, Delete Folder';
|
||
|
|
}, 1500);
|
||
|
|
} else {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger mb-0">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Delete failed: ${result.message}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Try Again';
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger mb-0">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Delete failed: ${error.message || error}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Try Again';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
let selectedFiles = [];
|
||
|
|
|
||
|
|
function showUploadModal() {
|
||
|
|
selectedFiles = [];
|
||
|
|
document.getElementById('filePreview').innerHTML = '<p class="text-muted text-center">No files selected</p>';
|
||
|
|
document.getElementById('uploadBtn').disabled = true;
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('uploadModal'));
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function selectFiles() {
|
||
|
|
document.getElementById('hiddenFileInput').click();
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleFileSelection(input) {
|
||
|
|
const files = Array.from(input.files);
|
||
|
|
selectedFiles = files;
|
||
|
|
displayFilePreview(files);
|
||
|
|
document.getElementById('uploadBtn').disabled = files.length === 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
function displayFilePreview(files) {
|
||
|
|
const preview = document.getElementById('filePreview');
|
||
|
|
if (files.length === 0) {
|
||
|
|
preview.innerHTML = '<p class="text-muted text-center">No files selected</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
preview.innerHTML = `
|
||
|
|
<div class="alert alert-info mb-3">
|
||
|
|
<i class="bi bi-images"></i> <strong>${files.length}</strong> file(s) selected
|
||
|
|
</div>
|
||
|
|
<div class="row g-2">
|
||
|
|
${files.map((file, index) => `
|
||
|
|
<div class="col-4">
|
||
|
|
<div class="card">
|
||
|
|
<img src="${URL.createObjectURL(file)}" class="card-img-top" style="height: 100px; object-fit: cover;">
|
||
|
|
<div class="card-body p-1">
|
||
|
|
<small class="text-truncate d-block">${file.name}</small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`).join('')}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function uploadSelectedFiles() {
|
||
|
|
if (selectedFiles.length === 0) return;
|
||
|
|
|
||
|
|
const formData = new FormData();
|
||
|
|
selectedFiles.forEach(file => formData.append('files', file));
|
||
|
|
|
||
|
|
const modal = document.getElementById('uploadModal');
|
||
|
|
const modalBody = modal.querySelector('.modal-body');
|
||
|
|
const originalContent = modalBody.innerHTML;
|
||
|
|
const uploadBtn = document.getElementById('uploadBtn');
|
||
|
|
|
||
|
|
uploadBtn.disabled = true;
|
||
|
|
uploadBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Uploading...';
|
||
|
|
|
||
|
|
fetch('/admin/upload/multiple', {
|
||
|
|
method: 'POST',
|
||
|
|
body: formData
|
||
|
|
})
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(result => {
|
||
|
|
if (result.success) {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-success">
|
||
|
|
<i class="bi bi-check-circle-fill"></i> ${selectedFiles.length} image(s) uploaded successfully!
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
setTimeout(() => {
|
||
|
|
location.reload();
|
||
|
|
}, 1500);
|
||
|
|
} else {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Upload failed: ${result.message}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
uploadBtn.disabled = false;
|
||
|
|
uploadBtn.innerHTML = '<i class="bi bi-upload"></i> Try Again';
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Upload failed: ${error}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
uploadBtn.disabled = false;
|
||
|
|
uploadBtn.innerHTML = '<i class="bi bi-upload"></i> Try Again';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Multi-select functions
|
||
|
|
function toggleImageSelection(img) {
|
||
|
|
const checkbox = img.closest('.card').querySelector('.image-checkbox');
|
||
|
|
checkbox.checked = !checkbox.checked;
|
||
|
|
updateSelection();
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateSelection() {
|
||
|
|
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||
|
|
const count = checkboxes.length;
|
||
|
|
document.getElementById('selectedCount').textContent = count;
|
||
|
|
|
||
|
|
if (count > 0) {
|
||
|
|
document.getElementById('bulkActionsBar').classList.remove('d-none');
|
||
|
|
} else {
|
||
|
|
document.getElementById('bulkActionsBar').classList.add('d-none');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update select all checkbox
|
||
|
|
const allCheckboxes = document.querySelectorAll('.image-checkbox');
|
||
|
|
document.getElementById('selectAllImages').checked = count === allCheckboxes.length && count > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
function toggleSelectAll(checkbox) {
|
||
|
|
document.querySelectorAll('.image-checkbox').forEach(cb => {
|
||
|
|
cb.checked = checkbox.checked;
|
||
|
|
});
|
||
|
|
updateSelection();
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearSelection() {
|
||
|
|
document.querySelectorAll('.image-checkbox').forEach(cb => {
|
||
|
|
cb.checked = false;
|
||
|
|
});
|
||
|
|
updateSelection();
|
||
|
|
}
|
||
|
|
|
||
|
|
function deleteSelectedImages() {
|
||
|
|
const selected = Array.from(document.querySelectorAll('.image-checkbox:checked')).map(cb => cb.dataset.image);
|
||
|
|
if (selected.length === 0) return;
|
||
|
|
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('bulkDeleteModal'));
|
||
|
|
document.getElementById('bulkDeleteCount').textContent = selected.length;
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function confirmBulkDelete() {
|
||
|
|
const selected = Array.from(document.querySelectorAll('.image-checkbox:checked')).map(cb => cb.dataset.image);
|
||
|
|
|
||
|
|
const modal = document.getElementById('bulkDeleteModal');
|
||
|
|
const modalBody = modal.querySelector('.modal-body');
|
||
|
|
const originalContent = modalBody.innerHTML;
|
||
|
|
const confirmBtn = modal.querySelector('.btn-danger');
|
||
|
|
|
||
|
|
confirmBtn.disabled = true;
|
||
|
|
confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Deleting...';
|
||
|
|
|
||
|
|
Promise.all(selected.map(imageUrl =>
|
||
|
|
fetch('/admin/upload/delete', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify(imageUrl)
|
||
|
|
}).then(r => r.json())
|
||
|
|
))
|
||
|
|
.then(results => {
|
||
|
|
const successCount = results.filter(r => r.success).length;
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-success">
|
||
|
|
<i class="bi bi-check-circle-fill"></i> ${successCount} of ${selected.length} image(s) deleted successfully!
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
setTimeout(() => {
|
||
|
|
location.reload();
|
||
|
|
}, 1500);
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Bulk delete failed: ${error}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Try Again';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function copyToClipboard(text) {
|
||
|
|
navigator.clipboard.writeText(text).then(() => {
|
||
|
|
alert('URL copied to clipboard!');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
let deleteTarget = null;
|
||
|
|
let deleteButton = null;
|
||
|
|
|
||
|
|
function deleteImage(imageUrl, button) {
|
||
|
|
deleteTarget = imageUrl;
|
||
|
|
deleteButton = button;
|
||
|
|
|
||
|
|
// Show Bootstrap confirmation modal
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
|
||
|
|
document.getElementById('deleteItemName').textContent = imageUrl.split('/').pop();
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function confirmDelete() {
|
||
|
|
if (!deleteTarget) return;
|
||
|
|
|
||
|
|
// Show loading state
|
||
|
|
const modal = document.getElementById('deleteConfirmModal');
|
||
|
|
const modalBody = modal.querySelector('.modal-body');
|
||
|
|
const originalContent = modalBody.innerHTML;
|
||
|
|
const confirmBtn = modal.querySelector('.btn-danger');
|
||
|
|
confirmBtn.disabled = true;
|
||
|
|
confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Deleting...';
|
||
|
|
|
||
|
|
fetch('/admin/upload/delete', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Accept': 'application/json'
|
||
|
|
},
|
||
|
|
body: JSON.stringify(deleteTarget),
|
||
|
|
credentials: 'same-origin',
|
||
|
|
redirect: 'manual'
|
||
|
|
})
|
||
|
|
.then(response => {
|
||
|
|
// Check if redirected to login
|
||
|
|
if (response.type === 'opaqueredirect' || response.status === 302) {
|
||
|
|
throw new Error('Session expired. Please refresh the page and log in again.');
|
||
|
|
}
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
|
|
}
|
||
|
|
return response.json();
|
||
|
|
})
|
||
|
|
.then(result => {
|
||
|
|
if (result.success) {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-success mb-0">
|
||
|
|
<i class="bi bi-check-circle-fill"></i> Image deleted successfully!
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
deleteButton.closest('.col-md-3').remove();
|
||
|
|
setTimeout(() => {
|
||
|
|
bootstrap.Modal.getInstance(modal).hide();
|
||
|
|
modalBody.innerHTML = originalContent;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Yes, Delete';
|
||
|
|
}, 1500);
|
||
|
|
} else {
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger mb-0">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Delete failed: ${result.message}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Try Again';
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('Delete error:', error);
|
||
|
|
modalBody.innerHTML = `
|
||
|
|
<div class="alert alert-danger mb-0">
|
||
|
|
<i class="bi bi-x-circle-fill"></i> Delete failed: ${error.message || 'Network error'}<br>
|
||
|
|
<small class="text-muted">Check browser console for details</small>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
confirmBtn.disabled = false;
|
||
|
|
confirmBtn.innerHTML = '<i class="bi bi-trash"></i> Try Again';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<!-- Delete Image Confirmation Modal -->
|
||
|
|
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmLabel" aria-hidden="true">
|
||
|
|
<div class="modal-dialog modal-dialog-centered">
|
||
|
|
<div class="modal-content">
|
||
|
|
<div class="modal-header bg-danger text-white">
|
||
|
|
<h5 class="modal-title" id="deleteConfirmLabel">
|
||
|
|
<i class="bi bi-exclamation-triangle-fill"></i> Confirm Deletion
|
||
|
|
</h5>
|
||
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
<p class="mb-2">Are you sure you want to delete this image?</p>
|
||
|
|
<p class="text-muted mb-0"><strong id="deleteItemName"></strong></p>
|
||
|
|
<div class="alert alert-warning mt-3 mb-0">
|
||
|
|
<i class="bi bi-exclamation-circle"></i> This action cannot be undone.
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="modal-footer">
|
||
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
|
|
<i class="bi bi-x-lg"></i> Cancel
|
||
|
|
</button>
|
||
|
|
<button type="button" class="btn btn-danger" onclick="confirmDelete()">
|
||
|
|
<i class="bi bi-trash"></i> Yes, Delete
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Delete Folder Confirmation Modal -->
|
||
|
|
<div class="modal fade" id="deleteFolderConfirmModal" tabindex="-1" aria-labelledby="deleteFolderConfirmLabel" aria-hidden="true">
|
||
|
|
<div class="modal-dialog modal-dialog-centered">
|
||
|
|
<div class="modal-content">
|
||
|
|
<div class="modal-header bg-danger text-white">
|
||
|
|
<h5 class="modal-title" id="deleteFolderConfirmLabel">
|
||
|
|
<i class="bi bi-exclamation-triangle-fill"></i> Delete Folder
|
||
|
|
</h5>
|
||
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
<p class="mb-2">Are you sure you want to delete this folder and all its contents?</p>
|
||
|
|
<p class="text-muted mb-0"><strong id="deleteFolderName"></strong></p>
|
||
|
|
<div class="alert alert-danger mt-3 mb-0">
|
||
|
|
<i class="bi bi-exclamation-triangle-fill"></i> <strong>Warning:</strong> This will permanently delete the folder and all images inside it. This action cannot be undone.
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="modal-footer">
|
||
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
|
|
<i class="bi bi-x-lg"></i> Cancel
|
||
|
|
</button>
|
||
|
|
<button type="button" class="btn btn-danger" onclick="confirmDeleteFolder()">
|
||
|
|
<i class="bi bi-trash"></i> Yes, Delete Folder
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Create Folder Modal -->
|
||
|
|
<div class="modal fade" id="createFolderModal" tabindex="-1" aria-labelledby="createFolderLabel" aria-hidden="true">
|
||
|
|
<div class="modal-dialog modal-dialog-centered">
|
||
|
|
<div class="modal-content">
|
||
|
|
<div class="modal-header bg-primary text-white">
|
||
|
|
<h5 class="modal-title" id="createFolderLabel">
|
||
|
|
<i class="bi bi-folder-plus"></i> Create New Folder
|
||
|
|
</h5>
|
||
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
<div class="mb-3">
|
||
|
|
<label for="newFolderName" class="form-label">Folder Name</label>
|
||
|
|
<input type="text" class="form-control" id="newFolderName" placeholder="e.g., Products, Blog Images, Portfolio">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="modal-footer">
|
||
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
|
|
<button type="button" class="btn btn-primary" onclick="createFolder()">
|
||
|
|
<i class="bi bi-check-lg"></i> Create Folder
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
}
|