Spaces:
Sleeping
Sleeping
| // Image handling utilities for community posts and uploads | |
| class ImageHandler { | |
| constructor() { | |
| this.maxFileSize = 5 * 1024 * 1024; // 5MB | |
| this.allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; | |
| this.storageKey = 'ecospire_images'; | |
| } | |
| // Validate image file | |
| validateImage(file) { | |
| const errors = []; | |
| if (!file) { | |
| errors.push('No file selected'); | |
| return { valid: false, errors }; | |
| } | |
| if (file.size > this.maxFileSize) { | |
| errors.push(`File size must be less than ${this.maxFileSize / 1024 / 1024}MB`); | |
| } | |
| if (!this.allowedTypes.includes(file.type)) { | |
| errors.push('File type not supported. Please use JPEG, PNG, GIF, or WebP'); | |
| } | |
| return { | |
| valid: errors.length === 0, | |
| errors | |
| }; | |
| } | |
| // Convert file to base64 for storage | |
| async fileToBase64(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve(reader.result); | |
| reader.onerror = reject; | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| // Compress image if needed | |
| async compressImage(file, maxWidth = 800, quality = 0.8) { | |
| return new Promise((resolve) => { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = () => { | |
| // Calculate new dimensions | |
| let { width, height } = img; | |
| if (width > maxWidth) { | |
| height = (height * maxWidth) / width; | |
| width = maxWidth; | |
| } | |
| canvas.width = width; | |
| canvas.height = height; | |
| // Draw and compress | |
| ctx.drawImage(img, 0, 0, width, height); | |
| canvas.toBlob(resolve, file.type, quality); | |
| }; | |
| img.src = URL.createObjectURL(file); | |
| }); | |
| } | |
| // Process image for upload | |
| async processImage(file, options = {}) { | |
| const validation = this.validateImage(file); | |
| if (!validation.valid) { | |
| throw new Error(validation.errors.join(', ')); | |
| } | |
| const { | |
| compress = true, | |
| maxWidth = 800, | |
| quality = 0.8 | |
| } = options; | |
| let processedFile = file; | |
| // Compress if requested and file is large | |
| if (compress && file.size > 500 * 1024) { // 500KB threshold | |
| processedFile = await this.compressImage(file, maxWidth, quality); | |
| } | |
| const base64 = await this.fileToBase64(processedFile); | |
| return { | |
| original: { | |
| name: file.name, | |
| size: file.size, | |
| type: file.type | |
| }, | |
| processed: { | |
| name: processedFile.name || file.name, | |
| size: processedFile.size, | |
| type: processedFile.type, | |
| data: base64 | |
| }, | |
| preview: base64 | |
| }; | |
| } | |
| // Store image in localStorage (for demo purposes) | |
| storeImage(imageData, imageId) { | |
| try { | |
| const stored = this.getStoredImages(); | |
| stored[imageId] = { | |
| ...imageData, | |
| timestamp: Date.now(), | |
| id: imageId | |
| }; | |
| localStorage.setItem(this.storageKey, JSON.stringify(stored)); | |
| return true; | |
| } catch (error) { | |
| console.error('Failed to store image:', error); | |
| return false; | |
| } | |
| } | |
| // Get stored images | |
| getStoredImages() { | |
| try { | |
| const stored = localStorage.getItem(this.storageKey); | |
| return stored ? JSON.parse(stored) : {}; | |
| } catch (error) { | |
| console.error('Failed to retrieve stored images:', error); | |
| return {}; | |
| } | |
| } | |
| // Get specific image | |
| getImage(imageId) { | |
| const stored = this.getStoredImages(); | |
| return stored[imageId] || null; | |
| } | |
| // Delete image | |
| deleteImage(imageId) { | |
| try { | |
| const stored = this.getStoredImages(); | |
| delete stored[imageId]; | |
| localStorage.setItem(this.storageKey, JSON.stringify(stored)); | |
| return true; | |
| } catch (error) { | |
| console.error('Failed to delete image:', error); | |
| return false; | |
| } | |
| } | |
| // Clean up old images (older than 30 days) | |
| cleanupOldImages() { | |
| try { | |
| const stored = this.getStoredImages(); | |
| const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000); | |
| let cleaned = 0; | |
| Object.keys(stored).forEach(imageId => { | |
| if (stored[imageId].timestamp < thirtyDaysAgo) { | |
| delete stored[imageId]; | |
| cleaned++; | |
| } | |
| }); | |
| if (cleaned > 0) { | |
| localStorage.setItem(this.storageKey, JSON.stringify(stored)); | |
| console.log(`Cleaned up ${cleaned} old images`); | |
| } | |
| return cleaned; | |
| } catch (error) { | |
| console.error('Failed to cleanup old images:', error); | |
| return 0; | |
| } | |
| } | |
| // Get storage usage info | |
| getStorageInfo() { | |
| try { | |
| const stored = this.getStoredImages(); | |
| const imageCount = Object.keys(stored).length; | |
| let totalSize = 0; | |
| Object.values(stored).forEach(image => { | |
| totalSize += image.processed?.size || 0; | |
| }); | |
| const storageUsed = new Blob([localStorage.getItem(this.storageKey) || '']).size; | |
| return { | |
| imageCount, | |
| totalSize, | |
| storageUsed, | |
| formattedSize: this.formatFileSize(totalSize), | |
| formattedStorageUsed: this.formatFileSize(storageUsed) | |
| }; | |
| } catch (error) { | |
| console.error('Failed to get storage info:', error); | |
| return { | |
| imageCount: 0, | |
| totalSize: 0, | |
| storageUsed: 0, | |
| formattedSize: '0 B', | |
| formattedStorageUsed: '0 B' | |
| }; | |
| } | |
| } | |
| // Format file size for display | |
| formatFileSize(bytes) { | |
| if (bytes === 0) return '0 B'; | |
| const k = 1024; | |
| const sizes = ['B', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Generate placeholder images for demo | |
| generatePlaceholderImage(type = 'environmental', width = 400, height = 300) { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = width; | |
| canvas.height = height; | |
| const ctx = canvas.getContext('2d'); | |
| // Background gradient | |
| const gradient = ctx.createLinearGradient(0, 0, 0, height); | |
| switch (type) { | |
| case 'water': | |
| gradient.addColorStop(0, '#87CEEB'); | |
| gradient.addColorStop(1, '#4682B4'); | |
| break; | |
| case 'nature': | |
| gradient.addColorStop(0, '#98FB98'); | |
| gradient.addColorStop(1, '#228B22'); | |
| break; | |
| case 'air': | |
| gradient.addColorStop(0, '#87CEEB'); | |
| gradient.addColorStop(1, '#B0E0E6'); | |
| break; | |
| default: | |
| gradient.addColorStop(0, '#90EE90'); | |
| gradient.addColorStop(1, '#32CD32'); | |
| } | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, width, height); | |
| // Add text | |
| ctx.fillStyle = 'white'; | |
| ctx.font = 'bold 24px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('🌿 EcoSpire', width / 2, height / 2); | |
| ctx.font = '16px Arial'; | |
| ctx.fillText('Environmental Image', width / 2, height / 2 + 30); | |
| return canvas.toDataURL('image/png'); | |
| } | |
| } | |
| export const imageHandler = new ImageHandler(); | |
| export default imageHandler; |