canvas-backend/src/components/EditPanel.jsx
2024-12-30 14:37:50 +06:00

396 lines
19 KiB
JavaScript

import { useCallback, useContext, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { BringToFront, CopyPlus, GroupIcon, PencilRuler, Save, Settings, Shapes, SquareX, Store, Trash2, UngroupIcon, Upload, ChevronDown, ChevronUp, MoveIcon, Type, ImageDown, X } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import OpenContext from './Context/openContext/OpenContext';
import CanvasContext from './Context/canvasContext/CanvasContext';
import ActiveObjectContext from './Context/activeObject/ObjectContext';
import { fabric } from 'fabric';
import RndComponent from './Layouts/RndComponent';
export function EditPanel() {
const [isCollapsed, setIsCollapsed] = useState(false);
const { setTabValue, setOpenSetting, setOpenObjectPanel, setCaptureOpen, setOpenPanel } = useContext(OpenContext);
const { canvas } = useContext(CanvasContext);
const { setActiveObject, activeObject } = useContext(ActiveObjectContext);
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();
} else {
console.log("Select at least two objects")
}
};
const ungroupSelectedObjects = () => {
const activeObject = canvas.getActiveObject();
if (activeObject && activeObject.type === "group") {
// Get the group
const group = activeObject;
canvas.discardActiveObject();
// Remove the group from the canvas
canvas.remove(group);
setActiveObject(null);
// Iterate through each object in the group
group._objects.forEach((object) => {
// Calculate the absolute position based on the group's transformation
const objLeft = object.left * group.scaleX + group.left;
const objTop = object.top * group.scaleY + group.top;
// Reset transformations and positions
object.set({
left: objLeft,
top: objTop,
scaleX: object.scaleX * group.scaleX, // Adjust scale based on group
scaleY: object.scaleY * group.scaleY, // Adjust scale based on group
angle: object.angle + group.angle, // Adjust rotation
hasControls: true, // Allow resizing
selectable: true, // Make selectable
group: null, // Remove group reference
originX: "center",
originY: "center",
});
// Update coordinates
object.setCoords();
// Add the object back to the canvas
canvas.add(object);
});
// Clear the selection
canvas.discardActiveObject();
// Render the canvas to reflect changes
canvas.renderAll();
}
};
// Bring Selected Object to Front
const bringToFront = () => {
if (activeObject) {
activeObject.bringToFront();
canvas.renderAll();
}
};
// 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);
}
if (activeObject && textObjects?.length === 1) {
canvas.remove(activeObject);
setActiveObject(null);
}
if (activeObject.length > 1) {
canvas.remove(...activeObject);
setActiveObject(null);
}
}, [canvas, setActiveObject]);
const saveCanvasState = () => {
// Get the JSON representation of all objects
const json = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need
console.log(json);
// Get background image data if it exists
let backgroundImageData = null;
if (canvas.backgroundImage) {
backgroundImageData = {
src: canvas.backgroundImage._element.src,
width: canvas.backgroundImage.width,
height: canvas.backgroundImage.height,
scaleX: canvas.backgroundImage.scaleX,
scaleY: canvas.backgroundImage.scaleY,
originX: canvas.backgroundImage.originX,
originY: canvas.backgroundImage.originY,
opacity: canvas.backgroundImage.opacity
};
}
// Create the complete canvas state
const canvasState = {
version: '1.0',
objects: json.objects,
background: backgroundImageData,
width: canvas.width,
height: canvas.height,
backgroundColor: canvas.backgroundColor
};
console.log('Canvas state saved:', canvasState);
// loadCanvasState(canvasState);
};
// 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();
});
}
// for clear canvas
const clearCanvas = () => {
canvas.clear();
canvas.renderAll();
}
const addText = () => {
if (canvas) {
const text = new fabric.IText('Editable Text', {
left: 100,
top: 100,
fontFamily: 'Poppins',
fontSize: 16,
});
// Add the text to the canvas and re-render
canvas.add(text);
// canvas.clipPath = text;
canvas.setActiveObject(text);
setActiveObject(text);
canvas.renderAll();
}
};
const rndValue = {
valueX: 0,
valueY: 20,
width: 250,
height: 0,
minWidth: 250,
maxWidth: 300,
minHeight: 0,
maxHeight: 0,
bound: "parent"
}
return (
<RndComponent value={rndValue}>
<Card className="w-full shadow-lg">
<CardHeader className="p-1 mr-12">
<div className="flex items-center justify-between">
<Button className="rnd-escape" variant={"ghost"} onClick={() => setOpenPanel(false)}><X /></Button>
<CardTitle className="text-lg">Edit Panel</CardTitle>
</div>
</CardHeader>
<Collapsible open={!isCollapsed} onOpenChange={(open) => setIsCollapsed(!open)}>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="absolute top-1 right-2 rnd-escape">
{isCollapsed ? <ChevronDown className="h-4 w-4" /> : <ChevronUp className="h-4 w-4" />}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="rnd-escape">
<CardContent className="p-2">
<Tabs defaultValue="edit" className="w-full">
<TabsList className="w-full flex justify-around gap-2">
<TabsTrigger value="edit">Edit</TabsTrigger>
<TabsTrigger value="add" className="block xl:hidden lg:hidden md:hidden">Add</TabsTrigger>
<TabsTrigger value="canvas">Canvas</TabsTrigger>
</TabsList>
<TabsContent value="edit" className="mt-2">
<TooltipProvider>
<div className="grid grid-cols-3 gap-2">
<ActionButton
icon={<GroupIcon className="h-4 w-4" />}
label="Group"
onClick={groupSelectedObjects}
tooltipContent={
<div className="text-sm">
<p className="font-semibold mb-1">Group selected objects</p>
<p>To select multiple objects:</p>
<ol className="list-decimal list-inside mt-1">
<li>Hold down the Shift key</li>
<li>Click and drag with the left mouse button to select objects</li>
<li>Release the Shift key and mouse button</li>
</ol>
<p className="mt-1">Then click this button to group the selected objects.</p>
</div>
}
/>
<ActionButton
icon={<UngroupIcon className="h-4 w-4" />}
label="Ungroup"
onClick={ungroupSelectedObjects}
tooltipContent="Ungroup selected objects"
/>
<ActionButton
icon={<CopyPlus className="h-4 w-4" />}
label="Duplicate"
onClick={duplicating}
tooltipContent="Duplicate selected objects"
/>
<ActionButton
icon={<BringToFront className="h-4 w-4" />}
label="To Front"
onClick={bringToFront}
tooltipContent="Bring selected objects to front"
/>
<ActionButton
icon={<MoveIcon className="h-4 w-4" />}
label="Move"
onClick={() => { }}
tooltipContent="Move selected objects"
/>
<ActionButton
icon={<Trash2 className="h-4 w-4" />}
label="Remove"
onClick={removeSelected}
tooltipContent="Remove selected objects"
/>
</div>
</TooltipProvider>
</TabsContent>
<TabsContent value="add" className="mt-2">
<TooltipProvider>
<div className="grid grid-cols-3 gap-2">
<ActionButton
icon={<Store className="h-4 w-4" />}
label="Icons"
onClick={() => {
setOpenObjectPanel(true);
setTabValue("icons");
}}
tooltipContent="Add icons to canvas"
/>
<ActionButton
icon={<Shapes className="h-4 w-4" />}
label="Shapes"
onClick={() => {
setTabValue("shapes");
setOpenObjectPanel(true);
}}
tooltipContent="Add shapes to canvas"
/>
<ActionButton
icon={<Type className="h-4 w-4" />}
label="Text"
onClick={() => {
addText();
setTabValue("customize"); setOpenObjectPanel(true);
}}
tooltipContent="Add text to canvas"
/>
<ActionButton
icon={<Upload className="h-4 w-4" />}
label="Image"
onClick={() => {
setTabValue("images"); setOpenObjectPanel(true);
}}
tooltipContent="Upload and add image to canvas"
/>
<ActionButton
icon={<PencilRuler className="h-4 w-4" />}
label="Customize"
onClick={() => {
setTabValue("customize"); setOpenObjectPanel(true);
}}
tooltipContent="Customize objects on canvas"
/>
</div>
</TooltipProvider>
</TabsContent>
<TabsContent value="canvas" className="mt-2">
<TooltipProvider>
<div className="grid grid-cols-3 gap-2">
<ActionButton
icon={<Save className="h-4 w-4" />}
label="Save"
onClick={saveCanvasState}
tooltipContent="Save current canvas state"
/>
<ActionButton
icon={<Settings className="h-4 w-4" />}
label="Settings"
onClick={() => setOpenSetting(true)}
tooltipContent="Open canvas settings"
/>
<ActionButton
icon={<SquareX className="h-4 w-4" />}
label="Clear"
onClick={clearCanvas}
tooltipContent="Clear entire canvas"
/>
<ActionButton
icon={<ImageDown className="h-4 w-4" />}
label="Capture"
onClick={() => setCaptureOpen(true)}
tooltipContent="Capture canvas"
/>
</div>
</TooltipProvider>
</TabsContent>
</Tabs>
</CardContent>
</CollapsibleContent>
</Collapsible>
</Card>
</RndComponent>
)
}
function ActionButton({ icon, label, onClick, tooltipContent }) {
return (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="md" className="w-full" onClick={onClick}>
<div className="flex flex-col items-center gap-0 p-1">
{icon}
<span className="text-xs">{label}</span>
</div>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" align="center" className="max-w-xs">
<p>{tooltipContent}</p>
</TooltipContent>
</Tooltip>
)
}