canvas_frontend_dev/src/components/CanvasSetting.jsx
2025-03-01 14:20:45 +06:00

409 lines
12 KiB
JavaScript

import { useContext, useEffect, useState } from "react";
import CanvasContext from "./Context/canvasContext/CanvasContext";
import { Card, CardTitle } from "./ui/card";
import { Button } from "./ui/button";
import { Trash2, UploadIcon } from "lucide-react";
import { Separator } from "./ui/separator";
import ColorComponent from "./ColorComponent";
import { Label } from "./ui/label";
import { Input } from "./ui/input";
import { Slider } from "./ui/slider";
import { fabric } from "fabric";
import { ScrollArea } from "./ui/scroll-area";
import { useNavigate, useParams } from "react-router-dom";
import { useToast } from "../hooks/use-toast";
import { useMutation } from "@tanstack/react-query";
import { deleteImage, uploadImage } from "../api/uploadApi";
import { createProject } from "../api/projectApi";
import SaveCanvas from "./SaveCanvas";
const CanvasSetting = () => {
const { canvas } = useContext(CanvasContext);
const params = useParams();
const { id } = params;
const { toast } = useToast();
const navigate = useNavigate();
const [bgImage, setBgImage] = useState(null);
const [bgOverLayImage, setBgOverLayImage] = useState(null);
// create empty project if no id is provided
useEffect(() => {
const createEmptyProject = async () => {
try {
const response = await createProject();
if (response?.status === 200) {
toast({
title: response?.status,
description: response?.message
})
}
if (response?.data?.id) {
navigate(`/${response.data.id}`);
}
} catch (error) {
console.error("Project creation failed:", error);
}
};
if (!id) {
createEmptyProject();
}
}, [id, navigate, toast]);
// upload bg-image handler
const { mutate: uploadBackgroundImage } = useMutation({
mutationFn: async ({ file, id }) => {
return await uploadImage({ file, id });
},
onSuccess: (data) => {
if (data?.status === 200) {
toast({
title: data?.status,
description: data?.message
});
setBgImage(data?.data[0]?.url);
// Create an image element
const imgElement = new Image();
// Set the crossOrigin attribute BEFORE setting the src
imgElement.crossOrigin = "anonymous"; // This ensures CORS headers are sent
imgElement.src = data?.data[0]?.url;
imgElement.onload = () => {
// Create a fabric.Image instance
const img = new fabric.Image(imgElement, {
scaleX: canvas.width / imgElement.width,
scaleY: canvas.height / imgElement.height,
});
// Set the background image on the canvas
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
};
imgElement.onerror = (error) => {
console.error('Failed to load image:', error);
toast({
variant: "destructive",
title: "Image Load Error",
description: "Failed to load the image. Please try again."
})
};
}
else {
toast({
variant: "destructive",
title: data?.status,
description: data?.message
});
}
}
})
// handle bg-image remove
const { mutate: removeBackgroundMutate } = useMutation({
mutationFn: async (url) => {
return await deleteImage(url);
},
onSuccess: (data) => {
if (data?.status === 200) {
toast({
title: data?.status,
description: data?.message
})
canvas.backgroundImage = null;
canvas.renderAll();
setBgImage(null);
}
else {
toast({
variant: "destructive",
title: data?.status,
description: data?.message
})
}
}
});
// upload bg-overLayImage handler
const { mutate: uploadOverlayImage } = useMutation({
mutationFn: async ({ file, id }) => {
return await uploadImage({ file, id });
},
onSuccess: (data) => {
if (data?.status === 200) {
toast({
title: data?.status,
description: data?.message
});
setBgOverLayImage(data?.data[0]?.url);
// Create an image element
const imgElement = new Image();
// Set the crossOrigin attribute BEFORE setting the src
imgElement.crossOrigin = "anonymous"; // This ensures CORS headers are sent
imgElement.src = data?.data[0]?.url;
imgElement.onload = () => {
// Create a fabric.Image instance
const img = new fabric.Image(imgElement, {
scaleX: canvas.width / imgElement.width,
scaleY: canvas.height / imgElement.height,
});
// Set the background image on the canvas
canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
};
imgElement.onerror = (error) => {
console.error('Failed to load image:', error);
toast({
variant: "destructive",
title: "Image Load Error",
description: "Failed to load the image. Please try again."
})
};
}
else {
toast({
variant: "destructive",
title: data?.status,
description: data?.message
});
}
}
})
// handle bg-overLayImage remove
const { mutate: removeOverLayMutate } = useMutation({
mutationFn: async (url) => {
return await deleteImage(url);
},
onSuccess: (data) => {
if (data?.status === 200) {
toast({
title: data?.status,
description: data?.message
})
canvas.overlayImage = null;
canvas.renderAll();
setBgOverLayImage(null);
}
else {
toast({
variant: "destructive",
title: data?.status,
description: data?.message
})
}
}
});
const setBackgroundImage = (e) => {
const file = e.target.files[0];
if (file) {
uploadBackgroundImage({ file, id })
}
else {
toast({
variant: "destructive",
title: "Error",
description: "Please select a file",
})
}
};
const setBackgroundOverlayImage = (e) => {
const file = e.target.files[0];
if (file) {
uploadOverlayImage({ file, id })
}
else {
toast({
variant: "destructive",
title: "Error",
description: "Please select a file",
})
}
};
const adjustBackgroundOpacity = (value) => {
if (canvas) {
if (canvas.backgroundImage) {
// Update the opacity of the background image if it exists
canvas.backgroundImage.set("opacity", value);
} else if (canvas.overlayImage) {
// Update the opacity of the overlay image if it exists
canvas.overlayImage.set("opacity", value);
} else {
console.error("No background or overlay image found on the canvas.");
return;
}
// Re-render the canvas to apply changes
canvas.renderAll();
} else {
console.error("Canvas is not initialized.");
}
};
const removeBackgroundImage = () => {
if (canvas) {
const bgUrl = canvas.backgroundImage?.getSrc();
console.log("background image from remove", bgUrl)
if (bgUrl) {
removeBackgroundMutate(bgUrl)
}
else {
toast({
variant: "destructive",
title: "Error",
description: "No background image found",
})
}
}
};
const removeBackgroundOverlayImage = () => {
if (canvas) {
const overLayUrl = canvas.overlayImage?.getSrc();
console.log("overlay image from remove", overLayUrl);
if (overLayUrl) {
removeOverLayMutate(overLayUrl);
}
else {
toast({
variant: "destructive",
title: "Error",
description: "No overlay image found",
})
}
}
};
useEffect(() => {
if (canvas) {
const bgUrl = canvas.backgroundImage?.getSrc();
setBgImage(bgUrl);
const overLayUrl = canvas.overlayImage?.getSrc();
setBgOverLayImage(overLayUrl);
}
}, [canvas, setBgImage, setBgOverLayImage])
return (
<div>
<Card className="xl:p-0 lg:p-0 md:p-0 p-2 border-none shadow-none">
<CardTitle className="flex items-center flex-wrap justify-between gap-1 xl:hidden lg:hidden md:hidden">
Canvas Setting
</CardTitle>
<Separator className="mt-4 block xl:hidden lg:hidden md:hidden" />
<ScrollArea className="h-[400px] xl:h-fit lg:h-fit md:h-fit">
<div className="rnd-escape">
<ColorComponent />
<Separator className="mt-2" />
<div className="flex flex-col my-2 gap-2 rnd-escape">
<div>
<Label>Background:</Label>
{
bgImage && <img src={bgImage} alt="canvas_bg_image" className="rounded-md mb-2" />
}
<div className="flex items-center w-fit gap-2 flex-wrap relative">
{
!bgImage &&
<Button className="top-0 absolute flex items-center w-[30px]">
<UploadIcon className="cursor-pointer" />
<Input
className="absolute top-0 opacity-0"
type="file"
accept="image/*"
onChange={setBackgroundImage}
/>
</Button>
}
{
bgImage &&
<Button
variant="secondary"
onClick={removeBackgroundImage}
>
<Trash2 />
</Button>
}
</div>
</div>
{
bgImage ? <Separator className="mt-4" /> : <Separator className="mt-12" />
}
<div>
<Label>Background Overlay:</Label>
{
bgOverLayImage && <img src={bgOverLayImage} alt="canvas_bgOverLay_image" className="rounded-md mb-2" />
}
<div className="flex items-center w-fit gap-2 flex-wrap relative">
{
!bgOverLayImage &&
<Button className="top-0 absolute flex items-center w-[30px] mb">
<UploadIcon className="cursor-pointer" />
<Input
className="absolute top-0 opacity-0"
type="file"
accept="image/*"
onChange={setBackgroundOverlayImage}
/>
</Button>
}
{
bgOverLayImage &&
<Button
variant="secondary"
onClick={removeBackgroundOverlayImage}
>
<Trash2 />
</Button>
}
</div>
</div>
{
bgOverLayImage ? <Separator className="mt-4" /> : <Separator className="mt-12" />
}
{/* opacity */}
<div className="flex flex-col gap-2 rnd-escape mt-2">
<Label>Background Opacity:</Label>
<Slider
defaultValue={[1.0]} // Default value, you can set it to 0.0 or another value
min={0.0}
max={1.0}
step={0.01} // Step size for fine control
onValueChange={(value) => {
const newOpacity = value[0]; // Extract slider value
adjustBackgroundOpacity(newOpacity); // Adjust Fabric.js background opacity
}}
/>
</div>
</div>
<Separator className="mt-4" />
{/* Save canvas Component */}
{
id &&
<SaveCanvas />
}
</div>
</ScrollArea>
</Card>
</div>
);
};
export default CanvasSetting;