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 (
{multipleObjects && ( } label="Group" onClick={groupSelectedObjects} tooltipContent={

Group selected objects

To select multiple objects:

  1. Hold down the Shift key
  2. Click and drag with the left mouse button to select objects
  3. Release the Shift key and mouse button

Then click this button to group the selected objects.

} /> )} {isGroupObject && ( } label="Ungroup" onClick={ungroupSelectedObjects} tooltipContent="Ungroup selected objects" /> )} } label="Duplicate" onClick={duplicating} tooltipContent="Duplicate selected objects" />

Change object position

} label="Remove" onClick={removeSelected} tooltipContent="Remove selected objects" /> } label="Clear" onClick={clearCanvas} tooltipContent="Clear entire canvas" />
); }; function ActionButton({ icon, label, onClick, tooltipContent }) { return (

{tooltipContent}

); }