import { useCallback, useContext, useMemo, useState } from "react"; import CanvasContext from "./Context/canvasContext/CanvasContext"; import ActiveObjectContext from "./Context/activeObject/ObjectContext"; import { fabric } from "fabric"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./ui/tooltip"; import { Button } from "./ui/button"; import { BringToFront, SendToBack, CopyPlus, GroupIcon, SquareX, Trash2, UngroupIcon, Layers, } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { useMutation } from "@tanstack/react-query"; import { deleteImage } from "../api/uploadApi"; import { toast } from "../hooks/use-toast"; import useProject from "@/hooks/useProject"; export const ObjectShortcut = ({ value }) => { const { canvas } = useContext(CanvasContext); const { setActiveObject, activeObject } = useContext(ActiveObjectContext); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const activeObjects = canvas.getActiveObjects(); const multipleObjects = activeObjects.length > 1; const objectActive = canvas.getActiveObject(); const isGroupObject = objectActive && objectActive.type === "group"; const { projectData, projectUpdate, id } = useProject(); const groupSelectedObjects = () => { const activeObjects = canvas.getActiveObjects(); // Get selected objects if (activeObjects.length > 1) { canvas.discardActiveObject(); const group = new fabric.Group(activeObjects, { left: canvas?.width / 2, top: canvas?.height / 2, originX: "center", originY: "center", selectable: true, // Allow group selection subTargetCheck: true, // Allow individual object selection hasControls: true, // Enable resizing/movement of the group }); canvas.remove(...activeObjects); canvas.add(group); canvas.setActiveObject(group); setActiveObject(group); canvas.renderAll(); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } else { toast({ title: "Select at least two objects", description: "Please select at least two objects to group.", variant: "destructive", }) } }; const ungroupSelectedObjects = () => { const activeObject = canvas.getActiveObject(); if (activeObject && activeObject.type === "group") { const groupObjects = activeObject._objects; canvas.discardActiveObject(); canvas.remove(activeObject); setActiveObject(null); const ungroupedObjects = []; groupObjects.forEach((object) => { // Calculate absolute position const objLeft = object.left * activeObject.scaleX + activeObject.left; const objTop = object.top * activeObject.scaleY + activeObject.top; object.set({ left: objLeft, top: objTop, scaleX: object.scaleX * activeObject.scaleX, scaleY: object.scaleY * activeObject.scaleY, angle: object.angle + activeObject.angle, hasControls: true, selectable: true, group: null, originX: "center", originY: "center", }); object.setCoords(); canvas.add(object); ungroupedObjects.push(object); }); const selection = new fabric.ActiveSelection(ungroupedObjects, { canvas: canvas, originX: "center", originY: "center", }); canvas.setActiveObject(selection); setActiveObject(selection); canvas.renderAll(); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } }; // Check if object is at front or back const objectPosition = useMemo(() => { if (!activeObject || !canvas) { return { isAtFront: false, isAtBack: false }; } const allObjects = canvas.getObjects(); const index = allObjects.indexOf(activeObject); return { isAtFront: index === allObjects.length - 1, isAtBack: index === 0, }; }, [activeObject, canvas]); // Layer ordering functions with checks const bringToFront = () => { if (activeObject && !objectPosition.isAtFront) { activeObject.bringToFront(); canvas.renderAll(); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); setIsPopoverOpen(false); } }; const sendToBack = () => { if (activeObject && !objectPosition.isAtBack) { activeObject.sendToBack(); canvas.renderAll(); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); setIsPopoverOpen(false); } }; const { mutate: deleteMutate } = useMutation({ mutationFn: async (url) => { return await deleteImage(url); }, onSuccess: (data) => { if (data?.status === 200) { toast({ title: data?.status, description: data?.message }) if (canvas) { const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } } else { toast({ variant: "destructive", title: data?.status, description: data?.message }) } } }); // Remove Selected Element const removeSelected = useCallback(() => { const activeObject = canvas?.getActiveObject(); const allObjects = canvas?.getObjects(); const textObjects = allObjects.filter( (obj) => obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" ); if (activeObject) { canvas.remove(activeObject); setActiveObject(null); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } if (activeObject && textObjects?.length === 1) { canvas.remove(activeObject); setActiveObject(null); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } if (activeObject.length > 1) { canvas.remove(...activeObject); setActiveObject(null); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); } if (activeObject?.type === "image") { const imgUrl = activeObject?._originalElement?.currentSrc; canvas.remove(activeObject); setActiveObject(null); canvas.renderAll(); deleteMutate(imgUrl); } }, [canvas, setActiveObject, deleteMutate, id, projectData, projectUpdate]); // duplicating current objects const duplicating = () => { // Clone the active object to create a true deep copy activeObject.clone((clonedObject) => { // Add the cloned object to the canvas clonedObject.set("left", clonedObject?.left + 30); canvas.add(clonedObject); canvas.renderAll(); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); }); }; // for clear canvas const clearCanvas = () => { canvas.clear(); canvas.renderAll(); canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas)); setActiveObject(null); const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need const updateData = { ...projectData?.data, object }; // Wait for the project update before continuing projectUpdate({ id, updateData }); }; return (
Group selected objects
To select multiple objects:
Then click this button to group the selected objects.
Change object position
{tooltipContent}