2025-05-24 17:23:37 +02:00

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');
}
});
}