mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-18 04:39:18 +02:00
feat: add automatic image compression before upload
Added client-side image compression to improve upload speed and reduce storage. New Features: - Created imageCompression utility (src/utils/imageCompression.ts) * Automatically compresses images larger than 1MB * Resizes to max 1920x1080px while preserving aspect ratio * Uses 80% JPEG quality by default * Preserves PNG transparency when present * Falls back to 60% quality if still over 2MB Mobile (NoteDetailPage.tsx): - Integrated compression before upload - Shows compression stats to user (e.g., "5.2 MB → 1.1 MB") - Better error messages with specific details Desktop (Index.tsx): - Same compression integration as mobile - Consistent user experience across platforms Benefits: ✅ Users can upload large photos from cameras/phones ✅ Faster uploads (smaller files) ✅ Reduced server storage requirements ✅ Better mobile experience (photos often 5-10MB) ✅ Transparent to user (happens automatically) Technical Details: - Uses HTML5 Canvas API for processing - Client-side compression (no server load) - Maintains image quality while reducing size - Handles JPEG, PNG, WebP, and GIF formats - Smart transparency detection for PNGs
This commit is contained in:
3 files changed
+198
-22
No files matched your search
+22
-11
@@ -31,6 +31,7 @@ import { showError, showSuccess } from "@/utils/toast";
|
||||
import { ConfirmModal } from "@/components/modals/ConfirmModal";
|
||||
import { InputModal } from "@/components/modals/InputModal";
|
||||
import { AddUserModal } from "@/components/modals/AddUserModal";
|
||||
import { compressImage, formatFileSize } from "@/utils/imageCompression";
|
||||
|
||||
// ===== FONCTIONS UTILITAIRES TIMEZONE EUROPE/PARIS =====
|
||||
|
||||
@@ -1339,18 +1340,28 @@ const Index = () => {
|
||||
className="hidden"
|
||||
onChange={async (e) => {
|
||||
if (e.target.files && e.target.files[0] && openNote?.id) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
// Vérifier la taille du fichier (5MB max)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (file.size > maxSize) {
|
||||
showError("L'image est trop grande (max 5MB)");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
const originalFile = e.target.files[0];
|
||||
const originalSize = formatFileSize(originalFile.size);
|
||||
|
||||
try {
|
||||
const image = await NotesService.uploadImage(openNote.id, file);
|
||||
// Compresser l'image avant l'upload
|
||||
const compressedFile = await compressImage(originalFile);
|
||||
const compressedSize = formatFileSize(compressedFile.size);
|
||||
|
||||
// Afficher un message si l'image a été compressée
|
||||
if (compressedFile.size < originalFile.size) {
|
||||
showSuccess(`Image compressée: ${originalSize} → ${compressedSize}`);
|
||||
}
|
||||
|
||||
// Vérifier la taille finale (5MB max après compression)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (compressedFile.size > maxSize) {
|
||||
showError("L'image est trop grande même après compression (max 5MB)");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const image = await NotesService.uploadImage(openNote.id, compressedFile);
|
||||
if (image) {
|
||||
const updatedImages = [...(openNote.images || []), image];
|
||||
setOpenNote({ ...openNote, images: updatedImages });
|
||||
@@ -1360,7 +1371,7 @@ const Index = () => {
|
||||
showError("Erreur lors de l'upload de l'image");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'upload de l'image");
|
||||
showError(error instanceof Error ? error.message : "Erreur lors de l'upload de l'image");
|
||||
}
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import NotesService, { Note } from "@/services/NotesService";
|
||||
import { showError, showSuccess } from "@/utils/toast";
|
||||
import { compressImage, formatFileSize } from "@/utils/imageCompression";
|
||||
|
||||
export default function NoteDetailPage() {
|
||||
const { id } = useParams();
|
||||
@@ -119,18 +120,28 @@ export default function NoteDetailPage() {
|
||||
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files || !e.target.files[0] || !note?.id) return;
|
||||
|
||||
const file = e.target.files[0];
|
||||
|
||||
// Vérifier la taille du fichier (5MB max)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (file.size > maxSize) {
|
||||
showError("L'image est trop grande (max 5MB)");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
const originalFile = e.target.files[0];
|
||||
const originalSize = formatFileSize(originalFile.size);
|
||||
|
||||
try {
|
||||
const image = await NotesService.uploadImage(note.id, file);
|
||||
// Compresser l'image avant l'upload
|
||||
const compressedFile = await compressImage(originalFile);
|
||||
const compressedSize = formatFileSize(compressedFile.size);
|
||||
|
||||
// Afficher un message si l'image a été compressée
|
||||
if (compressedFile.size < originalFile.size) {
|
||||
showSuccess(`Image compressée: ${originalSize} → ${compressedSize}`);
|
||||
}
|
||||
|
||||
// Vérifier la taille finale (5MB max après compression)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (compressedFile.size > maxSize) {
|
||||
showError("L'image est trop grande même après compression (max 5MB)");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const image = await NotesService.uploadImage(note.id, compressedFile);
|
||||
if (image) {
|
||||
const updatedImages = [...(note.images || []), image];
|
||||
setNote({ ...note, images: updatedImages });
|
||||
@@ -139,7 +150,7 @@ export default function NoteDetailPage() {
|
||||
showError("Erreur lors de l'upload de l'image");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'upload");
|
||||
showError(error instanceof Error ? error.message : "Erreur lors de l'upload");
|
||||
}
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user