import { Injectable, signal } from '@angular/core'; import imageCompression from 'browser-image-compression'; @Injectable({ providedIn: 'root' }) export class FileManagerService { file = signal(null); imagePreviewUrl = signal(null); fileError = signal(null); isCompressing = signal(false); private readonly MAX_FILE_SIZE = 5 * 1024 * 1024; // 5Mo en bytes private readonly ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp']; private readonly ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp']; formatFileSize(bytes: number): string { if (bytes === 0) return '0 Octets'; const k = 1024; const sizes = ['Octets', 'Ko', 'Mo', 'Go']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; } async onPictureChange($event: Event): Promise { const target = $event.target as HTMLInputElement; const selectedFile = target?.files?.[0]; if (!selectedFile) { return; } // Réinitialiser l'erreur this.fileError.set(null); // Vérifier le type de fichier if (!this.ALLOWED_IMAGE_TYPES.includes(selectedFile.type)) { this.resetFile(); this.fileError.set('Le fichier doit être une image (JPEG, PNG ou WebP)'); target.value = ''; return; } // Vérifier l'extension du fichier (sécurité supplémentaire) const fileExtension = selectedFile.name.split('.').pop()?.toLowerCase(); if (!fileExtension || !this.ALLOWED_EXTENSIONS.includes(fileExtension)) { this.resetFile(); this.fileError.set('Extension de fichier non autorisée'); target.value = ''; return; } // Vérifier la taille du fichier if (selectedFile.size > this.MAX_FILE_SIZE) { this.resetFile(); this.fileError.set( `L'image est trop volumineuse (${this.formatFileSize(selectedFile.size)}). Taille maximale autorisée : 5 Mo` ); target.value = ''; return; } // Configuration de la compression const options = { maxSizeMB: 1, // On vise une taille max de 1MB maxWidthOrHeight: 1920, // On redimensionne si l'image est immense (4K+) useWebWorker: true, // Ne pas bloquer l'interface fileType: 'image/webp', // (Optionnel) Convertir en WebP pour le web (plus léger) }; try { this.isCompressing.set(true); // Compression const compressedFile = await imageCompression(selectedFile, options); // Mise à jour des signaux avec le fichier optimisé this.file.set(compressedFile); this.readFile(compressedFile); } catch (error) { this.fileError.set("Impossible de compresser l'image."); this.resetFile(); } finally { this.isCompressing.set(false); target.value = ''; } } onFileChange($event: Event): void { const target = $event.target as HTMLInputElement; const selectedFile = target?.files?.[0]; if (!selectedFile) { return; } // Réinitialiser l'erreur this.fileError.set(null); // Vérifier le type de fichier if (selectedFile.type !== 'application/pdf') { this.fileError.set('Le fichier doit être au format PDF'); this.file.set(null); target.value = ''; // Réinitialiser l'input return; } // Vérifier la taille du fichier if (selectedFile.size > this.MAX_FILE_SIZE) { this.fileError.set( `Le fichier est trop volumineux (${this.formatFileSize(selectedFile.size)}). Taille maximale autorisée : 5 Mo` ); this.file.set(null); target.value = ''; // Réinitialiser l'input return; } this.file.set(selectedFile); } removeFile(): void { this.resetFile(); } resetFile(): void { this.file.set(null); this.imagePreviewUrl.set(null); this.fileError.set(null); } private readFile(file: File): void { const reader = new FileReader(); reader.onload = (e) => { this.imagePreviewUrl.set(e.target?.result as string); }; reader.onerror = () => { this.fileError.set('Erreur lors de la lecture du fichier'); this.resetFile(); }; reader.readAsDataURL(file); } }