396 lines
19 KiB
JavaScript
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>
|
|
)
|
|
}
|