// Image management functionality let currentPage = 1; let totalPages = 1; let imageBlobCache = new Map(); // Cache for blob URLs // Load image with authentication and return blob URL async function loadImageBlob(imageId) { // Check cache first if (imageBlobCache.has(imageId)) { return imageBlobCache.get(imageId); } try { const blobUrl = await apiClient.downloadImageBlob(imageId); imageBlobCache.set(imageId, blobUrl); return blobUrl; } catch (error) { console.error(`Failed to load image ${imageId}:`, error); throw error; // Let the caller handle the error } } // Clean up blob URLs to prevent memory leaks function cleanupBlobCache() { for (const [imageId, blobUrl] of imageBlobCache.entries()) { if (blobUrl.startsWith('blob:')) { URL.revokeObjectURL(blobUrl); } } imageBlobCache.clear(); } // 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 = '
Loading images...
'; 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 = '
Failed to load images
'; } } // Display images in grid async function displayImages(images) { const container = document.getElementById('imagesContainer'); if (!images || images.length === 0) { container.innerHTML = `

No images found

Upload your first image to get started!

`; return; } // Create image cards with placeholder images first const imagesHtml = images.map(image => `
${escapeHtml(image.description || 'Image')}
Loading...
${escapeHtml(truncateText(image.description || 'Untitled', 50))}

${formatDate(image.upload_date)}

${image.tags && image.tags.length > 0 ? `
${image.tags.map(tag => `${escapeHtml(tag)}`).join('')}
` : ''}
`).join(''); container.innerHTML = `
${imagesHtml}
`; // Load actual images asynchronously for (const image of images) { loadImageBlob(image.id).then(blobUrl => { const imgElement = document.getElementById(`img-${image.id}`); const loadingOverlay = imgElement.parentElement.querySelector('.loading-overlay'); if (imgElement) { imgElement.src = blobUrl; imgElement.style.opacity = '1'; if (loadingOverlay) { loadingOverlay.style.display = 'none'; } } }).catch(error => { console.error(`Failed to load image ${image.id}:`, error); const imgElement = document.getElementById(`img-${image.id}`); const loadingOverlay = imgElement.parentElement.querySelector('.loading-overlay'); if (imgElement && loadingOverlay) { // Show error state instead of broken image loadingOverlay.innerHTML = `
Failed to load
`; imgElement.style.display = 'none'; // Hide the img element completely } }); } } // Display pagination function displayPagination(response) { const container = document.getElementById('imagesContainer'); if (totalPages <= 1) return; const paginationHtml = ` `; container.insertAdjacentHTML('beforeend', paginationHtml); } // Show upload modal function showUploadModal() { const modalBody = `
Drag & drop an image here

or click to select a file

e.g., nature, landscape, sunset
`; const modalFooter = ` `; 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 { // Close any existing modals first const existingModals = ['viewImageModal', 'editImageModal']; existingModals.forEach(modalId => { const existingModal = document.getElementById(modalId); if (existingModal) { const bsModal = bootstrap.Modal.getInstance(existingModal); if (bsModal) { bsModal.hide(); } removeModal(modalId); } }); const image = await apiClient.getImage(imageId); // Load the image with authentication const imageSrc = await loadImageBlob(imageId); // Use unique modal ID to prevent conflicts const modalId = `viewImageModal-${imageId}`; const modalBody = `
${escapeHtml(image.description || 'Image')}
Description

${escapeHtml(image.description || 'No description')}

Details

Created: ${formatDate(image.upload_date)}

Size: ${formatFileSize(image.file_size)}

Type: ${image.content_type}

${image.tags && image.tags.length > 0 ? `
Tags
${image.tags.map(tag => `${escapeHtml(tag)}`).join('')}
` : ''} `; const modalFooter = ` `; const modal = createModal(modalId, 'Image Details', modalBody, modalFooter); // Add cleanup when modal is hidden const modalElement = document.getElementById(modalId); modalElement.addEventListener('hidden.bs.modal', () => { removeModal(modalId); }); modal.show(); } catch (error) { handleApiError(error, 'loading image details'); } } // Edit image async function editImage(imageId) { try { // Close any existing modals first const existingModals = document.querySelectorAll('.modal'); existingModals.forEach(modal => { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) { bsModal.hide(); } modal.remove(); }); const image = await apiClient.getImage(imageId); // Load the image with authentication const imageSrc = await loadImageBlob(imageId); // Use unique modal ID to prevent conflicts const modalId = `editImageModal-${imageId}`; const modalBody = `
${escapeHtml(image.description || 'Image')}
Enter tags separated by commas
`; const modalFooter = ` `; const modal = createModal(modalId, 'Edit Image', modalBody, modalFooter); // Add cleanup when modal is hidden const modalElement = document.getElementById(modalId); modalElement.addEventListener('hidden.bs.modal', () => { removeModal(modalId); }); modal.show(); } catch (error) { handleApiError(error, 'loading image for editing'); } } // Save image changes async function saveImageChanges(imageId) { const description = document.getElementById(`editDescription-${imageId}`).value.trim(); const tagsInput = document.getElementById(`editTags-${imageId}`).value.trim(); const tags = tagsInput ? tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag) : []; const saveButton = document.querySelector(`#editImageModal-${imageId} .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-${imageId}`)).hide(); removeModal(`editImageModal-${imageId}`); 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 for this image const modalsToClose = [`viewImageModal-${imageId}`, `editImageModal-${imageId}`]; modalsToClose.forEach(modalId => { const modalElement = document.getElementById(modalId); if (modalElement) { const bsModal = bootstrap.Modal.getInstance(modalElement); if (bsModal) { bsModal.hide(); } removeModal(modalId); } }); } catch (error) { handleApiError(error, 'deleting image'); } }); }