372 lines
14 KiB
JavaScript
372 lines
14 KiB
JavaScript
// Image management functionality
|
|
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
|
|
// Load images with pagination
|
|
async function loadImages(page = 1, tags = null) {
|
|
if (!config.isConfigured()) {
|
|
showAlert('Please configure your API settings first.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const container = document.getElementById('imagesContainer');
|
|
container.innerHTML = '<div class="text-center"><div class="loading-spinner"></div> Loading images...</div>';
|
|
|
|
try {
|
|
const response = await apiClient.getImages(page, 20, tags);
|
|
currentPage = page;
|
|
totalPages = Math.ceil(response.total / (response.limit || 20));
|
|
|
|
// Handle structured response - extract images array from response object
|
|
const images = response.images || response;
|
|
displayImages(images);
|
|
displayPagination(response);
|
|
} catch (error) {
|
|
handleApiError(error, 'loading images');
|
|
container.innerHTML = '<div class="alert alert-danger">Failed to load images</div>';
|
|
}
|
|
}
|
|
|
|
// Display images in grid
|
|
function displayImages(images) {
|
|
const container = document.getElementById('imagesContainer');
|
|
|
|
if (!images || images.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-images fa-3x text-muted mb-3"></i>
|
|
<h4>No images found</h4>
|
|
<p class="text-muted">Upload your first image to get started!</p>
|
|
<button class="btn btn-primary" onclick="showUploadModal()">
|
|
<i class="fas fa-plus me-1"></i>Upload Image
|
|
</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const imagesHtml = images.map(image => `
|
|
<div class="image-card card">
|
|
<img src="${image.public_url || '/placeholder-image.png'}"
|
|
alt="${escapeHtml(image.description || 'Image')}"
|
|
onclick="viewImage('${image.id}')"
|
|
style="cursor: pointer;">
|
|
<div class="card-body">
|
|
<h6 class="card-title">${escapeHtml(truncateText(image.description || 'Untitled', 50))}</h6>
|
|
<p class="card-text small text-muted">
|
|
<i class="fas fa-calendar me-1"></i>${formatDate(image.upload_date)}
|
|
</p>
|
|
${image.tags && image.tags.length > 0 ? `
|
|
<div class="mb-2">
|
|
${image.tags.map(tag => `<span class="badge bg-secondary me-1">${escapeHtml(tag)}</span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
<div class="btn-group w-100" role="group">
|
|
<button class="btn btn-sm btn-outline-primary" onclick="viewImage('${image.id}')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="editImage('${image.id}')">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteImage('${image.id}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = `<div class="image-grid">${imagesHtml}</div>`;
|
|
}
|
|
|
|
// Display pagination
|
|
function displayPagination(response) {
|
|
const container = document.getElementById('imagesContainer');
|
|
|
|
if (totalPages <= 1) return;
|
|
|
|
const paginationHtml = `
|
|
<nav class="mt-4">
|
|
<ul class="pagination justify-content-center">
|
|
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="loadImages(${currentPage - 1})">Previous</a>
|
|
</li>
|
|
${Array.from({length: Math.min(totalPages, 5)}, (_, i) => {
|
|
const page = i + 1;
|
|
return `
|
|
<li class="page-item ${page === currentPage ? 'active' : ''}">
|
|
<a class="page-link" href="#" onclick="loadImages(${page})">${page}</a>
|
|
</li>
|
|
`;
|
|
}).join('')}
|
|
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="loadImages(${currentPage + 1})">Next</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
`;
|
|
|
|
container.insertAdjacentHTML('beforeend', paginationHtml);
|
|
}
|
|
|
|
// Show upload modal
|
|
function showUploadModal() {
|
|
const modalBody = `
|
|
<form id="uploadForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Select Image</label>
|
|
<div class="upload-area" id="uploadArea">
|
|
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
|
|
<h5>Drag & drop an image here</h5>
|
|
<p class="text-muted">or click to select a file</p>
|
|
<input type="file" id="imageFile" accept="image/*" style="display: none;">
|
|
</div>
|
|
<div id="imagePreview" class="mt-3" style="display: none;">
|
|
<img id="previewImg" class="img-fluid rounded" style="max-height: 200px;">
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="imageDescription" class="form-label">Description</label>
|
|
<textarea class="form-control" id="imageDescription" rows="3"
|
|
placeholder="Describe this image..."></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="imageTags" class="form-label">Tags</label>
|
|
<input type="text" class="form-control" id="imageTags"
|
|
placeholder="Enter tags separated by commas">
|
|
<div class="form-text">e.g., nature, landscape, sunset</div>
|
|
</div>
|
|
</form>
|
|
`;
|
|
|
|
const modalFooter = `
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="uploadImage()">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
`;
|
|
|
|
const modal = createModal('uploadModal', 'Upload Image', modalBody, modalFooter);
|
|
|
|
// Setup file input and drag & drop
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
const fileInput = document.getElementById('imageFile');
|
|
|
|
uploadArea.addEventListener('click', () => fileInput.click());
|
|
|
|
fileInput.addEventListener('change', (e) => {
|
|
if (e.target.files.length > 0) {
|
|
handleFileSelection(e.target.files[0]);
|
|
}
|
|
});
|
|
|
|
setupFileDropZone(uploadArea, (files) => {
|
|
if (files.length > 0) {
|
|
handleFileSelection(files[0]);
|
|
}
|
|
});
|
|
|
|
modal.show();
|
|
}
|
|
|
|
// Handle file selection
|
|
async function handleFileSelection(file) {
|
|
try {
|
|
validateFile(file);
|
|
|
|
const preview = await createImagePreview(file);
|
|
const previewContainer = document.getElementById('imagePreview');
|
|
const previewImg = document.getElementById('previewImg');
|
|
|
|
previewImg.src = preview;
|
|
previewContainer.style.display = 'block';
|
|
|
|
// Store file for upload
|
|
document.getElementById('uploadForm').selectedFile = file;
|
|
|
|
} catch (error) {
|
|
showAlert(error.message, 'danger');
|
|
}
|
|
}
|
|
|
|
// Upload image
|
|
async function uploadImage() {
|
|
const form = document.getElementById('uploadForm');
|
|
const file = form.selectedFile;
|
|
|
|
if (!file) {
|
|
showAlert('Please select an image file', 'danger');
|
|
return;
|
|
}
|
|
|
|
const description = document.getElementById('imageDescription').value.trim();
|
|
const tagsInput = document.getElementById('imageTags').value.trim();
|
|
const tags = tagsInput ? tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
|
|
|
|
const uploadButton = document.querySelector('#uploadModal .btn-primary');
|
|
setLoadingState(uploadButton);
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('description', description);
|
|
if (tags.length > 0) {
|
|
formData.append('tags', tags.join(','));
|
|
}
|
|
|
|
await apiClient.uploadImage(formData);
|
|
|
|
showAlert('Image uploaded successfully!', 'success');
|
|
|
|
// Close modal and refresh images
|
|
bootstrap.Modal.getInstance(document.getElementById('uploadModal')).hide();
|
|
removeModal('uploadModal');
|
|
loadImages(currentPage);
|
|
|
|
} catch (error) {
|
|
handleApiError(error, 'uploading image');
|
|
} finally {
|
|
setLoadingState(uploadButton, false);
|
|
}
|
|
}
|
|
|
|
// View image details
|
|
async function viewImage(imageId) {
|
|
try {
|
|
const image = await apiClient.getImage(imageId);
|
|
|
|
const modalBody = `
|
|
<div class="text-center mb-3">
|
|
<img src="${image.public_url || '/placeholder-image.png'}" class="img-fluid rounded"
|
|
style="max-height: 400px;">
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Description</h6>
|
|
<p>${escapeHtml(image.description || 'No description')}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Details</h6>
|
|
<p><strong>Created:</strong> ${formatDate(image.upload_date)}</p>
|
|
<p><strong>Size:</strong> ${formatFileSize(image.file_size)}</p>
|
|
<p><strong>Type:</strong> ${image.content_type}</p>
|
|
</div>
|
|
</div>
|
|
${image.tags && image.tags.length > 0 ? `
|
|
<div class="mt-3">
|
|
<h6>Tags</h6>
|
|
${image.tags.map(tag => `<span class="badge bg-secondary me-1">${escapeHtml(tag)}</span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
|
|
const modalFooter = `
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" onclick="editImage('${imageId}')">
|
|
<i class="fas fa-edit me-1"></i>Edit
|
|
</button>
|
|
<button type="button" class="btn btn-danger" onclick="deleteImage('${imageId}')">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
`;
|
|
|
|
const modal = createModal('viewImageModal', 'Image Details', modalBody, modalFooter);
|
|
modal.show();
|
|
|
|
} catch (error) {
|
|
handleApiError(error, 'loading image details');
|
|
}
|
|
}
|
|
|
|
// Edit image
|
|
async function editImage(imageId) {
|
|
try {
|
|
const image = await apiClient.getImage(imageId);
|
|
|
|
const modalBody = `
|
|
<form id="editImageForm">
|
|
<div class="mb-3 text-center">
|
|
<img src="${image.public_url || '/placeholder-image.png'}" class="img-fluid rounded"
|
|
style="max-height: 200px;">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editDescription" class="form-label">Description</label>
|
|
<textarea class="form-control" id="editDescription" rows="3">${escapeHtml(image.description || '')}</textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editTags" class="form-label">Tags</label>
|
|
<input type="text" class="form-control" id="editTags"
|
|
value="${image.tags ? image.tags.join(', ') : ''}">
|
|
<div class="form-text">Enter tags separated by commas</div>
|
|
</div>
|
|
</form>
|
|
`;
|
|
|
|
const modalFooter = `
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveImageChanges('${imageId}')">
|
|
<i class="fas fa-save me-1"></i>Save Changes
|
|
</button>
|
|
`;
|
|
|
|
const modal = createModal('editImageModal', 'Edit Image', modalBody, modalFooter);
|
|
modal.show();
|
|
|
|
} catch (error) {
|
|
handleApiError(error, 'loading image for editing');
|
|
}
|
|
}
|
|
|
|
// Save image changes
|
|
async function saveImageChanges(imageId) {
|
|
const description = document.getElementById('editDescription').value.trim();
|
|
const tagsInput = document.getElementById('editTags').value.trim();
|
|
const tags = tagsInput ? tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
|
|
|
|
const saveButton = document.querySelector('#editImageModal .btn-primary');
|
|
setLoadingState(saveButton);
|
|
|
|
try {
|
|
await apiClient.updateImage(imageId, {
|
|
description,
|
|
tags
|
|
});
|
|
|
|
showAlert('Image updated successfully!', 'success');
|
|
|
|
// Close modal and refresh images
|
|
bootstrap.Modal.getInstance(document.getElementById('editImageModal')).hide();
|
|
removeModal('editImageModal');
|
|
loadImages(currentPage);
|
|
|
|
} catch (error) {
|
|
handleApiError(error, 'updating image');
|
|
} finally {
|
|
setLoadingState(saveButton, false);
|
|
}
|
|
}
|
|
|
|
// Delete image
|
|
function deleteImage(imageId) {
|
|
confirmAction('Are you sure you want to delete this image? This action cannot be undone.', async () => {
|
|
try {
|
|
await apiClient.deleteImage(imageId);
|
|
showAlert('Image deleted successfully!', 'success');
|
|
loadImages(currentPage);
|
|
|
|
// Close any open modals
|
|
const modals = ['viewImageModal', 'editImageModal'];
|
|
modals.forEach(modalId => {
|
|
const modalElement = document.getElementById(modalId);
|
|
if (modalElement) {
|
|
bootstrap.Modal.getInstance(modalElement)?.hide();
|
|
removeModal(modalId);
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
handleApiError(error, 'deleting image');
|
|
}
|
|
});
|
|
}
|