added text section design layout

This commit is contained in:
smfahim25 2025-01-26 17:26:25 +06:00
parent 976978aec4
commit 954ac950b0
21 changed files with 2156 additions and 1822 deletions

View file

@ -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 />

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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" />

View file

@ -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>
);
}

View file

@ -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>
);
}

View 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;

View file

@ -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>
);
}

View file

@ -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;

View file

@ -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>
);
}

View file

@ -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>
);