mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-15 19:29:03 +02:00
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
This commit is contained in:
1 parent
986d122528
commit
b764cdfc48
4 files changed
+167
-6
No files matched your search
@@ -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<HTMLDivElement>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
// State for generated ribbon overlay
|
||||
const [ribbonPublicId, setRibbonPublicId] = useState<string | null>(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<Blob>((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 ? (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg overflow-hidden bg-muted aspect-square flex items-center justify-center">
|
||||
<div className="relative w-full h-full">
|
||||
<div ref={previewRef} className="relative w-full h-full">
|
||||
<img
|
||||
src={previewUrl || selectedMedia.originalUrl}
|
||||
alt="Aperçu"
|
||||
@@ -645,9 +702,15 @@ export default function ImageEditor() {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button className="flex-1" variant="outline" data-testid="button-save">
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
data-testid="button-save"
|
||||
onClick={() => saveImageMutation.mutate()}
|
||||
disabled={saveImageMutation.isPending}
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
Enregistrer
|
||||
{saveImageMutation.isPending ? "Enregistrement..." : "Enregistrer"}
|
||||
</Button>
|
||||
<Button className="flex-1" data-testid="button-use-in-post">
|
||||
<ArrowRight className="w-4 h-4 mr-2" />
|
||||
|
||||
Reference in new issue
Block a user