From b764cdfc48af42ab438d14bea4d1f8a06139ba2f Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Sat, 11 Oct 2025 09:10:37 +0000 Subject: [PATCH] Add ability to save edited images with overlays to the media library Implement a new endpoint for uploading edited images, converting them to a JPEG blob using html2canvas, and storing them in Cloudinary. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/VEZwUqa --- client/src/pages/image-editor.tsx | 75 ++++++++++++++++++++++++++++--- package-lock.json | 50 +++++++++++++++++++++ package.json | 1 + server/routes.ts | 47 +++++++++++++++++++ 4 files changed, 167 insertions(+), 6 deletions(-) diff --git a/client/src/pages/image-editor.tsx b/client/src/pages/image-editor.tsx index e59be5e..975a352 100644 --- a/client/src/pages/image-editor.tsx +++ b/client/src/pages/image-editor.tsx @@ -1,6 +1,7 @@ -import { useState, useEffect } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { apiRequest } from "@/lib/queryClient"; +import { useState, useEffect, useRef } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import html2canvas from "html2canvas"; import Sidebar from "@/components/sidebar"; import TopBar from "@/components/topbar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -13,6 +14,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Slider } from "@/components/ui/slider"; import { Badge } from "@/components/ui/badge"; import { Wand2, Save, ArrowRight, Image as ImageIcon, X } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; interface Ribbon { enabled: boolean; @@ -84,10 +86,65 @@ export default function ImageEditor() { queryKey: ["/api/media"], }); + const previewRef = useRef(null); + const { toast } = useToast(); + // State for generated ribbon overlay const [ribbonPublicId, setRibbonPublicId] = useState(null); const [isGeneratingRibbon, setIsGeneratingRibbon] = useState(false); + // Save edited image mutation + const saveImageMutation = useMutation({ + mutationFn: async () => { + if (!previewRef.current || !selectedMedia) { + throw new Error("Aucune image sélectionnée"); + } + + // Capture the preview container as canvas (including CSS overlays) + const canvas = await html2canvas(previewRef.current, { + backgroundColor: null, + scale: 2, // Higher quality + useCORS: true, + allowTaint: true + }); + + // Convert canvas to blob + const blob = await new Promise((resolve) => { + canvas.toBlob((blob) => resolve(blob!), 'image/jpeg', 0.95); + }); + + // Upload to server + const formData = new FormData(); + formData.append('file', blob, `edited_${Date.now()}.jpg`); + + const response = await fetch('/api/media/upload-edited', { + method: 'POST', + body: formData, + credentials: 'include' + }); + + if (!response.ok) { + throw new Error("Erreur lors de l'upload"); + } + + return response.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/media"] }); + toast({ + title: "Image enregistrée !", + description: "L'image éditée a été ajoutée à votre médiathèque.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Erreur", + description: error.message || "Impossible d'enregistrer l'image.", + variant: "destructive" + }); + } + }); + // Generate ribbon when settings change useEffect(() => { if (!ribbon.enabled || !ribbon.text) { @@ -591,7 +648,7 @@ export default function ImageEditor() { {selectedMedia ? (
-
+
Aperçu
-