From 954ac950b0105b15b03c21ce24b430630a45f6c0 Mon Sep 17 00:00:00 2001 From: smfahim25 Date: Sun, 26 Jan 2025 17:26:25 +0600 Subject: [PATCH] added text section design layout --- src/App.jsx | 12 +- src/components/ActionButtons.jsx | 84 +- .../canvasContext/CanvasContextProvider.jsx | 49 +- .../Customization/AddImageIntoShape.jsx | 633 ++++++++------- .../Customization/CollapsibleComponent.jsx | 36 +- .../Customization/LockObject.jsx | 125 +-- .../Customization/PositionCustomization.jsx | 292 ++++--- .../Customization/ScaleObjects.jsx | 156 ++-- .../Customization/SelectObjectFromGroup.jsx | 290 +++---- .../Customization/ShadowCustomization.jsx | 246 +++--- .../Customization/SkewCustomization.jsx | 156 ++-- .../Customization/StrokeCustomization.jsx | 736 +++++++++--------- .../Customization/TextCustomization.jsx | 644 ++++++++------- src/components/Layouts/LeftSidebar.jsx | 29 +- src/components/Layouts/RightPanel.jsx | 28 - src/components/Panel/Canvas.jsx | 149 +++- src/components/Panel/CommonPanel.jsx | 65 ++ src/components/Panel/DesignPanel.jsx | 38 - src/components/Panel/EditorPanel.jsx | 72 +- src/components/Panel/ShapePanel.jsx | 20 - src/components/Panel/TextPanel.jsx | 118 ++- 21 files changed, 2156 insertions(+), 1822 deletions(-) delete mode 100644 src/components/Layouts/RightPanel.jsx create mode 100644 src/components/Panel/CommonPanel.jsx delete mode 100644 src/components/Panel/DesignPanel.jsx delete mode 100644 src/components/Panel/ShapePanel.jsx diff --git a/src/App.jsx b/src/App.jsx index e697031..e12cd48 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import "./App.css"; // import Canvas from "./components/Canvas"; import WebFont from "webfontloader"; @@ -8,12 +8,12 @@ import SheetLeftPanel from "./components/Layouts/SheetLeftPanel"; import CanvasCapture from "./components/CanvasCapture"; import { Toaster } from "./components/ui/toaster"; import { Sidebar } from "./components/Layouts/LeftSidebar"; -import RightPanel from "./components/Layouts/RightPanel"; -import { EditorPanel } from "./components/Panel/EditorPanel"; import { Canvas } from "./components/Panel/Canvas"; import TextPanel from "./components/Panel/TextPanel"; import { TopBar } from "./components/Panel/TopBar"; import { ActionButtons } from "./components/ActionButtons"; +import EditorPanel from "./components/Panel/EditorPanel"; +import CanvasContext from "./components/Context/canvasContext/CanvasContext"; function App() { useEffect(() => { @@ -74,7 +74,7 @@ function App() { }); }, []); - const [selectedItem, setSelectedItem] = useState("text"); + const { selectedPanel } = useContext(CanvasContext); const [hasSelectedObject, setHasSelectedObject] = useState(true); return ( @@ -93,8 +93,8 @@ function App() { //
- - {selectedItem === "text" && } + + {selectedPanel !== "" && }
diff --git a/src/components/ActionButtons.jsx b/src/components/ActionButtons.jsx index 3532a02..d4e6491 100644 --- a/src/components/ActionButtons.jsx +++ b/src/components/ActionButtons.jsx @@ -1,44 +1,58 @@ -import { ChevronDown } from "lucide-react"; +import CanvasContext from "./Context/canvasContext/CanvasContext"; import { Button } from "./ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./ui/select"; +import { useContext } from "react"; + +const aspectRatios = [ + { value: "1:1", label: "Square (1:1)" }, + { value: "4:3", label: "Standard (4:3)" }, + { value: "3:2", label: "Classic (3:2)" }, + { value: "16:9", label: "Widescreen (16:9)" }, + { value: "9:16", label: "Portrait (9:16)" }, + { value: "21:9", label: "Ultrawide (21:9)" }, + { value: "32:9", label: "Super Ultrawide (32:9)" }, + { value: "1.85:1", label: "Cinema Standard (1.85:1)" }, + { value: "2.39:1", label: "Anamorphic Widescreen (2.39:1)" }, + { value: "2.76:1", label: "Ultra Panavision 70 (2.76:1)" }, + { value: "5:4", label: "Large Format (5:4)" }, + { value: "7:5", label: "Artistic Format (7:5)" }, + { value: "11:8.5", label: "Letter Size (11:8.5)" }, + { value: "3:4", label: "Portrait (3:4)" }, + { value: "1.91:1", label: "Facebook Ads (1.91:1)" }, +]; export function ActionButtons() { + const { setCanvasRatio, canvasRatio } = useContext(CanvasContext); + const handleRatioChange = (newRatio) => { + setCanvasRatio(newRatio); + }; return (
- +
+ +
- - -
- {features.map((feature, index) => ( -
- -
-

{feature.title}

-

{feature.description}

-
-
- ))} -
-
- - - - - - - {errorMessage && ( -

{errorMessage}

- )} -
-
- {/* Width Slider */} -
- - setWidth(value[0])} - /> -
- - {/* Height Slider */} -
- - setHeight(value[0])} - /> -
- - {/* Quality Slider */} - { - format === "JPEG" && -
- - setQuality(value[0])} - /> -
- } - -
- {/* Rotation */} -
- - - -
- - {/* Format Dropdown */} -
- - -
-
-
- - -
-
- ) - } - - return ( - - - {content()} - + + +
+ {features.map((feature, index) => ( +
+ +
+

+ {feature.title} +

+

+ {feature.description} +

+
+
+ ))} +
+
+ + + +
+ + {errorMessage && ( +

{errorMessage}

+ )} +
+
+ {/* Width Slider */} +
+ + setWidth(value[0])} + /> +
+ + {/* Height Slider */} +
+ + setHeight(value[0])} + /> +
+ + {/* Quality Slider */} + {format === "JPEG" && ( +
+ + setQuality(value[0])} + /> +
+ )} + +
+ {/* Rotation */} +
+ + + +
+ + {/* Format Dropdown */} +
+ + +
+
+
+ + +
+
); + }; + + return ( + + + {content()} + + + ); }; export default AddImageIntoShape; - - - diff --git a/src/components/EachComponent/Customization/CollapsibleComponent.jsx b/src/components/EachComponent/Customization/CollapsibleComponent.jsx index 80e6c60..edd626a 100644 --- a/src/components/EachComponent/Customization/CollapsibleComponent.jsx +++ b/src/components/EachComponent/Customization/CollapsibleComponent.jsx @@ -1,6 +1,8 @@ -import { Button } from "@/components/ui/button"; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { ChevronUp, ChevronDown } from "lucide-react"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { useEffect, useState } from "react"; const CollapsibleComponent = ({ children, text }) => { @@ -10,27 +12,25 @@ const CollapsibleComponent = ({ children, text }) => { // Check if the text prop is "Canvas Setting" and set isOpen to false if (text === "Canvas Setting") { setIsOpen(false); + } else { + setIsOpen(true); } - else { - setIsOpen(true) - } - }, [text]) + }, [text]); return ( -
-

{text}

- +
+

{text}

- - {children} - + {children} - ) -} + ); +}; -export default CollapsibleComponent \ No newline at end of file +export default CollapsibleComponent; diff --git a/src/components/EachComponent/Customization/LockObject.jsx b/src/components/EachComponent/Customization/LockObject.jsx index 44dc16b..8d0ddc9 100644 --- a/src/components/EachComponent/Customization/LockObject.jsx +++ b/src/components/EachComponent/Customization/LockObject.jsx @@ -1,68 +1,73 @@ -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; -import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; -import { Button } from '@/components/ui/button'; -import { useToast } from '@/hooks/use-toast'; -import { useContext, useEffect, useState } from 'react'; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Button } from "@/components/ui/button"; +import { useToast } from "@/hooks/use-toast"; +import { useContext, useEffect, useState } from "react"; import { Lock, Unlock } from "lucide-react"; -import { Card } from '@/components/ui/card'; +import { Card } from "@/components/ui/card"; const LockObject = () => { - const { canvas } = useContext(CanvasContext); - const { activeObject } = useContext(ActiveObjectContext); - const [isLocked, setIsLocked] = useState(false); - const { toast } = useToast(); + const { canvas } = useContext(CanvasContext); + const { activeObject } = useContext(ActiveObjectContext); + const [isLocked, setIsLocked] = useState(false); + const { toast } = useToast(); - useEffect(() => { - if (activeObject?.lockMovementX === false) { - setIsLocked(false); - } - else { - setIsLocked(true); - } - }, [activeObject]) + useEffect(() => { + if (activeObject?.lockMovementX === false) { + setIsLocked(false); + } else { + setIsLocked(true); + } + }, [activeObject]); - const toggleLock = () => { - if (!canvas || !activeObject) { - toast({ - title: "No object selected", - description: "Please select an object to lock or unlock.", - variant: "destructive", - }) - return; - } - - const newLockState = !activeObject.lockMovementX; - activeObject.set({ - lockMovementX: newLockState, - lockMovementY: newLockState, - lockRotation: newLockState, - lockScalingX: newLockState, - lockScalingY: newLockState, - }); - - setIsLocked(newLockState); - canvas.requestRenderAll(); - - toast({ - title: newLockState ? "Object locked" : "Object unlocked", - description: newLockState ? "The object is now locked in place." : "The object can now be moved and resized.", - }) + const toggleLock = () => { + if (!canvas || !activeObject) { + toast({ + title: "No object selected", + description: "Please select an object to lock or unlock.", + variant: "destructive", + }); + return; } - return ( - -

{!isLocked ? "Lock" : "Unlock"} Object

- -
- ) -} + const newLockState = !activeObject.lockMovementX; + activeObject.set({ + lockMovementX: newLockState, + lockMovementY: newLockState, + lockRotation: newLockState, + lockScalingX: newLockState, + lockScalingY: newLockState, + }); -export default LockObject \ No newline at end of file + setIsLocked(newLockState); + canvas.requestRenderAll(); + + toast({ + title: newLockState ? "Object locked" : "Object unlocked", + description: newLockState + ? "The object is now locked in place." + : "The object can now be moved and resized.", + }); + }; + + return ( + +

{!isLocked ? "Lock" : "Unlock"} Object

+ +
+ ); +}; + +export default LockObject; diff --git a/src/components/EachComponent/Customization/PositionCustomization.jsx b/src/components/EachComponent/Customization/PositionCustomization.jsx index ce66f7a..3de9969 100644 --- a/src/components/EachComponent/Customization/PositionCustomization.jsx +++ b/src/components/EachComponent/Customization/PositionCustomization.jsx @@ -1,162 +1,158 @@ -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; -import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; -import { Card, CardContent } from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Slider } from '@/components/ui/slider'; -import { useContext, useEffect, useState } from 'react'; -import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight, ChevronUp, ChevronDown } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import CollapsibleComponent from './CollapsibleComponent'; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Slider } from "@/components/ui/slider"; +import { useContext, useEffect, useState } from "react"; +import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import CollapsibleComponent from "./CollapsibleComponent"; const PositionCustomization = () => { - const { canvas } = useContext(CanvasContext) - const { activeObject } = useContext(ActiveObjectContext) + const { canvas } = useContext(CanvasContext); + const { activeObject } = useContext(ActiveObjectContext); - const [objectPosition, setObjectPosition] = useState({ left: 50, top: 50 }) + const [objectPosition, setObjectPosition] = useState({ left: 50, top: 50 }); - const [isOpen, setIsOpen] = useState(true); - - useEffect(() => { - if (activeObject) { - setObjectPosition({ - left: Math.round(activeObject.left || 0), - top: Math.round(activeObject.top || 0), - }) - } - }, [activeObject]) - - const updateObjectPosition = (key, value) => { - const updatedPosition = { ...objectPosition, [key]: value } - setObjectPosition(updatedPosition) - - if (canvas && activeObject) { - activeObject.set(updatedPosition) - canvas.renderAll() - } + useEffect(() => { + if (activeObject) { + setObjectPosition({ + left: Math.round(activeObject.left || 0), + top: Math.round(activeObject.top || 0), + }); } + }, [activeObject]); - const handleInputChange = (key, value) => { - const numValue = parseInt(value, 10) - if (!isNaN(numValue)) { - updateObjectPosition(key, numValue) - } + const updateObjectPosition = (key, value) => { + const updatedPosition = { ...objectPosition, [key]: value }; + setObjectPosition(updatedPosition); + + if (canvas && activeObject) { + activeObject.set(updatedPosition); + canvas.renderAll(); } + }; - const handleSliderChange = (key, value) => { - updateObjectPosition(key, value[0]) + const handleInputChange = (key, value) => { + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + updateObjectPosition(key, numValue); } + }; - const adjustPosition = (key, delta) => { - const newValue = objectPosition[key] + delta - updateObjectPosition(key, newValue) - } + const handleSliderChange = (key, value) => { + updateObjectPosition(key, value[0]); + }; - const content = () => { - return ( - -
-
- -
- handleInputChange('left', e.target.value)} - className="w-20" - /> - handleSliderChange('left', value)} - className="flex-grow" - /> -
-
-
- -
- handleInputChange('top', e.target.value)} - className="w-20" - /> - handleSliderChange('top', value)} - className="flex-grow" - /> -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- ) - } + const adjustPosition = (key, delta) => { + const newValue = objectPosition[key] + delta; + updateObjectPosition(key, newValue); + }; + const content = () => { return ( - - - {content()} - - - ) -} + +
+
+ +
+ handleInputChange("left", e.target.value)} + className="w-20" + /> + handleSliderChange("left", value)} + className="flex-grow" + /> +
+
+
+ +
+ handleInputChange("top", e.target.value)} + className="w-20" + /> + handleSliderChange("top", value)} + className="flex-grow" + /> +
+
+
-export default PositionCustomization \ No newline at end of file +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); + }; + + return ( + + + {content()} + + + ); +}; + +export default PositionCustomization; diff --git a/src/components/EachComponent/Customization/ScaleObjects.jsx b/src/components/EachComponent/Customization/ScaleObjects.jsx index f7117be..248e8e8 100644 --- a/src/components/EachComponent/Customization/ScaleObjects.jsx +++ b/src/components/EachComponent/Customization/ScaleObjects.jsx @@ -6,89 +6,89 @@ import { useContext, useEffect, useState } from "react"; import CollapsibleComponent from "./CollapsibleComponent"; const ScaleObjects = () => { - const { canvas } = useContext(CanvasContext); // Access Fabric.js canvas from context - const { activeObject } = useContext(ActiveObjectContext); - const [scaleX, setScaleX] = useState(1); - const [scaleY, setScaleY] = useState(1); + const { canvas } = useContext(CanvasContext); // Access Fabric.js canvas from context + const { activeObject } = useContext(ActiveObjectContext); + const [scaleX, setScaleX] = useState(1); + const [scaleY, setScaleY] = useState(1); - useEffect(() => { - if (activeObject) { - setScaleX(activeObject?.scaleX); - setScaleY(activeObject?.scaleY); - } - }, [activeObject]) - - // Handle scaleX changes - const handleScaleXChange = (value) => { - const newScaleX = Math.max(0.001, Math.min(value)); // Clamp scaleX between 0.001 and 3 - setScaleX(newScaleX); - if (canvas && activeObject) { - activeObject.scaleX = newScaleX; - canvas.discardActiveObject(); - canvas.setActiveObject(activeObject); - canvas.renderAll(); // Re-render the canvas - } - }; - - // Handle scaleY changes - const handleScaleYChange = (value) => { - const newScaleY = Math.max(0.001, Math.min(value)); // Clamp scaleY between 0.001 and 3 - setScaleY(newScaleY); - if (canvas && activeObject) { - activeObject.scaleY = newScaleY; - canvas.discardActiveObject(); - canvas.setActiveObject(activeObject); - canvas.renderAll(); // Re-render the canvas - } - }; - - const content = () => { - return ( -
- {/* Scale X Input */} -
- - { - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - handleScaleXChange(inputValue); - } - }} - /> -
- - {/* Scale Y Input */} -
- - { - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - handleScaleYChange(inputValue); - } - }} - /> -
-
- ) + useEffect(() => { + if (activeObject) { + setScaleX(activeObject?.scaleX); + setScaleY(activeObject?.scaleY); } + }, [activeObject]); + // Handle scaleX changes + const handleScaleXChange = (value) => { + const newScaleX = Math.max(0.001, Math.min(value)); // Clamp scaleX between 0.001 and 3 + setScaleX(newScaleX); + if (canvas && activeObject) { + activeObject.scaleX = newScaleX; + canvas.discardActiveObject(); + canvas.setActiveObject(activeObject); + canvas.renderAll(); // Re-render the canvas + } + }; + + // Handle scaleY changes + const handleScaleYChange = (value) => { + const newScaleY = Math.max(0.001, Math.min(value)); // Clamp scaleY between 0.001 and 3 + setScaleY(newScaleY); + if (canvas && activeObject) { + activeObject.scaleY = newScaleY; + canvas.discardActiveObject(); + canvas.setActiveObject(activeObject); + canvas.renderAll(); // Re-render the canvas + } + }; + + const content = () => { return ( - - - {content()} - - +
+ {/* Scale X Input */} +
+ + { + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + handleScaleXChange(inputValue); + } + }} + /> +
+ + {/* Scale Y Input */} +
+ + { + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + handleScaleYChange(inputValue); + } + }} + /> +
+
); + }; + + return ( + + + {content()} + + + ); }; export default ScaleObjects; diff --git a/src/components/EachComponent/Customization/SelectObjectFromGroup.jsx b/src/components/EachComponent/Customization/SelectObjectFromGroup.jsx index c4ff0af..07a13b5 100644 --- a/src/components/EachComponent/Customization/SelectObjectFromGroup.jsx +++ b/src/components/EachComponent/Customization/SelectObjectFromGroup.jsx @@ -1,151 +1,159 @@ -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { useContext, useEffect, useRef, useState } from 'react' -import { Card, } from '@/components/ui/card'; -import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; -import { Separator } from '@/components/ui/separator'; -import { fabric } from 'fabric'; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useContext, useEffect, useRef, useState } from "react"; +import { Card } from "@/components/ui/card"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Separator } from "@/components/ui/separator"; +import { fabric } from "fabric"; const SelectObjectFromGroup = () => { - const [groupObjects, setGroupObjects] = useState([]) - const [selectedObject, setSelectedObject] = useState(null) - const { setActiveObject } = useContext(ActiveObjectContext) - const { canvas } = useContext(CanvasContext) - const previewCanvasRef = useRef(null) + const [groupObjects, setGroupObjects] = useState([]); + const [selectedObject, setSelectedObject] = useState(null); + const { setActiveObject } = useContext(ActiveObjectContext); + const { canvas } = useContext(CanvasContext); + const previewCanvasRef = useRef(null); - const activeObject = canvas?.getActiveObject() + const activeObject = canvas?.getActiveObject(); - useEffect(() => { - if (activeObject?.type === 'group') { - setGroupObjects(activeObject._objects || []) - setSelectedObject(null) - } else { - setGroupObjects([]) - setSelectedObject(null) - } - }, [activeObject]) - - // useEffect(() => { - // if (selectedObject && previewCanvasRef.current) { - // const previewCanvas = new fabric.StaticCanvas(previewCanvasRef.current); - - // previewCanvas.setDimensions({ - // width: 100, - // height: 100 - // }) - - // // Clone the active object to create a true deep copy - // selectedObject.clone((clonedObject) => { - // if (selectedObject?.width > 100 || selectedObject?.height > 100) { - // clonedObject.set({ - // scaleX: 0.2, - // scaleY: 0.2, - // }) - // } - // // Add the cloned object to the canvas - // clonedObject.set({ - // left: 50, - // top: 50, - // originX: 'center', - // originY: 'center', - // selectable: false, - // evented: false, - // }) - // previewCanvas.add(clonedObject); - // previewCanvas.renderAll(); - // }); - - // return () => { - // previewCanvas.dispose() - // } - // } - // }, [selectedObject]) - - - useEffect(() => { - if (selectedObject && previewCanvasRef.current) { - // Create a new static canvas - const previewCanvas = new fabric.StaticCanvas(previewCanvasRef.current); - - previewCanvas.setDimensions({ - width: 100, - height: 100 - }); - - // Clear previous objects (if any) - previewCanvas.clear(); - - // Clone the active object for preview - selectedObject.clone((clonedObject) => { - const scaledWidth = selectedObject.width * selectedObject.scaleX; - const scaledHeight = selectedObject.height * selectedObject.scaleY; - - if (scaledWidth > 100 || scaledHeight > 100) { - clonedObject.scaleToWidth(100); // Scale to fit width - clonedObject.scaleToHeight(100); // Scale to fit height - } - - clonedObject.set({ - left: 50, - top: 50, - originX: 'center', - originY: 'center', - }); - - // Add the cloned object to preview canvas - previewCanvas.add(clonedObject); - previewCanvas.renderAll(); - }); - - // Cleanup function to dispose of the canvas - return () => { - previewCanvas.clear(); // Clear all objects - previewCanvas.dispose(); // Properly dispose the canvas - }; - } - }, [selectedObject, previewCanvasRef]); - - - const handleSelectObject = (value) => { - const selected = groupObjects[parseInt(value)] - setSelectedObject(selected) - setActiveObject(selected) + useEffect(() => { + if (activeObject?.type === "group") { + setGroupObjects(activeObject._objects || []); + setSelectedObject(null); + } else { + setGroupObjects([]); + setSelectedObject(null); } + }, [activeObject]); - if (!activeObject || activeObject.type !== 'group') { - return null + // useEffect(() => { + // if (selectedObject && previewCanvasRef.current) { + // const previewCanvas = new fabric.StaticCanvas(previewCanvasRef.current); + + // previewCanvas.setDimensions({ + // width: 100, + // height: 100 + // }) + + // // Clone the active object to create a true deep copy + // selectedObject.clone((clonedObject) => { + // if (selectedObject?.width > 100 || selectedObject?.height > 100) { + // clonedObject.set({ + // scaleX: 0.2, + // scaleY: 0.2, + // }) + // } + // // Add the cloned object to the canvas + // clonedObject.set({ + // left: 50, + // top: 50, + // originX: 'center', + // originY: 'center', + // selectable: false, + // evented: false, + // }) + // previewCanvas.add(clonedObject); + // previewCanvas.renderAll(); + // }); + + // return () => { + // previewCanvas.dispose() + // } + // } + // }, [selectedObject]) + + useEffect(() => { + if (selectedObject && previewCanvasRef.current) { + // Create a new static canvas + const previewCanvas = new fabric.StaticCanvas(previewCanvasRef.current); + + previewCanvas.setDimensions({ + width: 100, + height: 100, + }); + + // Clear previous objects (if any) + previewCanvas.clear(); + + // Clone the active object for preview + selectedObject.clone((clonedObject) => { + const scaledWidth = selectedObject.width * selectedObject.scaleX; + const scaledHeight = selectedObject.height * selectedObject.scaleY; + + if (scaledWidth > 100 || scaledHeight > 100) { + clonedObject.scaleToWidth(100); // Scale to fit width + clonedObject.scaleToHeight(100); // Scale to fit height + } + + clonedObject.set({ + left: 50, + top: 50, + originX: "center", + originY: "center", + }); + + // Add the cloned object to preview canvas + previewCanvas.add(clonedObject); + previewCanvas.renderAll(); + }); + + // Cleanup function to dispose of the canvas + return () => { + previewCanvas.clear(); // Clear all objects + previewCanvas.dispose(); // Properly dispose the canvas + }; } + }, [selectedObject, previewCanvasRef]); - return ( -
- -

Group Objects

-
- + const handleSelectObject = (value) => { + const selected = groupObjects[parseInt(value)]; + setSelectedObject(selected); + setActiveObject(selected); + }; - {selectedObject && ( -
- -
- )} -
-
- + if (!activeObject || activeObject.type !== "group") { + return null; + } + + return ( +
+ +

Group Objects

+
+ + + {selectedObject && ( +
+ +
+ )}
- ) -} -export default SelectObjectFromGroup; \ No newline at end of file +
+ +
+ ); +}; +export default SelectObjectFromGroup; diff --git a/src/components/EachComponent/Customization/ShadowCustomization.jsx b/src/components/EachComponent/Customization/ShadowCustomization.jsx index 4018c31..b01c2e1 100644 --- a/src/components/EachComponent/Customization/ShadowCustomization.jsx +++ b/src/components/EachComponent/Customization/ShadowCustomization.jsx @@ -9,135 +9,143 @@ import { useContext, useEffect, useState } from "react"; import CollapsibleComponent from "./CollapsibleComponent"; const ShadowCustomization = () => { - // get values from context - const { activeObject } = useContext(ActiveObjectContext); - const { canvas } = useContext(CanvasContext); + // get values from context + const { activeObject } = useContext(ActiveObjectContext); + const { canvas } = useContext(CanvasContext); - // state to handle shadow inputs - const [shadowColor, setShadowColor] = useState(""); - const [offsetX, setOffsetX] = useState(5); - const [offsetY, setOffsetY] = useState(5); - const [blur, setBlur] = useState(0.5); - const [opacity, setOpacity] = useState(0.5); + // state to handle shadow inputs + const [shadowColor, setShadowColor] = useState(""); + const [offsetX, setOffsetX] = useState(5); + const [offsetY, setOffsetY] = useState(5); + const [blur, setBlur] = useState(0.5); + const [opacity, setOpacity] = useState(0.5); - useEffect(() => { - if (activeObject) { - setShadowColor(activeObject?.shadow?.color || "#db1d62"); - setOffsetX(activeObject?.shadow?.offsetX || 5); - setOffsetY(activeObject?.shadow?.offsetY || 5); - setBlur(activeObject?.shadow?.blur || 0.5); - setOpacity(activeObject?.shadow?.opacity || 0.5); - } - }, [activeObject]) - - const handleApplyShadow = () => { - if (activeObject && canvas) { - const shadow = { - color: shadowColor, - blur: blur, - offsetX: offsetX, - offsetY: offsetY, - opacity: opacity, - }; - activeObject.set("shadow", shadow); - // Ensure object updates and renders - activeObject.dirty = true; // Mark the object as dirty for re-render - canvas.renderAll(); // Trigger canvas re-render - } + useEffect(() => { + if (activeObject) { + setShadowColor(activeObject?.shadow?.color || "#db1d62"); + setOffsetX(activeObject?.shadow?.offsetX || 5); + setOffsetY(activeObject?.shadow?.offsetY || 5); + setBlur(activeObject?.shadow?.blur || 0.5); + setOpacity(activeObject?.shadow?.opacity || 0.5); } + }, [activeObject]); - const handleDisableShadow = () => { - if (activeObject && canvas) { - activeObject.set("shadow", null) - canvas.renderAll() - } + const handleApplyShadow = () => { + if (activeObject && canvas) { + const shadow = { + color: shadowColor, + blur: blur, + offsetX: offsetX, + offsetY: offsetY, + opacity: opacity, + }; + activeObject.set("shadow", shadow); + // Ensure object updates and renders + activeObject.dirty = true; // Mark the object as dirty for re-render + canvas.renderAll(); // Trigger canvas re-render } + }; - const content = () => { - return ( -
-
-
- -
- setShadowColor(e.target.value)} - className="w-16 h-10" - /> - {shadowColor} -
-
- -
- - setOffsetX(value[0])} - max={50} - min={-50} - step={1} - /> -
- -
- - setOffsetY(value[0])} - max={50} - min={-50} - step={1} - /> -
- -
- - setBlur(value[0])} - max={50} - min={0} - step={1} - /> -
- -
- - setOpacity(value[0])} - max={1} - min={0} - step={0.1} - /> -
-
- -
- A -
- -
- - -
-
- ) + const handleDisableShadow = () => { + if (activeObject && canvas) { + activeObject.set("shadow", null); + canvas.renderAll(); } + }; + const content = () => { return ( - - - {content()} - - +
+
+
+ +
+ setShadowColor(e.target.value)} + className="w-16 h-10" + /> + {shadowColor} +
+
+ +
+ + setOffsetX(value[0])} + max={50} + min={-50} + step={1} + /> +
+ +
+ + setOffsetY(value[0])} + max={50} + min={-50} + step={1} + /> +
+ +
+ + setBlur(value[0])} + max={50} + min={0} + step={1} + /> +
+ +
+ + setOpacity(value[0])} + max={1} + min={0} + step={0.1} + /> +
+
+ +
+ A +
+ +
+ + +
+
); + }; + + return ( + + + {content()} + + + ); }; export default ShadowCustomization; - diff --git a/src/components/EachComponent/Customization/SkewCustomization.jsx b/src/components/EachComponent/Customization/SkewCustomization.jsx index 092fe12..bb48286 100644 --- a/src/components/EachComponent/Customization/SkewCustomization.jsx +++ b/src/components/EachComponent/Customization/SkewCustomization.jsx @@ -1,85 +1,85 @@ -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; -import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; -import { Card } from '@/components/ui/card'; -import { Label } from '@/components/ui/label'; -import { Slider } from '@/components/ui/slider'; -import { useContext, useEffect, useState } from 'react' -import CollapsibleComponent from './CollapsibleComponent'; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Card } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Slider } from "@/components/ui/slider"; +import { useContext, useEffect, useState } from "react"; +import CollapsibleComponent from "./CollapsibleComponent"; const SkewCustomization = () => { - const { canvas } = useContext(CanvasContext); - const { activeObject } = useContext(ActiveObjectContext); - const [skewX, setSkewX] = useState(0); - const [skewY, setSkewY] = useState(0); + const { canvas } = useContext(CanvasContext); + const { activeObject } = useContext(ActiveObjectContext); + const [skewX, setSkewX] = useState(0); + const [skewY, setSkewY] = useState(0); - useEffect(() => { - if (activeObject) { - setSkewX(activeObject?.skewX); - setSkewY(activeObject?.skewY); - } - }, [activeObject]) - - // Update skewX directly - const handleSkewXChange = (value) => { - setSkewX(value[0]); - if (activeObject && canvas) { - activeObject.set({ skewX: value[0] }); - canvas.discardActiveObject(); - canvas.setActiveObject(activeObject); - canvas.renderAll(); - } - }; - - // Update skewY directly - const handleSkewYChange = (value) => { - setSkewY(value[0]); - if (activeObject && canvas) { - activeObject.set({ skewY: value[0] }); - canvas.discardActiveObject(); - canvas.setActiveObject(activeObject); - canvas.renderAll(); - } - }; - - const content = () => { - return ( -
-
- - { - handleSkewXChange(value) - }} - /> -
- -
- - { - handleSkewYChange(value) - }} - /> -
-
- ) + useEffect(() => { + if (activeObject) { + setSkewX(activeObject?.skewX); + setSkewY(activeObject?.skewY); } + }, [activeObject]); + // Update skewX directly + const handleSkewXChange = (value) => { + setSkewX(value[0]); + if (activeObject && canvas) { + activeObject.set({ skewX: value[0] }); + canvas.discardActiveObject(); + canvas.setActiveObject(activeObject); + canvas.renderAll(); + } + }; + + // Update skewY directly + const handleSkewYChange = (value) => { + setSkewY(value[0]); + if (activeObject && canvas) { + activeObject.set({ skewY: value[0] }); + canvas.discardActiveObject(); + canvas.setActiveObject(activeObject); + canvas.renderAll(); + } + }; + + const content = () => { return ( - - - {content()} - - - ) -} +
+
+ + { + handleSkewXChange(value); + }} + /> +
-export default SkewCustomization \ No newline at end of file +
+ + { + handleSkewYChange(value); + }} + /> +
+
+ ); + }; + + return ( + + + {content()} + + + ); +}; + +export default SkewCustomization; diff --git a/src/components/EachComponent/Customization/StrokeCustomization.jsx b/src/components/EachComponent/Customization/StrokeCustomization.jsx index 6bfecba..cfd73e8 100644 --- a/src/components/EachComponent/Customization/StrokeCustomization.jsx +++ b/src/components/EachComponent/Customization/StrokeCustomization.jsx @@ -1,367 +1,397 @@ -import { useContext, useState, useRef, useEffect } from 'react' -import { fabric } from 'fabric' -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext' -import CanvasContext from '@/components/Context/canvasContext/CanvasContext' -import { Card, CardContent } from '@/components/ui/card' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Slider } from '@/components/ui/slider' -import { Button } from '@/components/ui/button' -import { Paintbrush, ContrastIcon as Gradient } from 'lucide-react' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import CollapsibleComponent from './CollapsibleComponent' +import { useContext, useState, useRef, useEffect } from "react"; +import { fabric } from "fabric"; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Card, CardContent } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Slider } from "@/components/ui/slider"; +import { Button } from "@/components/ui/button"; +import { Paintbrush } from "lucide-react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import CollapsibleComponent from "./CollapsibleComponent"; const StrokeCustomization = () => { - const { activeObject } = useContext(ActiveObjectContext); - const { canvas } = useContext(CanvasContext); - const previewRef = useRef(null); + const { activeObject } = useContext(ActiveObjectContext); + const { canvas } = useContext(CanvasContext); + const previewRef = useRef(null); - const [strokeWidth, setStrokeWidth] = useState(0); - const [strokeColor, setStrokeColor] = useState("#000000"); - const [gradientStrokeColors, setGradientStrokeColors] = useState({ - color1: "#f97316", - color2: "#e26286", - }); - const [colorType, setColorType] = useState("color"); - const [gradientDirection, setGradientDirection] = useState("to bottom"); + const [strokeWidth, setStrokeWidth] = useState(0); + const [strokeColor, setStrokeColor] = useState("#000000"); + const [gradientStrokeColors, setGradientStrokeColors] = useState({ + color1: "#f97316", + color2: "#e26286", + }); + const [colorType, setColorType] = useState("color"); + const [gradientDirection, setGradientDirection] = useState("to bottom"); - - // Utility function to handle styles of objects - const handleObjectStyle = (object) => { - // Determine fill type (solid or gradient) - if (object.stroke) { - if (typeof object.stroke === "string") { - setColorType("color"); - setStrokeColor(object.stroke); // Solid color fill - } else if (object.stroke.colorStops) { - setColorType("gradient"); - setGradientStrokeColors({ - color1: object.stroke.colorStops[0]?.color || "#f97316", - color2: object.stroke.colorStops[1]?.color || "#e26286", - }); - } - } - // Handle stroke width - if (object.strokeWidth) { - setStrokeWidth(object.strokeWidth || 0); - } - }; - - // Recursively process group objects - const processGroupObjects = (group) => { - group._objects.forEach((obj) => { - if (obj.type === "group") { - processGroupObjects(obj); // Handle nested groups - } else { - handleObjectStyle(obj); // Apply styles to each object - } + // Utility function to handle styles of objects + const handleObjectStyle = (object) => { + if (object.stroke) { + if (typeof object.stroke === "string") { + setColorType("color"); + setStrokeColor(object.stroke); // Solid color fill + } else if (object.stroke.colorStops) { + setColorType("gradient"); + setGradientStrokeColors({ + color1: object.stroke.colorStops[0]?.color || "#f97316", + color2: object.stroke.colorStops[1]?.color || "#e26286", }); + } + } + if (object.strokeWidth) { + setStrokeWidth(object.strokeWidth || 0); + } + }; + + // Recursively process group objects + const processGroupObjects = (group) => { + group._objects.forEach((obj) => { + if (obj.type === "group") { + processGroupObjects(obj); // Handle nested groups + } else { + handleObjectStyle(obj); // Apply styles to each object + } + }); + }; + + // Effect to get previous values from active object + useEffect(() => { + if (activeObject) { + if (activeObject.type === "group") { + processGroupObjects(activeObject); // Process grouped objects + } else { + handleObjectStyle(activeObject); // Process single object + } + } + }, [activeObject]); + + const updatePreview = () => { + if (!previewRef.current) return; + + const previewStyle = { + width: "80px", + height: "80px", + border: `${strokeWidth}px solid`, + borderRadius: "4px", }; - // Effect to get previous values from active object - useEffect(() => { - if (activeObject) { - if (activeObject.type === "group") { - processGroupObjects(activeObject); // Process grouped objects - } else { - handleObjectStyle(activeObject); // Process single object - } - } - }, [activeObject]); - - const updatePreview = () => { - if (!previewRef.current) return; - - const previewStyle = { - width: '80px', - height: '80px', - border: `${strokeWidth}px solid`, - borderRadius: '4px', - }; - - if (colorType === "color") { - previewStyle.borderColor = strokeColor; - } else { - previewStyle.borderImage = `linear-gradient(${gradientDirection}, ${gradientStrokeColors.color1}, ${gradientStrokeColors.color2}) 1`; - } - - Object.assign(previewRef.current.style, previewStyle); - }; - - useEffect(() => { - updatePreview(); - }, [strokeWidth, strokeColor, gradientStrokeColors, colorType, gradientDirection]); - - const handleStrokeWidthChange = (value) => { - setStrokeWidth(value); - }; - - const handleColorTypeChange = (type) => { - setColorType(type); - }; - - const handleStrokeColorChange = (e) => { - setStrokeColor(e.target.value); - }; - - const handleGradientColorChange = (key, e) => { - setGradientStrokeColors(prev => ({ ...prev, [key]: e.target.value })); - }; - - const applyStrokeStyle = () => { - if (!activeObject || activeObject.type === "line") return; - - const width = activeObject?.width || 0; - const height = activeObject?.height || 0; - - const coords = { - "to bottom": { x1: 0, y1: 0, x2: 0, y2: height }, - "to top": { x1: 0, y1: height, x2: 0, y2: 0 }, - "to right": { x1: 0, y1: 0, x2: width, y2: 0 }, - "to left": { x1: width, y1: 0, x2: 0, y2: 0 }, - }; - const directionCoords = coords[gradientDirection]; - - const applyStrokeStyle = (object) => { - object.set("strokeWidth", strokeWidth); - - if (colorType === "color") { - object.set("stroke", strokeColor); - } else if (colorType === "gradient") { - const gradient = new fabric.Gradient({ - type: "linear", - gradientUnits: "pixels", - coords: directionCoords, - colorStops: [ - { offset: 0, color: gradientStrokeColors.color1 }, - { offset: 1, color: gradientStrokeColors.color2 }, - ], - }); - object.set("stroke", gradient); - } - }; - - const processGroupObjects = (group) => { - group._objects.forEach((obj) => { - if (obj.type === "group") { - processGroupObjects(obj); - } else { - applyStrokeStyle(obj); - } - }); - }; - - if (activeObject.type === "group") { - processGroupObjects(activeObject); - } else { - applyStrokeStyle(activeObject); - } - - canvas.renderAll(); - }; - - const revertStroke = () => { - if (!activeObject) return; - - const revertObject = (object) => { - object.set("strokeWidth", 0); - object.set("stroke", null); - }; - - const processGroupObjects = (group) => { - group._objects.forEach((obj) => { - if (obj.type === "group") { - processGroupObjects(obj); - } else { - revertObject(obj); - } - }); - }; - - if (activeObject.type === "group") { - processGroupObjects(activeObject); - } else { - revertObject(activeObject); - } - - canvas.renderAll(); - }; - - const content = () => { - return ( - -
-
- -
- handleStrokeWidthChange(value)} - className="flex-grow" - /> - handleStrokeWidthChange(Number(e.target.value))} - className="w-16" - /> -
-
- -
- handleColorTypeChange(value)}> - - - - Solid - - - - Gradient - - - -
- -
-
- -
-
- -
-
-
- -
- - -
- -
- -
-
- handleGradientColorChange('color1', e)} - className="w-10 h-10 p-1 rounded-md cursor-pointer" - /> -
-
- handleGradientColorChange('color1', e)} - className="flex-grow" - /> -
-
- -
- -
-
- handleGradientColorChange('color2', e)} - className="w-10 h-10 p-1 rounded-md cursor-pointer" - /> -
-
- handleGradientColorChange('color2', e)} - className="flex-grow" - /> -
-
-
-
-
- -
- -
-
-
-
- -
- - -
-
-
- ) + if (colorType === "color") { + previewStyle.borderColor = strokeColor; + } else { + previewStyle.borderImage = `linear-gradient(${gradientDirection}, ${gradientStrokeColors.color1}, ${gradientStrokeColors.color2}) 1`; } + Object.assign(previewRef.current.style, previewStyle); + }; + + useEffect(() => { + updatePreview(); + }, [ + strokeWidth, + strokeColor, + gradientStrokeColors, + colorType, + gradientDirection, + ]); + + const handleStrokeWidthChange = (value) => { + setStrokeWidth(value); + }; + + const handleColorTypeChange = (type) => { + setColorType(type); + }; + + const handleStrokeColorChange = (e) => { + setStrokeColor(e.target.value); + }; + + const handleGradientColorChange = (key, e) => { + setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value })); + }; + + const applyStrokeStyle = () => { + if (!activeObject || activeObject.type === "line") return; + + const width = activeObject?.width || 0; + const height = activeObject?.height || 0; + + const coords = { + "to bottom": { x1: 0, y1: 0, x2: 0, y2: height }, + "to top": { x1: 0, y1: height, x2: 0, y2: 0 }, + "to right": { x1: 0, y1: 0, x2: width, y2: 0 }, + "to left": { x1: width, y1: 0, x2: 0, y2: 0 }, + }; + const directionCoords = coords[gradientDirection]; + + const applyStrokeStyle = (object) => { + object.set("strokeWidth", strokeWidth); + + if (colorType === "color") { + object.set("stroke", strokeColor); + } else if (colorType === "gradient") { + const gradient = new fabric.Gradient({ + type: "linear", + gradientUnits: "pixels", + coords: directionCoords, + colorStops: [ + { offset: 0, color: gradientStrokeColors.color1 }, + { offset: 1, color: gradientStrokeColors.color2 }, + ], + }); + object.set("stroke", gradient); + } + }; + + const processGroupObjects = (group) => { + group._objects.forEach((obj) => { + if (obj.type === "group") { + processGroupObjects(obj); + } else { + applyStrokeStyle(obj); + } + }); + }; + + if (activeObject.type === "group") { + processGroupObjects(activeObject); + } else { + applyStrokeStyle(activeObject); + } + + canvas.renderAll(); + }; + + // Automatically apply stroke styles on state change + useEffect(() => { + applyStrokeStyle(); + }, [ + strokeWidth, + strokeColor, + gradientStrokeColors, + colorType, + gradientDirection, + ]); + + const revertStroke = () => { + if (!activeObject) return; + + const revertObject = (object) => { + object.set("strokeWidth", 0); + object.set("stroke", null); + }; + + const processGroupObjects = (group) => { + group._objects.forEach((obj) => { + if (obj.type === "group") { + processGroupObjects(obj); + } else { + revertObject(obj); + } + }); + }; + + if (activeObject.type === "group") { + processGroupObjects(activeObject); + } else { + revertObject(activeObject); + } + + canvas.renderAll(); + }; + + const content = () => { return ( - - - {content()} - - + +
+
+ +
+ handleStrokeWidthChange(value)} + className="flex-grow" + /> + + handleStrokeWidthChange(Number(e.target.value)) + } + className="w-16" + /> +
+
+ +
+ handleColorTypeChange(value)} + > + + + + Solid + + +
+ Gradient + + + +
+ +
+
+ +
+
+ +
+
+
+ +
+ + +
+ +
+ +
+
+ handleGradientColorChange("color1", e)} + className="w-10 h-10 p-1 rounded-md cursor-pointer" + /> +
+
+ handleGradientColorChange("color1", e)} + className="flex-grow" + /> +
+
+ +
+ +
+
+ handleGradientColorChange("color2", e)} + className="w-10 h-10 p-1 rounded-md cursor-pointer" + /> +
+
+ handleGradientColorChange("color2", e)} + className="flex-grow" + /> +
+
+
+ +
+ +
+ +
+
+
+
+ +
+ + +
+
+ ); + }; + + return ( + + + {content()} + + + ); }; export default StrokeCustomization; - - - - - - - - - - - - - diff --git a/src/components/EachComponent/Customization/TextCustomization.jsx b/src/components/EachComponent/Customization/TextCustomization.jsx index b303946..354c2fd 100644 --- a/src/components/EachComponent/Customization/TextCustomization.jsx +++ b/src/components/EachComponent/Customization/TextCustomization.jsx @@ -1,314 +1,382 @@ -import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; -import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Slider } from '@/components/ui/slider'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { useContext, useEffect, useState } from 'react' -import { AlignLeft, AlignCenter, AlignRight, Bold, Italic, Underline, Strikethrough } from 'lucide-react'; -import CollapsibleComponent from './CollapsibleComponent'; +import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; +import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Slider } from "@/components/ui/slider"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useContext, useEffect, useState } from "react"; +import { + AlignLeft, + AlignCenter, + AlignRight, + Bold, + Italic, + Underline, + Strikethrough, +} from "lucide-react"; +import CollapsibleComponent from "./CollapsibleComponent"; const fonts = [ - 'Roboto', 'Open Sans', 'Lato', 'Montserrat', 'Raleway', 'Poppins', 'Merriweather', - 'Playfair Display', 'Nunito', 'Oswald', 'Source Sans Pro', 'Ubuntu', 'Noto Sans', - 'Work Sans', 'Bebas Neue', 'Arimo', 'PT Sans', 'PT Serif', 'Titillium Web', - 'Fira Sans', 'Karla', 'Josefin Sans', 'Cairo', 'Rubik', 'Mulish', 'IBM Plex Sans', - 'Quicksand', 'Cabin', 'Heebo', 'Exo 2', 'Manrope', 'Jost', 'Anton', 'Asap', - 'Baloo 2', 'Barlow', 'Cantarell', 'Chivo', 'Inter', 'Dosis', 'Crimson Text', - 'Amatic SC', 'ABeeZee', 'Raleway Dots', 'Pacifico', 'Orbitron', 'Varela Round', - 'Acme', 'Teko', + "Roboto", + "Open Sans", + "Lato", + "Montserrat", + "Raleway", + "Poppins", + "Merriweather", + "Playfair Display", + "Nunito", + "Oswald", + "Source Sans Pro", + "Ubuntu", + "Noto Sans", + "Work Sans", + "Bebas Neue", + "Arimo", + "PT Sans", + "PT Serif", + "Titillium Web", + "Fira Sans", + "Karla", + "Josefin Sans", + "Cairo", + "Rubik", + "Mulish", + "IBM Plex Sans", + "Quicksand", + "Cabin", + "Heebo", + "Exo 2", + "Manrope", + "Jost", + "Anton", + "Asap", + "Baloo 2", + "Barlow", + "Cantarell", + "Chivo", + "Inter", + "Dosis", + "Crimson Text", + "Amatic SC", + "ABeeZee", + "Raleway Dots", + "Pacifico", + "Orbitron", + "Varela Round", + "Acme", + "Teko", ]; const TextCustomization = () => { - // get values from context - const { activeObject } = useContext(ActiveObjectContext); - const { canvas } = useContext(CanvasContext); + // get values from context + const { activeObject } = useContext(ActiveObjectContext); + const { canvas } = useContext(CanvasContext); - const [text, setText] = useState(''); - const [fontFamily, setFontFamily] = useState('Arial'); - const [fontSize, setFontSize] = useState(20); - const [fontStyle, setFontStyle] = useState('normal'); - const [fontWeight, setFontWeight] = useState('normal'); - const [lineHeight, setLineHeight] = useState(1.16); - const [charSpacing, setCharSpacing] = useState(0); - const [underline, setUnderline] = useState(false); - const [linethrough, setLinethrough] = useState(false); - const [textAlign, setTextAlign] = useState('left'); - const [previewText, setPreviewText] = useState(''); + const [text, setText] = useState(""); + const [fontFamily, setFontFamily] = useState("Arial"); + const [fontSize, setFontSize] = useState(20); + const [fontStyle, setFontStyle] = useState("normal"); + const [fontWeight, setFontWeight] = useState("normal"); + const [lineHeight, setLineHeight] = useState(1.16); + const [charSpacing, setCharSpacing] = useState(0); + const [underline, setUnderline] = useState(false); + const [linethrough, setLinethrough] = useState(false); + const [textAlign, setTextAlign] = useState("left"); + const [previewText, setPreviewText] = useState(""); - useEffect(() => { - if (activeObject?.type === "i-text") { - setText(activeObject?.text || ''); - setFontFamily(activeObject?.fontFamily || 'Arial'); - setFontSize(activeObject?.fontSize || 20); - setFontStyle(activeObject?.fontStyle || 'normal'); - setFontWeight(activeObject?.fontWeight || 'normal'); - setLineHeight(activeObject?.lineHeight || 1.16); - setCharSpacing(activeObject?.charSpacing || 0); - setUnderline(activeObject?.underline || false); - setLinethrough(activeObject?.linethrough || false); - setTextAlign(activeObject?.textAlign || 'left'); - setPreviewText(activeObject?.text || ''); - } - }, [activeObject]) - - const updateActiveObject = (properties) => { - if (activeObject?.type === "i-text") { - activeObject.set(properties) - canvas?.renderAll() - } + useEffect(() => { + if (activeObject?.type === "i-text") { + setText(activeObject?.text || ""); + setFontFamily(activeObject?.fontFamily || "Arial"); + setFontSize(activeObject?.fontSize || 20); + setFontStyle(activeObject?.fontStyle || "normal"); + setFontWeight(activeObject?.fontWeight || "normal"); + setLineHeight(activeObject?.lineHeight || 1.16); + setCharSpacing(activeObject?.charSpacing || 0); + setUnderline(activeObject?.underline || false); + setLinethrough(activeObject?.linethrough || false); + setTextAlign(activeObject?.textAlign || "left"); + setPreviewText(activeObject?.text || ""); } + }, [activeObject]); - const applyChanges = () => { - updateActiveObject({ - text, - fontFamily, - fontSize, - fontStyle, - fontWeight, - lineHeight, - charSpacing, - underline, - linethrough, - textAlign - }); + const updateActiveObject = (properties) => { + if (activeObject?.type === "i-text") { + activeObject.set(properties); + canvas?.renderAll(); } + }; - const handleTextChange = (newText) => { - setText(newText); - setPreviewText(newText); - } + const applyChanges = () => { + updateActiveObject({ + text, + fontFamily, + fontSize, + fontStyle, + fontWeight, + lineHeight, + charSpacing, + underline, + linethrough, + textAlign, + }); + }; - const handleFontFamilyChange = (newFontFamily) => { - setFontFamily(newFontFamily); - } + const handleTextChange = (newText) => { + setText(newText); + setPreviewText(newText); + }; - const handleFontSizeChange = (newFontSize) => { - setFontSize(newFontSize) - } + const handleFontFamilyChange = (newFontFamily) => { + setFontFamily(newFontFamily); + }; - const handleTextAlignChange = (newTextAlign) => { - setTextAlign(newTextAlign) - } + const handleFontSizeChange = (newFontSize) => { + setFontSize(newFontSize); + }; - const handleFontStyleChange = () => { - const newFontStyle = fontStyle === 'normal' ? 'italic' : 'normal' - setFontStyle(newFontStyle) - } + const handleTextAlignChange = (newTextAlign) => { + setTextAlign(newTextAlign); + }; - const handleFontWeightChange = () => { - const newFontWeight = fontWeight === 'normal' ? 'bold' : 'normal' - setFontWeight(newFontWeight) - } + const handleFontStyleChange = () => { + const newFontStyle = fontStyle === "normal" ? "italic" : "normal"; + setFontStyle(newFontStyle); + }; - const handleLineHeightChange = (newLineHeight) => { - setLineHeight(newLineHeight) - } + const handleFontWeightChange = () => { + const newFontWeight = fontWeight === "normal" ? "bold" : "normal"; + setFontWeight(newFontWeight); + }; - const handleCharSpacingChange = (newCharSpacing) => { - setCharSpacing(newCharSpacing) - } + const handleLineHeightChange = (newLineHeight) => { + setLineHeight(newLineHeight); + }; - const handleUnderlineChange = () => { - const newUnderline = !underline - setUnderline(newUnderline) - } + const handleCharSpacingChange = (newCharSpacing) => { + setCharSpacing(newCharSpacing); + }; - const handleLinethroughChange = () => { - const newLinethrough = !linethrough - setLinethrough(newLinethrough) - } + const handleUnderlineChange = () => { + const newUnderline = !underline; + setUnderline(newUnderline); + }; - const content = () => { - if (!(activeObject?.type === "i-text")) { - return ( -
- - -

Select a text object to customize

-
-
+ const handleLinethroughChange = () => { + const newLinethrough = !linethrough; + setLinethrough(newLinethrough); + }; + + const content = () => { + if (!(activeObject?.type === "i-text")) { + return ( +
+ + +

+ Select a text object to customize +

+
+
+
+ ); + } else { + return ( + + + + Text + Style + + + +
+ + handleTextChange(e.target.value)} + /> +
+
+ + +
+
+ +
+ handleFontSizeChange(value)} + min={8} + max={72} + step={1} + className="flex-grow" + /> + + handleFontSizeChange(parseInt(e.target.value, 10) || 16) + } + className="w-16" + />
- ) - } - - else { - return ( - - - - Text - Style - - - -
- - handleTextChange(e.target.value)} - /> -
-
- - -
-
- -
- handleFontSizeChange(value)} - min={8} - max={72} - step={1} - className="flex-grow" - /> - handleFontSizeChange(parseInt(e.target.value, 10) || 16)} - className="w-16" - /> -
-
-
- -
- -
- - - -
-
-
- -
- - - - -
-
-
- - handleLineHeightChange(value)} - min={0.5} - max={3} - step={0.1} - /> -
-
- - handleCharSpacingChange(value)} - min={-20} - max={100} - step={1} - /> -
-
-
-
- ) - } +
+
+ +
+ +
+ + + +
+
+
+ +
+ + + + +
+
+
+ + handleLineHeightChange(value)} + min={0.5} + max={3} + step={0.1} + /> +
+
+ + handleCharSpacingChange(value)} + min={-20} + max={100} + step={1} + /> +
+
+
+
+ ); } + }; - return ( - - - {content()} - -
- { - previewText && -
-

Preview:

-

- {previewText} -

-
- } + return ( + + + {content()} + +
+ {previewText && ( +
+

Preview:

+

+ {previewText} +

+
+ )} - -
-
- ) -} - -export default TextCustomization + +
+
+ ); +}; +export default TextCustomization; diff --git a/src/components/Layouts/LeftSidebar.jsx b/src/components/Layouts/LeftSidebar.jsx index c4e093a..8e99ee6 100644 --- a/src/components/Layouts/LeftSidebar.jsx +++ b/src/components/Layouts/LeftSidebar.jsx @@ -1,17 +1,28 @@ import { cn } from "@/lib/utils"; -import { Text, Square, Upload, Shield, Image, Folder } from "lucide-react"; +import { + Type, + Text, + Shapes, + FolderUp, + Shield, + Image, + FolderKanban, +} from "lucide-react"; +import { useContext } from "react"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; const sidebarItems = [ { id: "design", icon: Text, label: "Design" }, - { id: "text", icon: Text, label: "Text" }, - { id: "shape", icon: Square, label: "Shape" }, - { id: "upload", icon: Upload, label: "Upload" }, + { id: "text", icon: Type, label: "Text" }, + { id: "shape", icon: Shapes, label: "Shape" }, + { id: "upload", icon: FolderUp, label: "Upload" }, { id: "icon", icon: Shield, label: "Icon" }, { id: "image", icon: Image, label: "Image" }, - { id: "project", icon: Folder, label: "Project" }, + { id: "project", icon: FolderKanban, label: "Project" }, ]; -export function Sidebar({ selectedItem, onItemSelect }) { +export function Sidebar() { + const { selectedPanel, setSelectedPanel } = useContext(CanvasContext); return (
{sidebarItems.map((item) => { @@ -19,10 +30,12 @@ export function Sidebar({ selectedItem, onItemSelect }) { return ( -
- -
- ); -} diff --git a/src/components/Panel/Canvas.jsx b/src/components/Panel/Canvas.jsx index 8eb5fa9..283448e 100644 --- a/src/components/Panel/Canvas.jsx +++ b/src/components/Panel/Canvas.jsx @@ -1,10 +1,147 @@ +import { useEffect, useContext } from "react"; +import { AspectRatio } from "@/components/ui/aspect-ratio"; +import OpenContext from "../Context/openContext/OpenContext"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; +import { Card, CardContent } from "../ui/card"; + export function Canvas() { + const { + setLeftPanelOpen, + setRightPanelOpen, + setOpenSetting, + setOpenObjectPanel, + rightPanelOpen, + } = useContext(OpenContext); + + const { + canvasRef, + canvas, + setCanvas, + fabricCanvasRef, + canvasRatio, + setCanvasHeight, + setCanvasWidth, + setScreenWidth, + } = useContext(CanvasContext); + + useEffect(() => { + import("fabric").then((fabricModule) => { + window.fabric = fabricModule.fabric; + }); + }, []); + + const getRatioValue = (ratio) => { + const [width, height] = ratio.split(":").map(Number); + return width / height; + }; + + useEffect(() => { + const updateCanvasSize = () => { + if (canvasRef.current && canvas) { + // Update canvas dimensions + const newWidth = canvasRef?.current?.offsetWidth; + const newHeight = canvasRef?.current?.offsetHeight; + + canvas.setWidth(newWidth); + canvas.setHeight(newHeight); + setCanvasWidth(newWidth); + setCanvasHeight(newHeight); + + // Adjust the background image to fit the updated canvas size + const bgImage = canvas.backgroundImage; + if (bgImage) { + // Calculate scaling factors for width and height + const scaleX = newWidth / bgImage.width; + const scaleY = newHeight / bgImage.height; + + // Use the larger scale to cover the entire canvas + const scale = Math.max(scaleX, scaleY); + + // Apply scale and position the image + bgImage.scaleX = scale; + bgImage.scaleY = scale; + bgImage.left = 0; // Align left + bgImage.top = 0; // Align top + + // Update the background image + canvas.setBackgroundImage(bgImage, canvas.renderAll.bind(canvas)); + } else { + // Render the canvas if no background image + canvas.renderAll(); + } + } + + setScreenWidth(document.getElementById("root").offsetWidth); + + // Handle responsive behavior for panels + if (document.getElementById("root").offsetWidth <= 640) { + setLeftPanelOpen(false); + setRightPanelOpen(false); + } + if (document.getElementById("root").offsetWidth > 640) { + setOpenObjectPanel(false); + setOpenSetting(false); + } + }; + // Initial setup + updateCanvasSize(); + + // Listen for window resize + window.addEventListener("resize", updateCanvasSize); + + // Cleanup listener on unmount + return () => window.removeEventListener("resize", updateCanvasSize); + }, [ + setCanvasWidth, + setCanvasHeight, + canvasRatio, + canvasRef, + canvas, + setLeftPanelOpen, + setOpenObjectPanel, + setRightPanelOpen, + rightPanelOpen, + setScreenWidth, + setOpenSetting, + ]); + + useEffect(() => { + if (window.fabric) { + if (fabricCanvasRef?.current) { + fabricCanvasRef?.current.dispose(); + } + // Set styles directly on the canvas element + const canvasElement = document.getElementById("fabric-canvas"); + if (canvasElement) { + canvasElement.classList.add("fabric-canvas-container"); // Add the CSS class + } + + fabricCanvasRef.current = new window.fabric.Canvas("fabric-canvas", { + width: canvasRef?.current?.offsetWidth, + height: canvasRef?.current?.offsetWidth, + backgroundColor: "#ffffff", + }); + + setCanvas(fabricCanvasRef?.current); + } + }, []); + return ( -
- {/* Main Canvas Area */} -
-
-
-
+ + + +
+ +
+
+
+
); } diff --git a/src/components/Panel/CommonPanel.jsx b/src/components/Panel/CommonPanel.jsx new file mode 100644 index 0000000..252c0c3 --- /dev/null +++ b/src/components/Panel/CommonPanel.jsx @@ -0,0 +1,65 @@ +import { useContext } from "react"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; +import SelectObjectFromGroup from "../EachComponent/Customization/SelectObjectFromGroup"; +import StrokeCustomization from "../EachComponent/Customization/StrokeCustomization"; +import PositionCustomization from "../EachComponent/Customization/PositionCustomization"; +import { Card } from "../ui/card"; +import CollapsibleComponent from "../EachComponent/Customization/CollapsibleComponent"; +import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization"; +import FlipCustomization from "../EachComponent/Customization/FlipCustomization"; +import RotateCustomization from "../EachComponent/Customization/RotateCustomization"; +import SkewCustomization from "../EachComponent/Customization/SkewCustomization"; +import ScaleObjects from "../EachComponent/Customization/ScaleObjects"; +import ShadowCustomization from "../EachComponent/Customization/ShadowCustomization"; +import AddImageIntoShape from "../EachComponent/Customization/AddImageIntoShape"; + +const CommonPanel = () => { + const { canvas } = useContext(CanvasContext); + const activeObject = canvas?.getActiveObject(); + const customClipPath = activeObject?.isClipPath; + return ( +
+
+ + + {/* Apply stroke and stroke color */} + {!customClipPath && ( + <> + + + )} + + {activeObject?.type !== "group" && ( + <> + + + )} + + {/* Controls for opacity, flip, and rotation */} + + +
+ + + +
+
+
+ + {/* Skew Customization */} + + + {/* Scale Objects */} + + + {/* Shadow Customization */} + + + {/* Add image into shape */} + +
+
+ ); +}; + +export default CommonPanel; diff --git a/src/components/Panel/DesignPanel.jsx b/src/components/Panel/DesignPanel.jsx deleted file mode 100644 index 6de251e..0000000 --- a/src/components/Panel/DesignPanel.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Label } from "../ui/label"; -import { Slider } from "../ui/slider"; - -export default function DesignPanel() { - return ( -
-
- -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- ); -} diff --git a/src/components/Panel/EditorPanel.jsx b/src/components/Panel/EditorPanel.jsx index c04c2d5..ab44e28 100644 --- a/src/components/Panel/EditorPanel.jsx +++ b/src/components/Panel/EditorPanel.jsx @@ -1,56 +1,28 @@ -import { Plus, Search, Upload } from "lucide-react"; -import { Button } from "../ui/button"; -import { Input } from "../ui/input"; +import { useContext } from "react"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; +import TextPanel from "./TextPanel"; -export function EditorPanel({ type }) { - if (!type) return null; +const EditorPanel = () => { + const { selectedPanel } = useContext(CanvasContext); - const panelContent = { - text: ( - <> -

Text

- -
- - - -
- - ), - shape: ( - <> -

Shape

-
- - -
-
- {[...Array(6)].map((_, i) => ( -
- ))} -
- - ), - image: ( - <> -

Image

- -
- {[...Array(4)].map((_, i) => ( -
- ))} -
- - ), + const renderPanel = () => { + switch (selectedPanel) { + case "text": + return ; + default: + return; + } }; return ( -
- {panelContent[type]} -
+ <> + {selectedPanel !== "" && ( +
+ {renderPanel()} +
+ )} + ); -} +}; + +export default EditorPanel; diff --git a/src/components/Panel/ShapePanel.jsx b/src/components/Panel/ShapePanel.jsx deleted file mode 100644 index 4bc9728..0000000 --- a/src/components/Panel/ShapePanel.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Input } from "../ui/input"; -import { ScrollArea } from "../ui/scroll-area"; - -export default function ShapePanel() { - return ( -
- - -
- {Array.from({ length: 9 }).map((_, i) => ( -
- ))} -
- -
- ); -} diff --git a/src/components/Panel/TextPanel.jsx b/src/components/Panel/TextPanel.jsx index e651a4a..d375311 100644 --- a/src/components/Panel/TextPanel.jsx +++ b/src/components/Panel/TextPanel.jsx @@ -1,56 +1,92 @@ import { Button } from "../ui/Button"; -import { Input } from "../ui/Input"; import { X } from "lucide-react"; import { ScrollArea } from "../ui/scroll-area"; +import { useContext } from "react"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; +import ActiveObjectContext from "../Context/activeObject/ObjectContext"; +import { fabric } from "fabric"; +import CommonPanel from "./CommonPanel"; +import TextCustomization from "../EachComponent/Customization/TextCustomization"; export default function TextPanel() { + const { canvas } = useContext(CanvasContext); + const { setActiveObject } = useContext(ActiveObjectContext); + const activeObject = canvas?.getActiveObject(); + const activeObjectType = activeObject?.type; + const hasClipPath = !!activeObject?.clipPath; + const customClipPath = activeObject?.isClipPath; + + 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(); + } + }; + return ( -
- -
-

Text

- -
- - +
-
-
-

- Default text style -

- + + {activeObject ? ( +
+ +
- -
-
-

Text Design

- -
-
- {[...Array(6)].map((_, i) => ( -
- ))} -
-
-
+ ) : ( +

+ No active object found +

+ )}
);