Files
SkyArtShop/Views/AdminUpload/Index.cshtml

613 lines
24 KiB
Plaintext
Raw Normal View History

@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>
}