2025-05-24 15:25:58 +02:00

372 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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="${apiClient.getThumbnailUrl(image.id)}"
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.created_at)}
</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', JSON.stringify(tags));
}
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="${apiClient.getImageUrl(imageId)}" 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.created_at)}</p>
<p><strong>Size:</strong> ${formatFileSize(image.file_size)}</p>
<p><strong>Dimensions:</strong> ${image.width} × ${image.height}</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="${apiClient.getThumbnailUrl(imageId)}" 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');
}
});
}