/**
* Image Handler - Handles image upload and drag & drop functionality
*/
import { Logger } from '../core/logger.js';
import { domManager } from './dom-manager.js';
export class ImageHandler {
constructor() {
this.isInitialized = false;
this.currentImageData = null;
}
initialize() {
if (this.isInitialized) return;
this.setupEventListeners();
this.setupDragAndDrop();
this.isInitialized = true;
Logger.debug('Image', 'Image handler initialized');
}
setupEventListeners() {
// Image upload listeners
domManager.getElement('imageInput')?.addEventListener('change', (e) => this.handleImageUpload(e));
domManager.getElement('imageUploadBtn')?.addEventListener('click', () => this.triggerImageUpload());
Logger.debug('Image', 'Event listeners set up');
}
setupDragAndDrop() {
const imageUpload = domManager.getElement('imageUpload');
if (!imageUpload) {
Logger.warn('Image', 'Image upload element not found');
return;
}
imageUpload.addEventListener('dragover', (e) => {
e.preventDefault();
imageUpload.classList.add('drag-over');
});
imageUpload.addEventListener('dragleave', () => {
imageUpload.classList.remove('drag-over');
});
imageUpload.addEventListener('drop', (e) => {
e.preventDefault();
imageUpload.classList.remove('drag-over');
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
this.processImageFile(file);
} else {
domManager.updateStatus('Please drop a valid image file', 'warning');
}
});
Logger.debug('Image', 'Drag and drop set up');
}
triggerImageUpload() {
const imageInput = domManager.getElement('imageInput');
if (imageInput) {
imageInput.click();
}
}
handleImageUpload(e) {
const file = e.target.files[0];
if (file) {
this.processImageFile(file);
}
}
processImageFile(file) {
// Validate file type
if (!file.type.startsWith('image/')) {
domManager.updateStatus('Please select a valid image file', 'warning');
return;
}
// Validate file size (10MB limit)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
domManager.updateStatus('Image file is too large. Please select a file under 10MB', 'warning');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
this.displayImagePreview(e.target.result);
this.currentImageData = e.target.result;
domManager.updateStatus(`Image "${file.name}" loaded successfully`, 'success');
Logger.debug('Image', `Image processed: ${file.name} (${file.size} bytes)`);
} catch (error) {
Logger.error('Image', 'Error processing image:', error);
domManager.updateStatus('Error processing image', 'error');
}
};
reader.onerror = () => {
Logger.error('Image', 'Error reading image file');
domManager.updateStatus('Error reading image file', 'error');
};
reader.readAsDataURL(file);
}
displayImagePreview(imageSrc) {
const imagePreview = domManager.getElement('imagePreview');
const imageUpload = domManager.getElement('imageUpload');
const imageUploadBtn = domManager.getElement('imageUploadBtn');
if (imagePreview) {
imagePreview.src = imageSrc;
imagePreview.style.display = 'block';
}
if (imageUpload) {
imageUpload.classList.add('has-image');
}
if (imageUploadBtn) {
imageUploadBtn.innerHTML = `
Image Selected
`;
// Replace feather icons
if (typeof feather !== 'undefined') {
feather.replace(imageUploadBtn);
}
}
}
clearImage() {
const imagePreview = domManager.getElement('imagePreview');
const imageUpload = domManager.getElement('imageUpload');
const imageUploadBtn = domManager.getElement('imageUploadBtn');
const imageInput = domManager.getElement('imageInput');
if (imagePreview) {
imagePreview.style.display = 'none';
imagePreview.src = '';
}
if (imageUpload) {
imageUpload.classList.remove('has-image');
}
if (imageUploadBtn) {
imageUploadBtn.innerHTML = `
Upload Image
`;
// Replace feather icons
if (typeof feather !== 'undefined') {
feather.replace(imageUploadBtn);
}
}
if (imageInput) {
imageInput.value = '';
}
this.currentImageData = null;
Logger.debug('Image', 'Image cleared');
}
getCurrentImageData() {
return this.currentImageData;
}
hasImage() {
return this.currentImageData !== null;
}
// Get image data in format suitable for sending to server
getImageForSubmission() {
if (!this.currentImageData) {
return null;
}
try {
// Extract base64 data without the data URL prefix
const base64Data = this.currentImageData.split(',')[1];
const mimeType = this.currentImageData.split(';')[0].split(':')[1];
return {
data: base64Data,
mimeType: mimeType,
filename: `uploaded_image.${this.getExtensionFromMimeType(mimeType)}`
};
} catch (error) {
Logger.error('Image', 'Error preparing image for submission:', error);
return null;
}
}
getExtensionFromMimeType(mimeType) {
const extensions = {
'image/jpeg': 'jpg',
'image/jpg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'image/bmp': 'bmp',
'image/svg+xml': 'svg'
};
return extensions[mimeType] || 'jpg';
}
// Validate image before processing
validateImage(file) {
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'];
const maxSize = 10 * 1024 * 1024; // 10MB
const errors = [];
if (!validTypes.includes(file.type)) {
errors.push('Invalid file type. Please select a JPEG, PNG, GIF, WebP, or BMP image.');
}
if (file.size > maxSize) {
errors.push('File size too large. Please select an image under 10MB.');
}
if (file.size === 0) {
errors.push('File appears to be empty.');
}
return {
isValid: errors.length === 0,
errors: errors
};
}
// Get image metadata
getImageMetadata(file) {
return {
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified ? new Date(file.lastModified) : null
};
}
// Handle paste events for image upload
setupPasteHandler() {
document.addEventListener('paste', (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (file) {
this.processImageFile(file);
domManager.updateStatus('Image pasted from clipboard', 'success');
}
break;
}
}
});
Logger.debug('Image', 'Paste handler set up');
}
// Generate image thumbnail for preview
generateThumbnail(imageSrc, maxWidth = 200, maxHeight = 200) {
return new Promise((resolve, reject) => {
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = () => {
// Calculate new dimensions
let { width, height } = img;
if (width > height) {
if (width > maxWidth) {
height = (height * maxWidth) / width;
width = maxWidth;
}
} else {
if (height > maxHeight) {
width = (width * maxHeight) / height;
height = maxHeight;
}
}
canvas.width = width;
canvas.height = height;
// Draw resized image
ctx.drawImage(img, 0, 0, width, height);
// Convert to data URL
const thumbnailData = canvas.toDataURL('image/jpeg', 0.8);
resolve(thumbnailData);
};
img.onerror = () => {
reject(new Error('Failed to load image for thumbnail generation'));
};
img.src = imageSrc;
});
}
// SESSION MANAGEMENT METHODS
loadSessionImage(imageData) {
const imagePreview = domManager.getElement('imagePreview');
const imageUpload = document.querySelector('.image-upload');
const uploadBtn = document.querySelector('.image-upload-btn');
if (imageData && imagePreview && imageUpload && uploadBtn) {
// Load image into preview
imagePreview.src = imageData;
imagePreview.style.display = 'block';
imageUpload.classList.add('has-image');
// Update button state
uploadBtn.innerHTML = `
Image Selected
`;
// Store image data
this.currentImageData = imageData;
Logger.debug('Image', 'Session image loaded');
} else {
// Clear image if no data provided
this.clearImage();
}
// Replace feather icons
try {
if (typeof feather !== 'undefined' && uploadBtn) {
feather.replace(uploadBtn);
}
} catch (e) {
Logger.warn('Image', 'Could not replace feather icons in upload button:', e);
}
}
}
// Create singleton instance
export const imageHandler = new ImageHandler();