added text section design layout
This commit is contained in:
parent
976978aec4
commit
954ac950b0
21 changed files with 2156 additions and 1822 deletions
12
src/App.jsx
12
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() {
|
|||
// </div>
|
||||
|
||||
<div className="flex h-screen">
|
||||
<Sidebar selectedItem={selectedItem} onItemSelect={setSelectedItem} />
|
||||
{selectedItem === "text" && <TextPanel />}
|
||||
<Sidebar />
|
||||
{selectedPanel !== "" && <EditorPanel />}
|
||||
<div className="flex-1 relative">
|
||||
<TopBar isVisible={hasSelectedObject} />
|
||||
<ActionButtons />
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="absolute top-4 right-0 z-50 flex items-center gap-2 bg-white rounded-l-[16px]">
|
||||
<div className="px-2 py-2">
|
||||
<Button variant="ghost" className="gap-2 h-9 px-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
<div className="w-full">
|
||||
<Select onValueChange={handleRatioChange} value={canvasRatio}>
|
||||
<SelectTrigger className="w-full text-xs font-bold">
|
||||
<SelectValue placeholder="Select aspect ratio" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{aspectRatios.map((ratio) => (
|
||||
<SelectItem
|
||||
key={ratio.value}
|
||||
value={ratio.value}
|
||||
className="text-xs font-bold"
|
||||
>
|
||||
<g clipPath="url(#clip0_71_2678)">
|
||||
<path d="M1 3.5L11 3.5" stroke="black" strokeLinecap="round" />
|
||||
<path
|
||||
d="M1 8.5769L11 8.57691"
|
||||
stroke="black"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M9.0769 1L9.0769 11"
|
||||
stroke="black"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.4231 1L3.4231 11"
|
||||
stroke="black"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_71_2678">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
Resize <ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
{ratio.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mr-5">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRef, useState } from 'react'
|
||||
import CanvasContext from './CanvasContext';
|
||||
import { useRef, useState } from "react";
|
||||
import CanvasContext from "./CanvasContext";
|
||||
|
||||
const CanvasContextProvider = ({ children }) => {
|
||||
const canvasRef = useRef(null);
|
||||
|
|
@ -7,13 +7,32 @@ const CanvasContextProvider = ({ children }) => {
|
|||
const [canvasHeight, setCanvasHeight] = useState(0);
|
||||
const [canvasWidth, setCanvasWidth] = useState(0);
|
||||
const [screenWidth, setScreenWidth] = useState(0);
|
||||
const [canvasRatio, setCanvasRatio] = useState("4:3");
|
||||
const [selectedPanel, setSelectedPanel] = useState("");
|
||||
const fabricCanvasRef = useRef(null);
|
||||
|
||||
return (
|
||||
<CanvasContext.Provider value={{ canvasRef, canvas, setCanvas, fabricCanvasRef, canvasHeight, setCanvasHeight, canvasWidth, setCanvasWidth, screenWidth, setScreenWidth }}>
|
||||
<CanvasContext.Provider
|
||||
value={{
|
||||
canvasRef,
|
||||
canvas,
|
||||
setCanvas,
|
||||
fabricCanvasRef,
|
||||
canvasHeight,
|
||||
canvasRatio,
|
||||
setCanvasRatio,
|
||||
selectedPanel,
|
||||
setSelectedPanel,
|
||||
setCanvasHeight,
|
||||
canvasWidth,
|
||||
setCanvasWidth,
|
||||
screenWidth,
|
||||
setScreenWidth,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CanvasContext.Provider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasContextProvider
|
||||
export default CanvasContextProvider;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,60 @@
|
|||
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext';
|
||||
import CanvasContext from '@/components/Context/canvasContext/CanvasContext';
|
||||
import { useContext, useRef, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { fabric } from 'fabric';
|
||||
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
||||
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { fabric } from "fabric";
|
||||
import Resizer from "react-image-file-resizer";
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { HardDriveUpload, ImagePlus, Upload, Crop, RotateCw, FileType, ChevronUp, ChevronDown } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import {
|
||||
HardDriveUpload,
|
||||
ImagePlus,
|
||||
Upload,
|
||||
Crop,
|
||||
RotateCw,
|
||||
FileType,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
} from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import CollapsibleComponent from "./CollapsibleComponent";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
|
||||
const features = [
|
||||
{ icon: Upload, title: 'Auto-Resize', description: 'Images larger than 1080px are automatically resized' },
|
||||
{ icon: Crop, title: 'Custom Dimensions', description: 'Set your preferred resize dimensions' },
|
||||
{ icon: RotateCw, title: 'Quality & Rotation', description: 'Adjust image quality and rotation as needed' },
|
||||
{ icon: FileType, title: 'Format Conversion', description: 'Convert between various image formats' },
|
||||
]
|
||||
{
|
||||
icon: Upload,
|
||||
title: "Auto-Resize",
|
||||
description: "Images larger than 1080px are automatically resized",
|
||||
},
|
||||
{
|
||||
icon: Crop,
|
||||
title: "Custom Dimensions",
|
||||
description: "Set your preferred resize dimensions",
|
||||
},
|
||||
{
|
||||
icon: RotateCw,
|
||||
title: "Quality & Rotation",
|
||||
description: "Adjust image quality and rotation as needed",
|
||||
},
|
||||
{
|
||||
icon: FileType,
|
||||
title: "Format Conversion",
|
||||
description: "Convert between various image formats",
|
||||
},
|
||||
];
|
||||
|
||||
const AddImageIntoShape = () => {
|
||||
const { canvas } = useContext(CanvasContext);
|
||||
|
|
@ -29,7 +64,7 @@ const AddImageIntoShape = () => {
|
|||
const [height, setHeight] = useState(1080);
|
||||
const [quality, setQuality] = useState(100);
|
||||
const [rotation, setRotation] = useState("0");
|
||||
const [format, setFormat] = useState('JPEG');
|
||||
const [format, setFormat] = useState("JPEG");
|
||||
const fileInputRef = useRef(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
|
|
@ -44,7 +79,7 @@ const AddImageIntoShape = () => {
|
|||
(resizedFile) => {
|
||||
callback(resizedFile); // Pass the resized file to the callback
|
||||
},
|
||||
'file' // Output type
|
||||
"file" // Output type
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -155,14 +190,14 @@ const AddImageIntoShape = () => {
|
|||
top: activeObject.top,
|
||||
clipPath: activeObject, // Apply clipPath to the image
|
||||
originX: activeObject.originX, // Match origin point
|
||||
originY: activeObject.originY // Match origin point
|
||||
originY: activeObject.originY, // Match origin point
|
||||
});
|
||||
|
||||
// Adjust position based on the clipPath's transformations
|
||||
fabricImage.set({
|
||||
left: activeObject.left,
|
||||
top: activeObject.top,
|
||||
angle: activeObject.angle // Match rotation if any
|
||||
angle: activeObject.angle, // Match rotation if any
|
||||
});
|
||||
|
||||
canvas.add(fabricImage);
|
||||
|
|
@ -176,7 +211,6 @@ const AddImageIntoShape = () => {
|
|||
const content = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
{/* <Card className="my-2">
|
||||
<CardContent className="p-0">
|
||||
<Alert>
|
||||
|
|
@ -213,13 +247,21 @@ const AddImageIntoShape = () => {
|
|||
</AlertTitle>
|
||||
<AlertDescription className="mt-1">
|
||||
<p className="mb-1">
|
||||
Insert and customize images within shapes. Adjust image position and clipping after insertion.
|
||||
Insert and customize images within shapes. Adjust image
|
||||
position and clipping after insertion.
|
||||
</p>
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="outline" className="w-full justify-between mt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-between mt-2"
|
||||
>
|
||||
<span>Key Features</span>
|
||||
{isOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
{isOpen ? (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
|
|
@ -231,8 +273,12 @@ const AddImageIntoShape = () => {
|
|||
>
|
||||
<feature.icon className="h-6 w-6 mr-3 flex-shrink-0 text-primary" />
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">{feature.title}</h3>
|
||||
<p className="text-sm text-gray-600">{feature.description}</p>
|
||||
<h3 className="font-semibold mb-1">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -247,7 +293,7 @@ const AddImageIntoShape = () => {
|
|||
{errorMessage && (
|
||||
<p className="text-red-600 text-sm mt-2">{errorMessage}</p>
|
||||
)}
|
||||
<div className='flex flex-col w-[100%]'>
|
||||
<div className="flex flex-col w-[100%]">
|
||||
<div className="space-y-1">
|
||||
{/* Width Slider */}
|
||||
<div className="space-y-1">
|
||||
|
|
@ -274,8 +320,7 @@ const AddImageIntoShape = () => {
|
|||
</div>
|
||||
|
||||
{/* Quality Slider */}
|
||||
{
|
||||
format === "JPEG" &&
|
||||
{format === "JPEG" && (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">Quality: {quality}%</Label>
|
||||
<Slider
|
||||
|
|
@ -285,14 +330,17 @@ const AddImageIntoShape = () => {
|
|||
onValueChange={(value) => setQuality(value[0])}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-2 gap-1 items-center'>
|
||||
<div className="grid grid-cols-2 gap-1 items-center">
|
||||
{/* Rotation */}
|
||||
<div>
|
||||
<Label className="text-xs">Rotation: {rotation}°</Label>
|
||||
|
||||
<Select value={rotation} onValueChange={(value) => setRotation(value)}>
|
||||
<Select
|
||||
value={rotation}
|
||||
onValueChange={(value) => setRotation(value)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select rotation in degree" />
|
||||
</SelectTrigger>
|
||||
|
|
@ -309,7 +357,10 @@ const AddImageIntoShape = () => {
|
|||
{/* Format Dropdown */}
|
||||
<div>
|
||||
<Label className="text-xs">Format</Label>
|
||||
<Select value={format} onValueChange={(value) => setFormat(value)}>
|
||||
<Select
|
||||
value={format}
|
||||
onValueChange={(value) => setFormat(value)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select format" />
|
||||
</SelectTrigger>
|
||||
|
|
@ -331,15 +382,16 @@ const AddImageIntoShape = () => {
|
|||
type="file"
|
||||
accept="image/*"
|
||||
ref={fileInputRef}
|
||||
onChange={fileHandler} />
|
||||
onChange={fileHandler}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col p-2">
|
||||
<Card className="flex flex-col shadow-none border-0">
|
||||
<CollapsibleComponent text={"Insert Image"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
|
|
@ -348,6 +400,3 @@ const AddImageIntoShape = () => {
|
|||
};
|
||||
|
||||
export default AddImageIntoShape;
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<div className={`flex items-center justify-between cursor-pointer ${!isOpen ? "my-2" : ""}`}>
|
||||
<h2 className='font-bold'>{text}</h2>
|
||||
<Button variant={"outline"}>
|
||||
{isOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
</Button>
|
||||
<div
|
||||
className={`flex items-center justify-between cursor-pointer ${
|
||||
!isOpen ? "my-2" : ""
|
||||
}`}
|
||||
>
|
||||
<h2 className="font-bold mb-2">{text}</h2>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
{children}
|
||||
</CollapsibleContent>
|
||||
<CollapsibleContent>{children}</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapsibleComponent
|
||||
export default CollapsibleComponent;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
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);
|
||||
|
|
@ -15,11 +15,10 @@ const LockObject = () => {
|
|||
useEffect(() => {
|
||||
if (activeObject?.lockMovementX === false) {
|
||||
setIsLocked(false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setIsLocked(true);
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
const toggleLock = () => {
|
||||
if (!canvas || !activeObject) {
|
||||
|
|
@ -27,7 +26,7 @@ const LockObject = () => {
|
|||
title: "No object selected",
|
||||
description: "Please select an object to lock or unlock.",
|
||||
variant: "destructive",
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -45,13 +44,15 @@ const LockObject = () => {
|
|||
|
||||
toast({
|
||||
title: newLockState ? "Object locked" : "Object unlocked",
|
||||
description: newLockState ? "The object is now locked in place." : "The object can now be moved and resized.",
|
||||
})
|
||||
}
|
||||
description: newLockState
|
||||
? "The object is now locked in place."
|
||||
: "The object can now be moved and resized.",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<h2 className='font-bold'>{!isLocked ? "Lock" : "Unlock"} Object</h2>
|
||||
<Card className="shadow-none border-0">
|
||||
<h2 className="font-bold">{!isLocked ? "Lock" : "Unlock"} Object</h2>
|
||||
<Button
|
||||
onClick={toggleLock}
|
||||
variant="outline"
|
||||
|
|
@ -59,10 +60,14 @@ const LockObject = () => {
|
|||
disabled={!activeObject}
|
||||
title={isLocked ? "Unlock object" : "Lock object"}
|
||||
>
|
||||
{isLocked ? <Unlock className="h-4 w-4" /> : <Lock className="h-4 w-4" />}
|
||||
{isLocked ? (
|
||||
<Unlock className="h-4 w-4" />
|
||||
) : (
|
||||
<Lock className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default LockObject
|
||||
export default LockObject;
|
||||
|
|
|
|||
|
|
@ -1,57 +1,54 @@
|
|||
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 [isOpen, setIsOpen] = useState(true);
|
||||
const [objectPosition, setObjectPosition] = useState({ left: 50, top: 50 });
|
||||
|
||||
useEffect(() => {
|
||||
if (activeObject) {
|
||||
setObjectPosition({
|
||||
left: Math.round(activeObject.left || 0),
|
||||
top: Math.round(activeObject.top || 0),
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
const updateObjectPosition = (key, value) => {
|
||||
const updatedPosition = { ...objectPosition, [key]: value }
|
||||
setObjectPosition(updatedPosition)
|
||||
const updatedPosition = { ...objectPosition, [key]: value };
|
||||
setObjectPosition(updatedPosition);
|
||||
|
||||
if (canvas && activeObject) {
|
||||
activeObject.set(updatedPosition)
|
||||
canvas.renderAll()
|
||||
}
|
||||
activeObject.set(updatedPosition);
|
||||
canvas.renderAll();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (key, value) => {
|
||||
const numValue = parseInt(value, 10)
|
||||
const numValue = parseInt(value, 10);
|
||||
if (!isNaN(numValue)) {
|
||||
updateObjectPosition(key, numValue)
|
||||
}
|
||||
updateObjectPosition(key, numValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSliderChange = (key, value) => {
|
||||
updateObjectPosition(key, value[0])
|
||||
}
|
||||
updateObjectPosition(key, value[0]);
|
||||
};
|
||||
|
||||
const adjustPosition = (key, delta) => {
|
||||
const newValue = objectPosition[key] + delta
|
||||
updateObjectPosition(key, newValue)
|
||||
}
|
||||
const newValue = objectPosition[key] + delta;
|
||||
updateObjectPosition(key, newValue);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
return (
|
||||
|
|
@ -64,7 +61,7 @@ const PositionCustomization = () => {
|
|||
id="position-left"
|
||||
type="number"
|
||||
value={objectPosition.left}
|
||||
onChange={(e) => handleInputChange('left', e.target.value)}
|
||||
onChange={(e) => handleInputChange("left", e.target.value)}
|
||||
className="w-20"
|
||||
/>
|
||||
<Slider
|
||||
|
|
@ -72,7 +69,7 @@ const PositionCustomization = () => {
|
|||
max={canvas ? canvas.width : 1000}
|
||||
step={1}
|
||||
value={[objectPosition.left]}
|
||||
onValueChange={(value) => handleSliderChange('left', value)}
|
||||
onValueChange={(value) => handleSliderChange("left", value)}
|
||||
className="flex-grow"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -84,7 +81,7 @@ const PositionCustomization = () => {
|
|||
id="position-top"
|
||||
type="number"
|
||||
value={objectPosition.top}
|
||||
onChange={(e) => handleInputChange('top', e.target.value)}
|
||||
onChange={(e) => handleInputChange("top", e.target.value)}
|
||||
className="w-20"
|
||||
/>
|
||||
<Slider
|
||||
|
|
@ -92,7 +89,7 @@ const PositionCustomization = () => {
|
|||
max={canvas ? canvas.height : 1000}
|
||||
step={1}
|
||||
value={[objectPosition.top]}
|
||||
onValueChange={(value) => handleSliderChange('top', value)}
|
||||
onValueChange={(value) => handleSliderChange("top", value)}
|
||||
className="flex-grow"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -102,7 +99,7 @@ const PositionCustomization = () => {
|
|||
<div className="w-32 h-32 grid grid-cols-3 gap-1 p-2 mx-auto">
|
||||
<div className="col-start-2">
|
||||
<Button
|
||||
onClick={() => adjustPosition('top', -1)}
|
||||
onClick={() => adjustPosition("top", -1)}
|
||||
aria-label="Move up"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
|
@ -113,7 +110,7 @@ const PositionCustomization = () => {
|
|||
</div>
|
||||
<div className="col-start-1 row-start-2">
|
||||
<Button
|
||||
onClick={() => adjustPosition('left', -1)}
|
||||
onClick={() => adjustPosition("left", -1)}
|
||||
aria-label="Move left"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
|
@ -124,7 +121,7 @@ const PositionCustomization = () => {
|
|||
</div>
|
||||
<div className="col-start-3 row-start-2">
|
||||
<Button
|
||||
onClick={() => adjustPosition('left', 1)}
|
||||
onClick={() => adjustPosition("left", 1)}
|
||||
aria-label="Move right"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
|
@ -135,7 +132,7 @@ const PositionCustomization = () => {
|
|||
</div>
|
||||
<div className="col-start-2 row-start-3">
|
||||
<Button
|
||||
onClick={() => adjustPosition('top', 1)}
|
||||
onClick={() => adjustPosition("top", 1)}
|
||||
aria-label="Move down"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
|
@ -145,18 +142,17 @@ const PositionCustomization = () => {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</CardContent>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<Card className="shadow-none border-0">
|
||||
<CollapsibleComponent text={"Position Control"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PositionCustomization
|
||||
export default PositionCustomization;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const ScaleObjects = () => {
|
|||
setScaleX(activeObject?.scaleX);
|
||||
setScaleY(activeObject?.scaleY);
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
// Handle scaleX changes
|
||||
const handleScaleXChange = (value) => {
|
||||
|
|
@ -79,11 +79,11 @@ const ScaleObjects = () => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="grid items-center gap-2 p-2">
|
||||
<Card className="grid items-center gap-2 shadow-none border-0">
|
||||
<CollapsibleComponent text={"Scale Control"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
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)
|
||||
if (activeObject?.type === "group") {
|
||||
setGroupObjects(activeObject._objects || []);
|
||||
setSelectedObject(null);
|
||||
} else {
|
||||
setGroupObjects([])
|
||||
setSelectedObject(null)
|
||||
setGroupObjects([]);
|
||||
setSelectedObject(null);
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (selectedObject && previewCanvasRef.current) {
|
||||
|
|
@ -61,7 +67,6 @@ const SelectObjectFromGroup = () => {
|
|||
// }
|
||||
// }, [selectedObject])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedObject && previewCanvasRef.current) {
|
||||
// Create a new static canvas
|
||||
|
|
@ -69,7 +74,7 @@ const SelectObjectFromGroup = () => {
|
|||
|
||||
previewCanvas.setDimensions({
|
||||
width: 100,
|
||||
height: 100
|
||||
height: 100,
|
||||
});
|
||||
|
||||
// Clear previous objects (if any)
|
||||
|
|
@ -88,8 +93,8 @@ const SelectObjectFromGroup = () => {
|
|||
clonedObject.set({
|
||||
left: 50,
|
||||
top: 50,
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
});
|
||||
|
||||
// Add the cloned object to preview canvas
|
||||
|
|
@ -105,25 +110,28 @@ const SelectObjectFromGroup = () => {
|
|||
}
|
||||
}, [selectedObject, previewCanvasRef]);
|
||||
|
||||
|
||||
const handleSelectObject = (value) => {
|
||||
const selected = groupObjects[parseInt(value)]
|
||||
setSelectedObject(selected)
|
||||
setActiveObject(selected)
|
||||
}
|
||||
const selected = groupObjects[parseInt(value)];
|
||||
setSelectedObject(selected);
|
||||
setActiveObject(selected);
|
||||
};
|
||||
|
||||
if (!activeObject || activeObject.type !== 'group') {
|
||||
return null
|
||||
if (!activeObject || activeObject.type !== "group") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className="p-4">
|
||||
<h2 className='font-bold mb-4'>Group Objects</h2>
|
||||
<div className='flex flex-col items-center justify-center space-y-4'>
|
||||
<Card className="p-4 shadow-none border-0">
|
||||
<h2 className="font-bold mb-4">Group Objects</h2>
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<Select
|
||||
onValueChange={handleSelectObject}
|
||||
value={selectedObject ? groupObjects.indexOf(selectedObject).toString() : undefined}
|
||||
value={
|
||||
selectedObject
|
||||
? groupObjects.indexOf(selectedObject).toString()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full max-w-xs">
|
||||
<SelectValue placeholder="Select object" />
|
||||
|
|
@ -138,7 +146,7 @@ const SelectObjectFromGroup = () => {
|
|||
</Select>
|
||||
|
||||
{selectedObject && (
|
||||
<div className='w-[100px] h-[100px] bg-muted rounded-md overflow-hidden border border-gray-300'>
|
||||
<div className="w-[100px] h-[100px] bg-muted rounded-md overflow-hidden border border-gray-300">
|
||||
<canvas ref={previewCanvasRef} width={100} height={100} />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -146,6 +154,6 @@ const SelectObjectFromGroup = () => {
|
|||
</Card>
|
||||
<Separator className="my-4" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
export default SelectObjectFromGroup;
|
||||
|
|
@ -28,7 +28,7 @@ const ShadowCustomization = () => {
|
|||
setBlur(activeObject?.shadow?.blur || 0.5);
|
||||
setOpacity(activeObject?.shadow?.opacity || 0.5);
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
const handleApplyShadow = () => {
|
||||
if (activeObject && canvas) {
|
||||
|
|
@ -44,14 +44,14 @@ const ShadowCustomization = () => {
|
|||
activeObject.dirty = true; // Mark the object as dirty for re-render
|
||||
canvas.renderAll(); // Trigger canvas re-render
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisableShadow = () => {
|
||||
if (activeObject && canvas) {
|
||||
activeObject.set("shadow", null)
|
||||
canvas.renderAll()
|
||||
}
|
||||
activeObject.set("shadow", null);
|
||||
canvas.renderAll();
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
return (
|
||||
|
|
@ -116,22 +116,31 @@ const ShadowCustomization = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-32 h-32 bg-white rounded-md flex items-center justify-center mx-auto border-2 my-4" style={{
|
||||
boxShadow: `${offsetX}px ${offsetY}px ${blur}px ${shadowColor}${Math.round(opacity * 255).toString(16).padStart(2, '0')}`
|
||||
}}>
|
||||
<div
|
||||
className="w-32 h-32 bg-white rounded-md flex items-center justify-center mx-auto border-2 my-4"
|
||||
style={{
|
||||
boxShadow: `${offsetX}px ${offsetY}px ${blur}px ${shadowColor}${Math.round(
|
||||
opacity * 255
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`,
|
||||
}}
|
||||
>
|
||||
<span className="text-4xl">A</span>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Button onClick={handleApplyShadow}>Apply Shadow</Button>
|
||||
<Button variant="outline" onClick={handleDisableShadow}>Disable Shadow</Button>
|
||||
<Button variant="outline" onClick={handleDisableShadow}>
|
||||
Disable Shadow
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className='p-2'>
|
||||
<Card className="shadow-none border-0">
|
||||
<CollapsibleComponent text={"Shadow Control"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
|
|
@ -140,4 +149,3 @@ const ShadowCustomization = () => {
|
|||
};
|
||||
|
||||
export default ShadowCustomization;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
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);
|
||||
|
|
@ -17,7 +17,7 @@ const SkewCustomization = () => {
|
|||
setSkewX(activeObject?.skewX);
|
||||
setSkewY(activeObject?.skewY);
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
// Update skewX directly
|
||||
const handleSkewXChange = (value) => {
|
||||
|
|
@ -52,7 +52,7 @@ const SkewCustomization = () => {
|
|||
step={1}
|
||||
value={[skewX]}
|
||||
onValueChange={(value) => {
|
||||
handleSkewXChange(value)
|
||||
handleSkewXChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -65,21 +65,21 @@ const SkewCustomization = () => {
|
|||
step={1}
|
||||
value={[skewY]}
|
||||
onValueChange={(value) => {
|
||||
handleSkewYChange(value)
|
||||
handleSkewYChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<Card className="shadow-none border-0">
|
||||
<CollapsibleComponent text={"Skew Control"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SkewCustomization
|
||||
export default SkewCustomization;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
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);
|
||||
|
|
@ -26,10 +32,8 @@ const StrokeCustomization = () => {
|
|||
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");
|
||||
|
|
@ -42,7 +46,6 @@ const StrokeCustomization = () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
// Handle stroke width
|
||||
if (object.strokeWidth) {
|
||||
setStrokeWidth(object.strokeWidth || 0);
|
||||
}
|
||||
|
|
@ -74,10 +77,10 @@ const StrokeCustomization = () => {
|
|||
if (!previewRef.current) return;
|
||||
|
||||
const previewStyle = {
|
||||
width: '80px',
|
||||
height: '80px',
|
||||
width: "80px",
|
||||
height: "80px",
|
||||
border: `${strokeWidth}px solid`,
|
||||
borderRadius: '4px',
|
||||
borderRadius: "4px",
|
||||
};
|
||||
|
||||
if (colorType === "color") {
|
||||
|
|
@ -91,7 +94,13 @@ const StrokeCustomization = () => {
|
|||
|
||||
useEffect(() => {
|
||||
updatePreview();
|
||||
}, [strokeWidth, strokeColor, gradientStrokeColors, colorType, gradientDirection]);
|
||||
}, [
|
||||
strokeWidth,
|
||||
strokeColor,
|
||||
gradientStrokeColors,
|
||||
colorType,
|
||||
gradientDirection,
|
||||
]);
|
||||
|
||||
const handleStrokeWidthChange = (value) => {
|
||||
setStrokeWidth(value);
|
||||
|
|
@ -106,7 +115,7 @@ const StrokeCustomization = () => {
|
|||
};
|
||||
|
||||
const handleGradientColorChange = (key, e) => {
|
||||
setGradientStrokeColors(prev => ({ ...prev, [key]: e.target.value }));
|
||||
setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value }));
|
||||
};
|
||||
|
||||
const applyStrokeStyle = () => {
|
||||
|
|
@ -161,6 +170,17 @@ const StrokeCustomization = () => {
|
|||
canvas.renderAll();
|
||||
};
|
||||
|
||||
// Automatically apply stroke styles on state change
|
||||
useEffect(() => {
|
||||
applyStrokeStyle();
|
||||
}, [
|
||||
strokeWidth,
|
||||
strokeColor,
|
||||
gradientStrokeColors,
|
||||
colorType,
|
||||
gradientDirection,
|
||||
]);
|
||||
|
||||
const revertStroke = () => {
|
||||
if (!activeObject) return;
|
||||
|
||||
|
|
@ -209,21 +229,26 @@ const StrokeCustomization = () => {
|
|||
min={0}
|
||||
max={50}
|
||||
value={strokeWidth}
|
||||
onChange={(e) => handleStrokeWidthChange(Number(e.target.value))}
|
||||
onChange={(e) =>
|
||||
handleStrokeWidthChange(Number(e.target.value))
|
||||
}
|
||||
className="w-16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Tabs value={colorType} onValueChange={(value) => handleColorTypeChange(value)}>
|
||||
<Tabs
|
||||
value={colorType}
|
||||
onValueChange={(value) => handleColorTypeChange(value)}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="color">
|
||||
<Paintbrush className="w-4 h-4 mr-2" />
|
||||
Solid
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="gradient">
|
||||
<Gradient className="w-4 h-4 mr-2" />
|
||||
<TabsTrigger value="gradient" className="flex gap-2">
|
||||
<div className="h-4 w-4 rounded bg-gradient-to-r from-purple-500 to-pink-500" />
|
||||
Gradient
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
@ -241,7 +266,10 @@ const StrokeCustomization = () => {
|
|||
/>
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{ backgroundColor: strokeColor, borderRadius: '0.375rem' }}
|
||||
style={{
|
||||
backgroundColor: strokeColor,
|
||||
borderRadius: "0.375rem",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<Input
|
||||
|
|
@ -256,7 +284,10 @@ const StrokeCustomization = () => {
|
|||
<TabsContent value="gradient" className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="gradient-direction">Gradient Direction</Label>
|
||||
<Select value={gradientDirection} onValueChange={setGradientDirection}>
|
||||
<Select
|
||||
value={gradientDirection}
|
||||
onValueChange={setGradientDirection}
|
||||
>
|
||||
<SelectTrigger id="gradient-direction">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
|
@ -277,18 +308,21 @@ const StrokeCustomization = () => {
|
|||
id="gradient-color-1"
|
||||
type="color"
|
||||
value={gradientStrokeColors.color1}
|
||||
onChange={(e) => handleGradientColorChange('color1', e)}
|
||||
onChange={(e) => handleGradientColorChange("color1", e)}
|
||||
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{ backgroundColor: gradientStrokeColors.color1, borderRadius: '0.375rem' }}
|
||||
style={{
|
||||
backgroundColor: gradientStrokeColors.color1,
|
||||
borderRadius: "0.375rem",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={gradientStrokeColors.color1}
|
||||
onChange={(e) => handleGradientColorChange('color1', e)}
|
||||
onChange={(e) => handleGradientColorChange("color1", e)}
|
||||
className="flex-grow"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -302,18 +336,21 @@ const StrokeCustomization = () => {
|
|||
id="gradient-color-2"
|
||||
type="color"
|
||||
value={gradientStrokeColors.color2}
|
||||
onChange={(e) => handleGradientColorChange('color2', e)}
|
||||
onChange={(e) => handleGradientColorChange("color2", e)}
|
||||
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{ backgroundColor: gradientStrokeColors.color2, borderRadius: '0.375rem' }}
|
||||
style={{
|
||||
backgroundColor: gradientStrokeColors.color2,
|
||||
borderRadius: "0.375rem",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={gradientStrokeColors.color2}
|
||||
onChange={(e) => handleGradientColorChange('color2', e)}
|
||||
onChange={(e) => handleGradientColorChange("color2", e)}
|
||||
className="flex-grow"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -324,13 +361,19 @@ const StrokeCustomization = () => {
|
|||
|
||||
<div className="space-y-1">
|
||||
<Label>Preview</Label>
|
||||
<div className="border rounded-md p-2 flex items-center justify-center" style={{ height: '120px' }}>
|
||||
<div ref={previewRef} style={{ width: '80px', height: '80px' }}></div>
|
||||
<div
|
||||
className="border rounded-md p-2 flex items-center justify-center"
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div
|
||||
ref={previewRef}
|
||||
style={{ width: "80px", height: "80px" }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-1">
|
||||
<Button onClick={applyStrokeStyle}>
|
||||
<Button onClick={applyStrokeStyle} className="bg-[#FF2B85]">
|
||||
Apply Stroke
|
||||
</Button>
|
||||
<Button onClick={revertStroke} variant="outline">
|
||||
|
|
@ -339,11 +382,11 @@ const StrokeCustomization = () => {
|
|||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<Card className=" shadow-none border-0">
|
||||
<CollapsibleComponent text={"Stroke Control"}>
|
||||
{content()}
|
||||
</CollapsibleComponent>
|
||||
|
|
@ -352,16 +395,3 @@ const StrokeCustomization = () => {
|
|||
};
|
||||
|
||||
export default StrokeCustomization;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,81 @@
|
|||
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 = () => {
|
||||
|
|
@ -27,40 +83,40 @@ const TextCustomization = () => {
|
|||
const { activeObject } = useContext(ActiveObjectContext);
|
||||
const { canvas } = useContext(CanvasContext);
|
||||
|
||||
const [text, setText] = useState('');
|
||||
const [fontFamily, setFontFamily] = useState('Arial');
|
||||
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 [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 [textAlign, setTextAlign] = useState("left");
|
||||
const [previewText, setPreviewText] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (activeObject?.type === "i-text") {
|
||||
setText(activeObject?.text || '');
|
||||
setFontFamily(activeObject?.fontFamily || 'Arial');
|
||||
setText(activeObject?.text || "");
|
||||
setFontFamily(activeObject?.fontFamily || "Arial");
|
||||
setFontSize(activeObject?.fontSize || 20);
|
||||
setFontStyle(activeObject?.fontStyle || 'normal');
|
||||
setFontWeight(activeObject?.fontWeight || 'normal');
|
||||
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 || '');
|
||||
setTextAlign(activeObject?.textAlign || "left");
|
||||
setPreviewText(activeObject?.text || "");
|
||||
}
|
||||
}, [activeObject])
|
||||
}, [activeObject]);
|
||||
|
||||
const updateActiveObject = (properties) => {
|
||||
if (activeObject?.type === "i-text") {
|
||||
activeObject.set(properties)
|
||||
canvas?.renderAll()
|
||||
}
|
||||
activeObject.set(properties);
|
||||
canvas?.renderAll();
|
||||
}
|
||||
};
|
||||
|
||||
const applyChanges = () => {
|
||||
updateActiveObject({
|
||||
|
|
@ -73,69 +129,69 @@ const TextCustomization = () => {
|
|||
charSpacing,
|
||||
underline,
|
||||
linethrough,
|
||||
textAlign
|
||||
textAlign,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextChange = (newText) => {
|
||||
setText(newText);
|
||||
setPreviewText(newText);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFontFamilyChange = (newFontFamily) => {
|
||||
setFontFamily(newFontFamily);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFontSizeChange = (newFontSize) => {
|
||||
setFontSize(newFontSize)
|
||||
}
|
||||
setFontSize(newFontSize);
|
||||
};
|
||||
|
||||
const handleTextAlignChange = (newTextAlign) => {
|
||||
setTextAlign(newTextAlign)
|
||||
}
|
||||
setTextAlign(newTextAlign);
|
||||
};
|
||||
|
||||
const handleFontStyleChange = () => {
|
||||
const newFontStyle = fontStyle === 'normal' ? 'italic' : 'normal'
|
||||
setFontStyle(newFontStyle)
|
||||
}
|
||||
const newFontStyle = fontStyle === "normal" ? "italic" : "normal";
|
||||
setFontStyle(newFontStyle);
|
||||
};
|
||||
|
||||
const handleFontWeightChange = () => {
|
||||
const newFontWeight = fontWeight === 'normal' ? 'bold' : 'normal'
|
||||
setFontWeight(newFontWeight)
|
||||
}
|
||||
const newFontWeight = fontWeight === "normal" ? "bold" : "normal";
|
||||
setFontWeight(newFontWeight);
|
||||
};
|
||||
|
||||
const handleLineHeightChange = (newLineHeight) => {
|
||||
setLineHeight(newLineHeight)
|
||||
}
|
||||
setLineHeight(newLineHeight);
|
||||
};
|
||||
|
||||
const handleCharSpacingChange = (newCharSpacing) => {
|
||||
setCharSpacing(newCharSpacing)
|
||||
}
|
||||
setCharSpacing(newCharSpacing);
|
||||
};
|
||||
|
||||
const handleUnderlineChange = () => {
|
||||
const newUnderline = !underline
|
||||
setUnderline(newUnderline)
|
||||
}
|
||||
const newUnderline = !underline;
|
||||
setUnderline(newUnderline);
|
||||
};
|
||||
|
||||
const handleLinethroughChange = () => {
|
||||
const newLinethrough = !linethrough
|
||||
setLinethrough(newLinethrough)
|
||||
}
|
||||
const newLinethrough = !linethrough;
|
||||
setLinethrough(newLinethrough);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!(activeObject?.type === "i-text")) {
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className="mt-2">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<p className="text-center text-gray-500">Select a text object to customize</p>
|
||||
<p className="text-center text-gray-500">
|
||||
Select a text object to customize
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
else {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<CardContent className="p-2">
|
||||
<Tabs defaultValue="text" className="w-full">
|
||||
|
|
@ -155,14 +211,21 @@ const TextCustomization = () => {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Font Family</Label>
|
||||
<Select value={fontFamily} onValueChange={handleFontFamilyChange}>
|
||||
<Select
|
||||
value={fontFamily}
|
||||
onValueChange={handleFontFamilyChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a font" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="h-[250px]">
|
||||
<SelectGroup>
|
||||
{fonts.map((font) => (
|
||||
<SelectItem style={{ fontFamily: font }} key={font} value={font}>
|
||||
<SelectItem
|
||||
style={{ fontFamily: font }}
|
||||
key={font}
|
||||
value={font}
|
||||
>
|
||||
{font}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -184,7 +247,9 @@ const TextCustomization = () => {
|
|||
<Input
|
||||
type="number"
|
||||
value={fontSize}
|
||||
onChange={(e) => handleFontSizeChange(parseInt(e.target.value, 10) || 16)}
|
||||
onChange={(e) =>
|
||||
handleFontSizeChange(parseInt(e.target.value, 10) || 16)
|
||||
}
|
||||
className="w-16"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -195,23 +260,23 @@ const TextCustomization = () => {
|
|||
<Label>Text Alignment</Label>
|
||||
<div className="flex space-x-1">
|
||||
<Button
|
||||
variant={textAlign === 'left' ? 'default' : 'outline'}
|
||||
variant={textAlign === "left" ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={() => handleTextAlignChange('left')}
|
||||
onClick={() => handleTextAlignChange("left")}
|
||||
>
|
||||
<AlignLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={textAlign === 'center' ? 'default' : 'outline'}
|
||||
variant={textAlign === "center" ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={() => handleTextAlignChange('center')}
|
||||
onClick={() => handleTextAlignChange("center")}
|
||||
>
|
||||
<AlignCenter className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={textAlign === 'right' ? 'default' : 'outline'}
|
||||
variant={textAlign === "right" ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={() => handleTextAlignChange('right')}
|
||||
onClick={() => handleTextAlignChange("right")}
|
||||
>
|
||||
<AlignRight className="h-4 w-4" />
|
||||
</Button>
|
||||
|
|
@ -221,28 +286,28 @@ const TextCustomization = () => {
|
|||
<Label>Text Style</Label>
|
||||
<div className="flex space-x-1">
|
||||
<Button
|
||||
variant={fontWeight === 'bold' ? 'default' : 'outline'}
|
||||
variant={fontWeight === "bold" ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={handleFontWeightChange}
|
||||
>
|
||||
<Bold className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={fontStyle === 'italic' ? 'default' : 'outline'}
|
||||
variant={fontStyle === "italic" ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={handleFontStyleChange}
|
||||
>
|
||||
<Italic className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={underline ? 'default' : 'outline'}
|
||||
variant={underline ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={handleUnderlineChange}
|
||||
>
|
||||
<Underline className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={linethrough ? 'default' : 'outline'}
|
||||
variant={linethrough ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={handleLinethroughChange}
|
||||
>
|
||||
|
|
@ -273,9 +338,9 @@ const TextCustomization = () => {
|
|||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
|
|
@ -283,32 +348,35 @@ const TextCustomization = () => {
|
|||
{content()}
|
||||
</CollapsibleComponent>
|
||||
<div className="mt-4 space-y-4">
|
||||
{
|
||||
previewText &&
|
||||
{previewText && (
|
||||
<div className="p-4 border rounded-md overflow-hidden">
|
||||
<p className="font-bold mb-2">Preview:</p>
|
||||
<p style={{
|
||||
<p
|
||||
style={{
|
||||
fontFamily,
|
||||
fontSize: `${fontSize}px`,
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
lineHeight,
|
||||
letterSpacing: `${charSpacing}px`,
|
||||
textDecoration: `${underline ? 'underline' : ''} ${linethrough ? 'line-through' : ''}`,
|
||||
textAlign
|
||||
}} className='truncate'>
|
||||
textDecoration: `${underline ? "underline" : ""} ${
|
||||
linethrough ? "line-through" : ""
|
||||
}`,
|
||||
textAlign,
|
||||
}}
|
||||
className="truncate"
|
||||
>
|
||||
{previewText}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
<Button onClick={applyChanges} className="w-full">
|
||||
Apply Changes
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextCustomization
|
||||
);
|
||||
};
|
||||
|
||||
export default TextCustomization;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="w-20 border-r h-screen bg-background flex flex-col items-center py-4 gap-6">
|
||||
{sidebarItems.map((item) => {
|
||||
|
|
@ -19,10 +30,12 @@ export function Sidebar({ selectedItem, onItemSelect }) {
|
|||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => onItemSelect(item.id)}
|
||||
onClick={() => {
|
||||
setSelectedPanel(item.id);
|
||||
}}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1 p-2 rounded-lg w-16 hover:bg-accent",
|
||||
selectedItem === item.id && "bg-accent"
|
||||
selectedPanel === item.id && "bg-accent"
|
||||
)}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { X } from "lucide-react";
|
||||
import { Button } from "../ui/button";
|
||||
import DesignPanel from "../Panel/DesignPanel";
|
||||
// import TextPanel from "../Panel/TextPanel";
|
||||
import ShapePanel from "../Panel/ShapePanel";
|
||||
|
||||
const panels = {
|
||||
design: DesignPanel,
|
||||
// text: TextPanel,
|
||||
shape: ShapePanel,
|
||||
// Add other panels as needed
|
||||
};
|
||||
|
||||
export default function RightPanel({ activePanel }) {
|
||||
const PanelComponent = panels[activePanel] || (() => null);
|
||||
|
||||
return (
|
||||
<div className="fixed right-0 top-0 h-screen w-72 border-l bg-background p-4">
|
||||
<div className="flex items-center justify-between border-b pb-4">
|
||||
<h2 className="text-lg font-semibold capitalize">{activePanel}</h2>
|
||||
<Button variant="ghost" size="icon">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<PanelComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex-1 h-[88vh] bg-[#F5F0FF] p-8 flex flex-col mt-20">
|
||||
{/* Main Canvas Area */}
|
||||
<div className="flex-1 bg-white rounded-3xl shadow-sm mb-4 flex items-center justify-center">
|
||||
<div className="w-32 h-32 bg-muted rounded-lg" />
|
||||
</div>
|
||||
<Card className="w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none flex-1 flex flex-col mt-24 mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none">
|
||||
<CardContent className="p-0 space-y-2">
|
||||
<AspectRatio
|
||||
ratio={getRatioValue(canvasRatio)}
|
||||
className="overflow-y-scroll shadow-red-200 overflow-x-hidden shadow-lg rounded-lg border-2 border-primary/10 transition-all duration-300 ease-in-out hover:shadow-xl scrollbar-hide"
|
||||
>
|
||||
<div
|
||||
ref={canvasRef}
|
||||
className="w-full h-full flex items-center justify-center bg-white rounded-md shadow-lg"
|
||||
id="canvas-ref"
|
||||
>
|
||||
<canvas id="fabric-canvas" />
|
||||
</div>
|
||||
</AspectRatio>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
65
src/components/Panel/CommonPanel.jsx
Normal file
65
src/components/Panel/CommonPanel.jsx
Normal file
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="space-y-5">
|
||||
<SelectObjectFromGroup />
|
||||
|
||||
{/* Apply stroke and stroke color */}
|
||||
{!customClipPath && (
|
||||
<>
|
||||
<StrokeCustomization />
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeObject?.type !== "group" && (
|
||||
<>
|
||||
<PositionCustomization />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Controls for opacity, flip, and rotation */}
|
||||
<Card className="shadow-none border-0">
|
||||
<CollapsibleComponent text={"Opacity, Flip, Rotate Control"}>
|
||||
<div className="space-y-2">
|
||||
<OpacityCustomization />
|
||||
<FlipCustomization />
|
||||
<RotateCustomization />
|
||||
</div>
|
||||
</CollapsibleComponent>
|
||||
</Card>
|
||||
|
||||
{/* Skew Customization */}
|
||||
<SkewCustomization />
|
||||
|
||||
{/* Scale Objects */}
|
||||
<ScaleObjects />
|
||||
|
||||
{/* Shadow Customization */}
|
||||
<ShadowCustomization />
|
||||
|
||||
{/* Add image into shape */}
|
||||
<AddImageIntoShape />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommonPanel;
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import { Label } from "../ui/label";
|
||||
import { Slider } from "../ui/slider";
|
||||
|
||||
export default function DesignPanel() {
|
||||
return (
|
||||
<div className="space-y-4 pt-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Shadow Style</Label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-square rounded-lg bg-muted hover:bg-muted/80"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Offset X</Label>
|
||||
<Slider defaultValue={[0]} max={100} step={1} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Offset Y</Label>
|
||||
<Slider defaultValue={[0]} max={100} step={1} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Blur</Label>
|
||||
<Slider defaultValue={[0]} max={100} step={1} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Opacity</Label>
|
||||
<Slider defaultValue={[100]} max={100} step={1} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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: (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold mb-4">Text</h2>
|
||||
<Button className="w-full">
|
||||
<Plus className="mr-2 h-4 w-4" /> Add Text
|
||||
</Button>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Input placeholder="Add a Heading" />
|
||||
<Input placeholder="Add a Subheading" />
|
||||
<Input placeholder="Add body text" />
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
shape: (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold mb-4">Shape</h2>
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search shapes" className="pl-8" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="aspect-square bg-muted rounded-md" />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
image: (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold mb-4">Image</h2>
|
||||
<Button className="w-full">
|
||||
<Upload className="mr-2 h-4 w-4" /> Upload Image
|
||||
</Button>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div key={i} className="aspect-square bg-muted rounded-md" />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
const renderPanel = () => {
|
||||
switch (selectedPanel) {
|
||||
case "text":
|
||||
return <TextPanel />;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-80 bg-background border-r border-border p-4 overflow-y-auto">
|
||||
{panelContent[type]}
|
||||
<>
|
||||
{selectedPanel !== "" && (
|
||||
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-xl shadow-lg mx-4 my-4">
|
||||
{renderPanel()}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default EditorPanel;
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { Input } from "../ui/input";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
|
||||
export default function ShapePanel() {
|
||||
return (
|
||||
<div className="space-y-4 pt-4">
|
||||
<Input placeholder="Search with project name" />
|
||||
<ScrollArea className="h-[calc(100vh-8rem)]">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Array.from({ length: 9 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-square rounded-md bg-muted hover:bg-muted/80"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-3xl shadow-lg mx-4 my-4">
|
||||
<ScrollArea className="h-[calc(100vh-32px)] px-4 py-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<div className="flex justify-between items-center p-4 border-b">
|
||||
<h2 className="text-lg font-semibold">Text</h2>
|
||||
<Button variant="ghost" size="icon">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button className="w-full bg-[#FF4D8D] hover:bg-[#FF3D7D] text-white rounded-2xl mb-6 h-12 text-base font-medium">
|
||||
<ScrollArea className="h-[calc(100vh-115px)] px-4 py-4">
|
||||
<Button
|
||||
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
|
||||
onClick={() => {
|
||||
addText();
|
||||
}}
|
||||
>
|
||||
Add Text
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M7.5 18.3333H12.5C16.6667 18.3333 18.3333 16.6666 18.3333 12.5V7.49996C18.3333 3.33329 16.6667 1.66663 12.5 1.66663H7.5C3.33333 1.66663 1.66667 3.33329 1.66667 7.49996V12.5C1.66667 16.6666 3.33333 18.3333 7.5 18.3333Z"
|
||||
stroke="white"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.83333 7.40837C8.45833 6.10004 11.5417 6.10004 14.1667 7.40837"
|
||||
stroke="white"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 13.5833V6.60828"
|
||||
stroke="white"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
|
||||
{activeObject ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Default text style
|
||||
<CommonPanel />
|
||||
<TextCustomization />
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm font-semibold text-center">
|
||||
No active object found
|
||||
</p>
|
||||
<Input
|
||||
placeholder="Add a Heading H1"
|
||||
className="mb-2 rounded-2xl h-12"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Add a Subheading H2"
|
||||
className="mb-2 rounded-2xl h-12"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Add Some body text"
|
||||
className="rounded-2xl h-12"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<p className="text-sm text-muted-foreground">Text Design</p>
|
||||
<Button variant="link" size="sm">
|
||||
See all
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="aspect-square bg-muted rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue