canvas layout change, customization alignment changed
This commit is contained in:
parent
ac870250d5
commit
29a30904f1
13 changed files with 482 additions and 351 deletions
|
|
@ -8,6 +8,7 @@ import { Card, CardContent } from './ui/card';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Separator } from './ui/separator';
|
import { Separator } from './ui/separator';
|
||||||
|
import { ObjectShortcut } from './ObjectShortcut';
|
||||||
|
|
||||||
const aspectRatios = [
|
const aspectRatios = [
|
||||||
{ value: "1:1", label: "Square (1:1)" },
|
{ value: "1:1", label: "Square (1:1)" },
|
||||||
|
|
@ -28,10 +29,10 @@ const aspectRatios = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function AspectCanvas() {
|
export function AspectCanvas() {
|
||||||
const { setLeftPanelOpen, setRightPanelOpen, setOpenPanel, setCaptureOpen, setOpenSetting, openObjectPanel, setOpenObjectPanel, rightPanelOpen } = useContext(OpenContext);
|
const { setLeftPanelOpen, setRightPanelOpen, setOpenPanel, setCaptureOpen, setOpenSetting, setOpenObjectPanel, rightPanelOpen } = useContext(OpenContext);
|
||||||
const [selectedRatio, setSelectedRatio] = useState("4:3");
|
const [selectedRatio, setSelectedRatio] = useState("4:3");
|
||||||
|
|
||||||
const { canvasRef, canvas, setCanvas, fabricCanvasRef, setCanvasHeight, setCanvasWidth } = useContext(CanvasContext);
|
const { canvasRef, canvas, setCanvas, fabricCanvasRef, setCanvasHeight, setCanvasWidth, setScreenWidth } = useContext(CanvasContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
import('fabric').then((fabricModule) => {
|
import('fabric').then((fabricModule) => {
|
||||||
|
|
@ -80,6 +81,8 @@ export function AspectCanvas() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setScreenWidth(document.getElementById("root").offsetWidth);
|
||||||
|
|
||||||
// Handle responsive behavior for panels
|
// Handle responsive behavior for panels
|
||||||
if (document.getElementById("root").offsetWidth <= 640) {
|
if (document.getElementById("root").offsetWidth <= 640) {
|
||||||
setLeftPanelOpen(false);
|
setLeftPanelOpen(false);
|
||||||
|
|
@ -87,9 +90,9 @@ export function AspectCanvas() {
|
||||||
}
|
}
|
||||||
if (document.getElementById("root").offsetWidth > 640) {
|
if (document.getElementById("root").offsetWidth > 640) {
|
||||||
setOpenObjectPanel(false);
|
setOpenObjectPanel(false);
|
||||||
|
setOpenSetting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial setup
|
// Initial setup
|
||||||
updateCanvasSize();
|
updateCanvasSize();
|
||||||
|
|
||||||
|
|
@ -98,18 +101,7 @@ export function AspectCanvas() {
|
||||||
|
|
||||||
// Cleanup listener on unmount
|
// Cleanup listener on unmount
|
||||||
return () => window.removeEventListener('resize', updateCanvasSize);
|
return () => window.removeEventListener('resize', updateCanvasSize);
|
||||||
}, [
|
}, [setCanvasWidth, setCanvasHeight, selectedRatio, canvasRef, canvas, setLeftPanelOpen, setOpenObjectPanel, setRightPanelOpen, rightPanelOpen, setScreenWidth, setOpenSetting]);
|
||||||
setCanvasWidth,
|
|
||||||
setCanvasHeight,
|
|
||||||
selectedRatio,
|
|
||||||
canvasRef,
|
|
||||||
canvas,
|
|
||||||
openObjectPanel,
|
|
||||||
setLeftPanelOpen,
|
|
||||||
setOpenObjectPanel,
|
|
||||||
setRightPanelOpen,
|
|
||||||
rightPanelOpen
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.fabric) {
|
if (window.fabric) {
|
||||||
|
|
@ -138,10 +130,10 @@ export function AspectCanvas() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none">
|
<Card className="w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none">
|
||||||
<CardContent className="p-0 space-y-4">
|
<CardContent className="p-0 space-y-2">
|
||||||
<div className='flex w-full flex-wrap items-center justify-between mx-auto gap-2'>
|
<div className='flex w-full flex-wrap items-center justify-between mx-auto'>
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-1'>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|
@ -155,19 +147,22 @@ export function AspectCanvas() {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<div className="flex justify-between gap-2 items-center">
|
<div className="flex justify-between gap-1 items-center">
|
||||||
<TooltipProvider>
|
<div className="block xl:hidden lg:hidden md:hidden">
|
||||||
<Tooltip>
|
<TooltipProvider>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button variant="outline" size="icon" onClick={() => setOpenSetting(true)}>
|
<TooltipTrigger asChild>
|
||||||
<Settings className="h-4 w-4" />
|
<Button variant="outline" size="icon" onClick={() => setOpenSetting(true)}>
|
||||||
</Button>
|
<Settings className="h-4 w-4" />
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>
|
</TooltipTrigger>
|
||||||
<p>Open Settings</p>
|
<TooltipContent>
|
||||||
</TooltipContent>
|
<p>Open Settings</p>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|
@ -180,6 +175,7 @@ export function AspectCanvas() {
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|
@ -201,12 +197,12 @@ export function AspectCanvas() {
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Select onValueChange={handleRatioChange} value={selectedRatio}>
|
<Select onValueChange={handleRatioChange} value={selectedRatio}>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full text-xs font-bold">
|
||||||
<SelectValue placeholder="Select aspect ratio" />
|
<SelectValue placeholder="Select aspect ratio" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{aspectRatios.map((ratio) => (
|
{aspectRatios.map((ratio) => (
|
||||||
<SelectItem key={ratio.value} value={ratio.value}>
|
<SelectItem key={ratio.value} value={ratio.value} className="text-xs font-bold">
|
||||||
{ratio.label}
|
{ratio.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -223,6 +219,10 @@ export function AspectCanvas() {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ObjectShortcut value={"default"} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<AspectRatio ratio={getRatioValue(selectedRatio)} 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">
|
<AspectRatio ratio={getRatioValue(selectedRatio)} 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">
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { ScrollArea } from './ui/scroll-area';
|
||||||
import RndComponent from './Layouts/RndComponent';
|
import RndComponent from './Layouts/RndComponent';
|
||||||
|
|
||||||
const CanvasSetting = () => {
|
const CanvasSetting = () => {
|
||||||
const { canvas, canvasHeight, canvasWidth } = useContext(CanvasContext);
|
const { canvas, canvasHeight, canvasWidth, screenWidth } = useContext(CanvasContext);
|
||||||
const { setOpenSetting } = useContext(OpenContext);
|
const { setOpenSetting } = useContext(OpenContext);
|
||||||
const bgImgRef = useRef(null);
|
const bgImgRef = useRef(null);
|
||||||
|
|
||||||
|
|
@ -168,17 +168,18 @@ const CanvasSetting = () => {
|
||||||
maxHeight: 400,
|
maxHeight: 400,
|
||||||
bound: "parent"
|
bound: "parent"
|
||||||
}
|
}
|
||||||
|
const content = () => {
|
||||||
return (
|
return (
|
||||||
<RndComponent value={rndValue}>
|
<Card className="xl:p-0 lg:p-0 md:p-0 p-2">
|
||||||
<Card className="px-2 py-2">
|
<CardTitle className="flex items-center flex-wrap justify-between gap-1 xl:hidden lg:hidden md:hidden">Canvas Setting <Button className="rnd-escape" variant="secondary" onClick={() => setOpenSetting(false)}><X /></Button> </CardTitle>
|
||||||
<CardTitle className="flex items-center flex-wrap justify-between gap-1">Canvas Setting <Button className="rnd-escape" variant="secondary" onClick={() => setOpenSetting(false)}><X /></Button> </CardTitle>
|
<Separator className="mt-4 block xl:hidden lg:hidden md:hidden" />
|
||||||
<Separator className="mt-4" />
|
|
||||||
<ScrollArea className="h-[400px] xl:h-fit lg:h-fit md:h-fit">
|
<ScrollArea className="h-[400px] xl:h-fit lg:h-fit md:h-fit">
|
||||||
<div className='rnd-escape'>
|
<div className='rnd-escape'>
|
||||||
|
|
||||||
<ColorComponent />
|
<ColorComponent />
|
||||||
|
|
||||||
|
<Separator className="mt-2" />
|
||||||
|
|
||||||
<div className='flex flex-col my-2 gap-2 rnd-escape'>
|
<div className='flex flex-col my-2 gap-2 rnd-escape'>
|
||||||
<div>
|
<div>
|
||||||
<Label>Background:</Label>
|
<Label>Background:</Label>
|
||||||
|
|
@ -215,12 +216,10 @@ const CanvasSetting = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator className="mt-4" />
|
|
||||||
|
|
||||||
{/* opacity */}
|
{/* opacity */}
|
||||||
<div className='flex flex-col gap-2 rnd-escape'>
|
<div className='flex flex-col gap-2 rnd-escape mt-2'>
|
||||||
<Label>
|
<Label>
|
||||||
Adjust Opacity:
|
Background Opacity:
|
||||||
</Label>
|
</Label>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={[1.0]} // Default value, you can set it to 0.0 or another value
|
defaultValue={[1.0]} // Default value, you can set it to 0.0 or another value
|
||||||
|
|
@ -274,8 +273,18 @@ const CanvasSetting = () => {
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Card>
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return screenWidth <= 768 ? (
|
||||||
|
<RndComponent value={rndValue}>
|
||||||
|
{content()}
|
||||||
</RndComponent>
|
</RndComponent>
|
||||||
)
|
) : (
|
||||||
|
<div>
|
||||||
|
{content()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CanvasSetting
|
export default CanvasSetting
|
||||||
|
|
@ -1,104 +1,134 @@
|
||||||
import { useContext, useEffect } from 'react'
|
import { useContext, useEffect, useState } from 'react'
|
||||||
import { Label } from './ui/label';
|
import { Label } from '@/components/ui/label'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { Input } from './ui/input';
|
import { Input } from '@/components/ui/input'
|
||||||
import ColorContext from './Context/colorContext/ColorContext';
|
import { Button } from '@/components/ui/button'
|
||||||
import CanvasContext from './Context/canvasContext/CanvasContext';
|
import ColorContext from './Context/colorContext/ColorContext'
|
||||||
import { fabric } from 'fabric';
|
import CanvasContext from './Context/canvasContext/CanvasContext'
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
const ColorComponent = () => {
|
const ColorComponent = () => {
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext)
|
||||||
const {
|
const { backgroundType, setBackgroundType, solidColor, gradientColors, setSolidColor, setGradientColors, gradientDirection, setGradientDirection, setDirectionCoords,
|
||||||
backgroundType,
|
} = useContext(ColorContext)
|
||||||
setBackgroundType,
|
|
||||||
solidColor,
|
|
||||||
gradientColors,
|
|
||||||
setSolidColor,
|
|
||||||
setGradientColors,
|
|
||||||
gradientDirection,
|
|
||||||
setGradientDirection,
|
|
||||||
setDirectionCoords,
|
|
||||||
directionCoords,
|
|
||||||
} = useContext(ColorContext);
|
|
||||||
|
|
||||||
const applySolidColor = (color) => {
|
const [previewBackgroundType, setPreviewBackgroundType] = useState(backgroundType)
|
||||||
setSolidColor(color);
|
const [previewSolidColor, setPreviewSolidColor] = useState(solidColor)
|
||||||
};
|
const [previewGradientColors, setPreviewGradientColors] = useState(gradientColors)
|
||||||
|
const [previewGradientDirection, setPreviewGradientDirection] = useState(gradientDirection)
|
||||||
const applyGradient = () => {
|
|
||||||
const width = canvas?.width || 0;
|
|
||||||
const height = canvas?.height || 0;
|
|
||||||
|
|
||||||
// Define coordinates for linear gradient
|
|
||||||
const linearCoords = {
|
|
||||||
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
|
|
||||||
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
|
|
||||||
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
|
|
||||||
"right-to-left": { x1: width, y1: 0, x2: 0, y2: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const directionCoords = linearCoords[gradientDirection];
|
|
||||||
if (directionCoords) {
|
|
||||||
setDirectionCoords(directionCoords);
|
|
||||||
} else {
|
|
||||||
console.error(`Invalid gradient direction: ${gradientDirection}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gradientDirection && backgroundType === "gradient") {
|
setPreviewBackgroundType(backgroundType)
|
||||||
applyGradient();
|
setPreviewSolidColor(solidColor)
|
||||||
}
|
setPreviewGradientColors(gradientColors)
|
||||||
}, [gradientDirection, backgroundType]);
|
setPreviewGradientDirection(gradientDirection)
|
||||||
|
}, [backgroundType, solidColor, gradientColors, gradientDirection])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const object = canvas?.getActiveObject();
|
if (previewBackgroundType === "gradient" && canvas) {
|
||||||
if (!object) {
|
const width = canvas.width || 0
|
||||||
if (canvas && solidColor && backgroundType === "color") {
|
const height = canvas.height || 0
|
||||||
canvas.backgroundColor = solidColor;
|
|
||||||
canvas.renderAll();
|
const linearCoords = {
|
||||||
|
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
|
||||||
|
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
|
||||||
|
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
|
||||||
|
"right-to-left": { x1: width, y1: 0, x2: 0, y2: 0 },
|
||||||
}
|
}
|
||||||
if (canvas && directionCoords && gradientColors && backgroundType === "gradient") {
|
|
||||||
|
const coords = linearCoords[previewGradientDirection]
|
||||||
|
if (coords) {
|
||||||
|
setDirectionCoords(coords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [previewBackgroundType, previewGradientDirection, canvas, setDirectionCoords])
|
||||||
|
|
||||||
|
const applyChanges = () => {
|
||||||
|
setBackgroundType(previewBackgroundType)
|
||||||
|
setSolidColor(previewSolidColor)
|
||||||
|
setGradientColors(previewGradientColors)
|
||||||
|
setGradientDirection(previewGradientDirection)
|
||||||
|
applyToCanvas()
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyToCanvas = () => {
|
||||||
|
if (!canvas) return
|
||||||
|
|
||||||
|
if (previewBackgroundType === "color") {
|
||||||
|
canvas.backgroundColor = previewSolidColor
|
||||||
|
} else if (previewBackgroundType === "gradient") {
|
||||||
|
const width = canvas.width || 0
|
||||||
|
const height = canvas.height || 0
|
||||||
|
|
||||||
|
const linearCoords = {
|
||||||
|
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
|
||||||
|
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
|
||||||
|
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
|
||||||
|
"right-to-left": { x1: width, y1: 0, x2: 0, y2: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const coords = linearCoords[previewGradientDirection]
|
||||||
|
if (coords) {
|
||||||
|
setDirectionCoords(coords)
|
||||||
const gradient = new fabric.Gradient({
|
const gradient = new fabric.Gradient({
|
||||||
type: "linear",
|
type: "linear",
|
||||||
gradientUnits: "pixels",
|
gradientUnits: "pixels",
|
||||||
coords: directionCoords,
|
coords: coords,
|
||||||
colorStops: [
|
colorStops: [
|
||||||
{ offset: 0, color: gradientColors.color1 },
|
{ offset: 0, color: previewGradientColors.color1 },
|
||||||
{ offset: 1, color: gradientColors.color2 },
|
{ offset: 1, color: previewGradientColors.color2 },
|
||||||
],
|
],
|
||||||
});
|
})
|
||||||
canvas.backgroundColor = gradient;
|
canvas.backgroundColor = gradient
|
||||||
canvas.renderAll();
|
|
||||||
}
|
}
|
||||||
if (canvas && gradientColors && backgroundType === "radial") {
|
} else if (previewBackgroundType === "radial") {
|
||||||
const gradient = new fabric.Gradient({
|
const gradient = new fabric.Gradient({
|
||||||
type: "radial",
|
type: "radial",
|
||||||
gradientUnits: "pixels",
|
gradientUnits: "pixels",
|
||||||
coords: {
|
coords: {
|
||||||
x1: canvas.width / 2,
|
x1: canvas.width / 2,
|
||||||
y1: canvas.height / 2,
|
y1: canvas.height / 2,
|
||||||
r1: 0,
|
r1: 0,
|
||||||
x2: canvas.width / 2,
|
x2: canvas.width / 2,
|
||||||
y2: canvas.height / 2,
|
y2: canvas.height / 2,
|
||||||
r2: Math.min(canvas.width, canvas.height) / 2,
|
r2: Math.min(canvas.width, canvas.height) / 2,
|
||||||
},
|
},
|
||||||
colorStops: [
|
colorStops: [
|
||||||
{ offset: 0, color: gradientColors.color1 },
|
{ offset: 0, color: previewGradientColors.color1 },
|
||||||
{ offset: 1, color: gradientColors.color2 },
|
{ offset: 1, color: previewGradientColors.color2 },
|
||||||
],
|
],
|
||||||
});
|
})
|
||||||
canvas.backgroundColor = gradient;
|
canvas.backgroundColor = gradient
|
||||||
canvas.renderAll();
|
}
|
||||||
|
|
||||||
|
canvas.renderAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPreviewStyle = () => {
|
||||||
|
if (previewBackgroundType === "color") {
|
||||||
|
return { backgroundColor: previewSolidColor }
|
||||||
|
} else if (previewBackgroundType === "gradient") {
|
||||||
|
const direction = {
|
||||||
|
'top-to-bottom': '180deg',
|
||||||
|
'bottom-to-top': '0deg',
|
||||||
|
'left-to-right': '90deg',
|
||||||
|
'right-to-left': '270deg'
|
||||||
|
}[previewGradientDirection]
|
||||||
|
return {
|
||||||
|
backgroundImage: `linear-gradient(${direction}, ${previewGradientColors.color1}, ${previewGradientColors.color2})`
|
||||||
|
}
|
||||||
|
} else if (previewBackgroundType === "radial") {
|
||||||
|
return {
|
||||||
|
backgroundImage: `radial-gradient(circle, ${previewGradientColors.color1}, ${previewGradientColors.color2})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [gradientColors, directionCoords, solidColor, canvas, backgroundType]);
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap my-2 gap-2'>
|
<div className='flex flex-col gap-4 p-1'>
|
||||||
<div className='flex gap-2 flex-wrap mb-auto'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Background Type:</Label>
|
<Label>Background Type:</Label>
|
||||||
<Select value={backgroundType} onValueChange={setBackgroundType}>
|
<Select value={previewBackgroundType} onValueChange={setPreviewBackgroundType}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select background type" />
|
<SelectValue placeholder="Select background type" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -110,24 +140,22 @@ const ColorComponent = () => {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{backgroundType === "color" ? (
|
{previewBackgroundType === "color" ? (
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Solid Color:</Label>
|
<Label>Solid Color:</Label>
|
||||||
<Input
|
<Input
|
||||||
type="color"
|
type="color"
|
||||||
value={solidColor}
|
value={previewSolidColor}
|
||||||
onChange={(e) => applySolidColor(e.target.value)}
|
onChange={(e) => setPreviewSolidColor(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : backgroundType === "gradient" ? (
|
) : previewBackgroundType === "gradient" ? (
|
||||||
<div className='grid grid-cols-1 gap-2'>
|
<div className='grid grid-cols-1 gap-2'>
|
||||||
<div className='flex flex-wrap gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Direction:</Label>
|
<Label>Direction:</Label>
|
||||||
<Select
|
<Select
|
||||||
value={gradientDirection}
|
value={previewGradientDirection}
|
||||||
onValueChange={(value) => {
|
onValueChange={setPreviewGradientDirection}
|
||||||
setGradientDirection(value);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select direction" />
|
<SelectValue placeholder="Select direction" />
|
||||||
|
|
@ -141,26 +169,26 @@ const ColorComponent = () => {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex gap-2 flex-wrap'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Color 1:</Label>
|
<Label>Color 1:</Label>
|
||||||
<Input
|
<Input
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientColors.color1}
|
value={previewGradientColors.color1}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setGradientColors((prev) => ({
|
setPreviewGradientColors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
color1: e.target.value,
|
color1: e.target.value,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-wrap gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Color 2:</Label>
|
<Label>Color 2:</Label>
|
||||||
<Input
|
<Input
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientColors.color2}
|
value={previewGradientColors.color2}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setGradientColors((prev) => ({
|
setPreviewGradientColors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
color2: e.target.value,
|
color2: e.target.value,
|
||||||
}))}
|
}))}
|
||||||
|
|
@ -168,27 +196,27 @@ const ColorComponent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex flex-wrap gap-2'>
|
<div className='grid grid-cols-1 gap-2'>
|
||||||
<div className='flex gap-2 flex-wrap'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Color 1:</Label>
|
<Label>Color 1:</Label>
|
||||||
<Input
|
<Input
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientColors.color1}
|
value={previewGradientColors.color1}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setGradientColors((prev) => ({
|
setPreviewGradientColors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
color1: e.target.value,
|
color1: e.target.value,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-wrap gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label>Color 2:</Label>
|
<Label>Color 2:</Label>
|
||||||
<Input
|
<Input
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientColors.color2}
|
value={previewGradientColors.color2}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setGradientColors((prev) => ({
|
setPreviewGradientColors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
color2: e.target.value,
|
color2: e.target.value,
|
||||||
}))}
|
}))}
|
||||||
|
|
@ -196,8 +224,21 @@ const ColorComponent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ColorComponent;
|
<div>
|
||||||
|
<Label>Preview:</Label>
|
||||||
|
<div
|
||||||
|
className='w-full h-24 rounded-md mt-2'
|
||||||
|
style={getPreviewStyle()}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={applyChanges}>
|
||||||
|
Apply Changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ColorComponent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ const CanvasContextProvider = ({ children }) => {
|
||||||
const [canvas, setCanvas] = useState(null);
|
const [canvas, setCanvas] = useState(null);
|
||||||
const [canvasHeight, setCanvasHeight] = useState(0);
|
const [canvasHeight, setCanvasHeight] = useState(0);
|
||||||
const [canvasWidth, setCanvasWidth] = useState(0);
|
const [canvasWidth, setCanvasWidth] = useState(0);
|
||||||
|
const [screenWidth, setScreenWidth] = useState(0);
|
||||||
const fabricCanvasRef = useRef(null);
|
const fabricCanvasRef = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasContext.Provider value={{ canvasRef, canvas, setCanvas, fabricCanvasRef, canvasHeight, setCanvasHeight, canvasWidth, setCanvasWidth }}>
|
<CanvasContext.Provider value={{ canvasRef, canvas, setCanvas, fabricCanvasRef, canvasHeight, setCanvasHeight, canvasWidth, setCanvasWidth, screenWidth, setScreenWidth }}>
|
||||||
{children}
|
{children}
|
||||||
</CanvasContext.Provider>
|
</CanvasContext.Provider>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import ColorContext from './ColorContext'
|
||||||
|
|
||||||
const ColorContextProvider = ({ children }) => {
|
const ColorContextProvider = ({ children }) => {
|
||||||
const [backgroundType, setBackgroundType] = useState("color"); // 'color' or 'gradient'
|
const [backgroundType, setBackgroundType] = useState("color"); // 'color' or 'gradient'
|
||||||
const [solidColor, setSolidColor] = useState("#ffffff");
|
const [solidColor, setSolidColor] = useState("#FFA500");
|
||||||
const [gradientColors, setGradientColors] = useState({
|
const [gradientColors, setGradientColors] = useState({
|
||||||
color1: "#ffffff",
|
color1: "#ffffff",
|
||||||
color2: "#e26286",
|
color2: "#e26286",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
import { ChevronUp, ChevronDown } from "lucide-react";
|
import { ChevronUp, ChevronDown } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const CollapsibleComponent = ({ children, text }) => {
|
const CollapsibleComponent = ({ children, text }) => {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if the text prop is "Canvas Setting" and set isOpen to false
|
||||||
|
if (text === "Canvas Setting") {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIsOpen(true)
|
||||||
|
}
|
||||||
|
}, [text])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,11 @@ const AllIconsPage = () => {
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
className="border p-2 mb-0 w-full"
|
className="border p-2 mb-0 w-full"
|
||||||
/>
|
/>
|
||||||
<Card className="flex items-center justify-center py-1">
|
<Card className="flex items-center justify-center rounded-none p-1">
|
||||||
<Grid
|
<Grid
|
||||||
columnCount={4}
|
columnCount={4}
|
||||||
columnWidth={50}
|
columnWidth={50}
|
||||||
height={380}
|
height={330}
|
||||||
rowCount={Math.ceil(filtered.length / 4)}
|
rowCount={Math.ceil(filtered.length / 4)}
|
||||||
rowHeight={70}
|
rowHeight={70}
|
||||||
width={240}
|
width={240}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useContext, useState } from "react"
|
import { useContext, useState } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { BringToFront, CopyPlus, GroupIcon, PencilRuler, Save, Settings, Shapes, SquareX, Store, Trash2, UngroupIcon, Upload, ChevronDown, ChevronUp, MoveIcon, Type, ImageDown, X } from 'lucide-react';
|
import { PencilRuler, Save, Settings, Shapes, SquareX, Store, Upload, ChevronDown, ChevronUp, Type, ImageDown, X } from 'lucide-react';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
|
|
@ -10,6 +10,7 @@ import CanvasContext from './Context/canvasContext/CanvasContext';
|
||||||
import ActiveObjectContext from './Context/activeObject/ObjectContext';
|
import ActiveObjectContext from './Context/activeObject/ObjectContext';
|
||||||
import { fabric } from 'fabric';
|
import { fabric } from 'fabric';
|
||||||
import RndComponent from './Layouts/RndComponent';
|
import RndComponent from './Layouts/RndComponent';
|
||||||
|
import { ObjectShortcut } from "./ObjectShortcut";
|
||||||
|
|
||||||
export function EditPanel() {
|
export function EditPanel() {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
|
@ -17,108 +18,7 @@ export function EditPanel() {
|
||||||
|
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext);
|
||||||
|
|
||||||
const { setActiveObject, activeObject } = useContext(ActiveObjectContext);
|
const { setActiveObject } = useContext(ActiveObjectContext);
|
||||||
|
|
||||||
const groupSelectedObjects = () => {
|
|
||||||
const activeObjects = canvas.getActiveObjects(); // Get selected objects
|
|
||||||
if (activeObjects.length > 1) {
|
|
||||||
|
|
||||||
canvas.discardActiveObject();
|
|
||||||
|
|
||||||
const group = new fabric.Group(activeObjects, {
|
|
||||||
left: canvas?.width / 2,
|
|
||||||
top: canvas?.height / 2,
|
|
||||||
originX: "center",
|
|
||||||
originY: "center",
|
|
||||||
selectable: true, // Allow group selection
|
|
||||||
subTargetCheck: true, // Allow individual object selection
|
|
||||||
hasControls: true, // Enable resizing/movement of the group
|
|
||||||
})
|
|
||||||
canvas.remove(...activeObjects);
|
|
||||||
canvas.add(group);
|
|
||||||
canvas.setActiveObject(group);
|
|
||||||
setActiveObject(group);
|
|
||||||
canvas.renderAll();
|
|
||||||
} else {
|
|
||||||
console.log("Select at least two objects")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ungroupSelectedObjects = () => {
|
|
||||||
const activeObject = canvas.getActiveObject();
|
|
||||||
if (activeObject && activeObject.type === "group") {
|
|
||||||
// Get the group
|
|
||||||
const group = activeObject;
|
|
||||||
canvas.discardActiveObject();
|
|
||||||
// Remove the group from the canvas
|
|
||||||
canvas.remove(group);
|
|
||||||
setActiveObject(null);
|
|
||||||
|
|
||||||
// Iterate through each object in the group
|
|
||||||
group._objects.forEach((object) => {
|
|
||||||
// Calculate the absolute position based on the group's transformation
|
|
||||||
const objLeft = object.left * group.scaleX + group.left;
|
|
||||||
const objTop = object.top * group.scaleY + group.top;
|
|
||||||
|
|
||||||
// Reset transformations and positions
|
|
||||||
object.set({
|
|
||||||
left: objLeft,
|
|
||||||
top: objTop,
|
|
||||||
scaleX: object.scaleX * group.scaleX, // Adjust scale based on group
|
|
||||||
scaleY: object.scaleY * group.scaleY, // Adjust scale based on group
|
|
||||||
angle: object.angle + group.angle, // Adjust rotation
|
|
||||||
hasControls: true, // Allow resizing
|
|
||||||
selectable: true, // Make selectable
|
|
||||||
group: null, // Remove group reference
|
|
||||||
originX: "center",
|
|
||||||
originY: "center",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update coordinates
|
|
||||||
object.setCoords();
|
|
||||||
|
|
||||||
// Add the object back to the canvas
|
|
||||||
canvas.add(object);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the selection
|
|
||||||
canvas.discardActiveObject();
|
|
||||||
|
|
||||||
// Render the canvas to reflect changes
|
|
||||||
canvas.renderAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bring Selected Object to Front
|
|
||||||
const bringToFront = () => {
|
|
||||||
if (activeObject) {
|
|
||||||
activeObject.bringToFront();
|
|
||||||
canvas.renderAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove Selected Element
|
|
||||||
const removeSelected = useCallback(() => {
|
|
||||||
const activeObject = canvas?.getActiveObject();
|
|
||||||
|
|
||||||
const allObjects = canvas?.getObjects();
|
|
||||||
|
|
||||||
const textObjects = allObjects.filter((obj) => obj.type === 'textbox' || obj.type === 'text' ||
|
|
||||||
obj.type === 'i-text');
|
|
||||||
|
|
||||||
if (activeObject) {
|
|
||||||
canvas.remove(activeObject);
|
|
||||||
setActiveObject(null);
|
|
||||||
}
|
|
||||||
if (activeObject && textObjects?.length === 1) {
|
|
||||||
canvas.remove(activeObject);
|
|
||||||
setActiveObject(null);
|
|
||||||
}
|
|
||||||
if (activeObject.length > 1) {
|
|
||||||
canvas.remove(...activeObject);
|
|
||||||
setActiveObject(null);
|
|
||||||
}
|
|
||||||
}, [canvas, setActiveObject]);
|
|
||||||
|
|
||||||
const saveCanvasState = () => {
|
const saveCanvasState = () => {
|
||||||
// Get the JSON representation of all objects
|
// Get the JSON representation of all objects
|
||||||
|
|
@ -155,17 +55,6 @@ export function EditPanel() {
|
||||||
// loadCanvasState(canvasState);
|
// loadCanvasState(canvasState);
|
||||||
};
|
};
|
||||||
|
|
||||||
// duplicating current objects
|
|
||||||
const duplicating = () => {
|
|
||||||
// Clone the active object to create a true deep copy
|
|
||||||
activeObject.clone((clonedObject) => {
|
|
||||||
// Add the cloned object to the canvas
|
|
||||||
clonedObject.set("left", clonedObject?.left + 30)
|
|
||||||
canvas.add(clonedObject);
|
|
||||||
canvas.renderAll();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// for clear canvas
|
// for clear canvas
|
||||||
const clearCanvas = () => {
|
const clearCanvas = () => {
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
|
|
@ -227,57 +116,7 @@ export function EditPanel() {
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="edit" className="mt-2">
|
<TabsContent value="edit" className="mt-2">
|
||||||
<TooltipProvider>
|
<ObjectShortcut value={"edit"} />
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
<ActionButton
|
|
||||||
icon={<GroupIcon className="h-4 w-4" />}
|
|
||||||
label="Group"
|
|
||||||
onClick={groupSelectedObjects}
|
|
||||||
tooltipContent={
|
|
||||||
<div className="text-sm">
|
|
||||||
<p className="font-semibold mb-1">Group selected objects</p>
|
|
||||||
<p>To select multiple objects:</p>
|
|
||||||
<ol className="list-decimal list-inside mt-1">
|
|
||||||
<li>Hold down the Shift key</li>
|
|
||||||
<li>Click and drag with the left mouse button to select objects</li>
|
|
||||||
<li>Release the Shift key and mouse button</li>
|
|
||||||
</ol>
|
|
||||||
<p className="mt-1">Then click this button to group the selected objects.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
icon={<UngroupIcon className="h-4 w-4" />}
|
|
||||||
label="Ungroup"
|
|
||||||
onClick={ungroupSelectedObjects}
|
|
||||||
tooltipContent="Ungroup selected objects"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
icon={<CopyPlus className="h-4 w-4" />}
|
|
||||||
label="Duplicate"
|
|
||||||
onClick={duplicating}
|
|
||||||
tooltipContent="Duplicate selected objects"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
icon={<BringToFront className="h-4 w-4" />}
|
|
||||||
label="To Front"
|
|
||||||
onClick={bringToFront}
|
|
||||||
tooltipContent="Bring selected objects to front"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
icon={<MoveIcon className="h-4 w-4" />}
|
|
||||||
label="Move"
|
|
||||||
onClick={() => { }}
|
|
||||||
tooltipContent="Move selected objects"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
icon={<Trash2 className="h-4 w-4" />}
|
|
||||||
label="Remove"
|
|
||||||
onClick={removeSelected}
|
|
||||||
tooltipContent="Remove selected objects"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TooltipProvider>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="add" className="mt-2">
|
<TabsContent value="add" className="mt-2">
|
||||||
|
|
@ -343,12 +182,14 @@ export function EditPanel() {
|
||||||
onClick={saveCanvasState}
|
onClick={saveCanvasState}
|
||||||
tooltipContent="Save current canvas state"
|
tooltipContent="Save current canvas state"
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<div className="block xl:hidden lg:hidden md:hidden">
|
||||||
icon={<Settings className="h-4 w-4" />}
|
<ActionButton
|
||||||
label="Settings"
|
icon={<Settings className="h-4 w-4" />}
|
||||||
onClick={() => setOpenSetting(true)}
|
label="Settings"
|
||||||
tooltipContent="Open canvas settings"
|
onClick={() => setOpenSetting(true)}
|
||||||
/>
|
tooltipContent="Open canvas settings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<SquareX className="h-4 w-4" />}
|
icon={<SquareX className="h-4 w-4" />}
|
||||||
label="Clear"
|
label="Clear"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const RndComponent = ({ children, value }) => {
|
||||||
x: valueX,
|
x: valueX,
|
||||||
y: valueY,
|
y: valueY,
|
||||||
width: width,
|
width: width,
|
||||||
height: 'auto',
|
height: height,
|
||||||
}}
|
}}
|
||||||
minWidth={minWidth}
|
minWidth={minWidth}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ const SheetLeftPanel = () => {
|
||||||
|
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
|
|
||||||
<TabsContent value="shapes" className="mt-2 h-[530px] overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-white">
|
<TabsContent value="shapes" className="mt-2 h-[450px] overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-white">
|
||||||
<AddShapes />
|
<AddShapes />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ const SheetLeftPanel = () => {
|
||||||
<AllIconsPage />
|
<AllIconsPage />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="image" className="mt-2 h-[530px] overflow-y-scroll px-1 scrollbar-thin scrollbar-thumb-secondary scrollbar-track-white">
|
<TabsContent value="image" className="mt-2 h-[450px] overflow-y-scroll px-1 scrollbar-thin scrollbar-thumb-secondary scrollbar-track-white">
|
||||||
<UploadImage />
|
<UploadImage />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ import { X } from "lucide-react";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
|
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
|
||||||
import OpenContext from "../Context/openContext/OpenContext";
|
import OpenContext from "../Context/openContext/OpenContext";
|
||||||
|
import CanvasSetting from "../CanvasSetting";
|
||||||
|
import CollapsibleComponent from "../EachComponent/Customization/CollapsibleComponent";
|
||||||
|
import { Card } from "../ui/card";
|
||||||
|
|
||||||
const SheetRightPanel = () => {
|
const SheetRightPanel = () => {
|
||||||
const { rightPanelOpen, setRightPanelOpen } = useContext(OpenContext)
|
const { rightPanelOpen, setRightPanelOpen } = useContext(OpenContext)
|
||||||
|
|
@ -43,7 +46,7 @@ const SheetRightPanel = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet onOpenChange={handleOpenChange} open={rightPanelOpen} modal={false}>
|
<Sheet onOpenChange={handleOpenChange} open={rightPanelOpen} modal={false}>
|
||||||
<SheetContent className="w-[300px] top-[60px] overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-white">
|
<SheetContent className="w-[300px] top-[60px]">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="text-left flex items-center gap-1 flex-wrap justify-between">
|
<SheetTitle className="text-left flex items-center gap-1 flex-wrap justify-between">
|
||||||
<SheetDescription>Edit Customization</SheetDescription>
|
<SheetDescription>Edit Customization</SheetDescription>
|
||||||
|
|
@ -52,16 +55,30 @@ const SheetRightPanel = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
|
|
||||||
<SheetDescription className="text-left">
|
<SheetDescription className="text-left text-xs">
|
||||||
Customize each shapes, and text as per your choice.
|
Customize each shapes, and text as per your choice.
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
|
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="my-2 mb-14">
|
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
{
|
|
||||||
open ? <CustomizeShape /> : <p className='text-sm font-semibold'>No active object found</p>
|
<div className="h-[500px] overflow-y-scroll">
|
||||||
}
|
<div>
|
||||||
|
<Card className="p-2 mx-1">
|
||||||
|
<CollapsibleComponent text={"Canvas Setting"}>
|
||||||
|
<div className="mt-2">
|
||||||
|
<CanvasSetting />
|
||||||
|
</div>
|
||||||
|
</CollapsibleComponent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<Separator className="my-2" />
|
||||||
|
{
|
||||||
|
open ? <CustomizeShape /> : <p className='text-sm font-semibold'>No active object found</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const ObjectPanel = () => {
|
||||||
valueY: 20,
|
valueY: 20,
|
||||||
width: 250,
|
width: 250,
|
||||||
height: 0,
|
height: 0,
|
||||||
minWidth: 250,
|
minWidth: 280,
|
||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
maxHeight: 500,
|
maxHeight: 500,
|
||||||
|
|
@ -31,7 +31,7 @@ const ObjectPanel = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RndComponent value={rndValue}>
|
<RndComponent value={rndValue}>
|
||||||
<Card className="w-full shadow-lg">
|
<Card className="w-full shadow-lg px-1">
|
||||||
<CardHeader className="p-1 mr-12">
|
<CardHeader className="p-1 mr-12">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button className="rnd-escape" variant={"ghost"} size={"sm"} onClick={() => setOpenObjectPanel(false)}><X /></Button>
|
<Button className="rnd-escape" variant={"ghost"} size={"sm"} onClick={() => setOpenObjectPanel(false)}><X /></Button>
|
||||||
|
|
@ -48,7 +48,7 @@ const ObjectPanel = () => {
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<ScrollArea className="h-[500px] xl:h-fit lg:h-fit md:h-fit">
|
<div className="h-[450px] xl:h-fit lg:h-fit md:h-fit overflow-y-scroll px-1">
|
||||||
<Tabs className="w-full h-fit"
|
<Tabs className="w-full h-fit"
|
||||||
value={tabValue}
|
value={tabValue}
|
||||||
onValueChange={(value) => setTabValue(value)} // Sync tab state with context
|
onValueChange={(value) => setTabValue(value)} // Sync tab state with context
|
||||||
|
|
@ -101,18 +101,18 @@ const ObjectPanel = () => {
|
||||||
|
|
||||||
{/* All shapes */}
|
{/* All shapes */}
|
||||||
<TabsContent value="shapes" className="mt-2">
|
<TabsContent value="shapes" className="mt-2">
|
||||||
<Card className="p-1">
|
<div>
|
||||||
<CardTitle className="p-1">Shapes </CardTitle>
|
<CardTitle className="p-1">Shapes </CardTitle>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<ScrollArea className="h-[450px]">
|
<ScrollArea className="h-[450px]">
|
||||||
<AddShapes />
|
<AddShapes />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Upload images */}
|
{/* Upload images */}
|
||||||
<TabsContent value="images" className="mt-2">
|
<TabsContent value="images" className="mt-2">
|
||||||
<Card className="p-1">
|
<div>
|
||||||
<CardTitle className="p-1">Upload</CardTitle>
|
<CardTitle className="p-1">Upload</CardTitle>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<ScrollArea className="h-[450px]">
|
<ScrollArea className="h-[450px]">
|
||||||
|
|
@ -120,23 +120,23 @@ const ObjectPanel = () => {
|
||||||
<UploadImage />
|
<UploadImage />
|
||||||
</Card>
|
</Card>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Customization */}
|
{/* Customization */}
|
||||||
<TabsContent value="customize" className="mt-2">
|
<TabsContent value="customize" className="mt-2">
|
||||||
<Card className="p-1">
|
<div>
|
||||||
<CardTitle className="p-1">Object customization </CardTitle>
|
<CardTitle className="p-1">Object customization </CardTitle>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<ScrollArea className="h-[450px]">
|
<ScrollArea className="h-[450px]">
|
||||||
<CustomizeShape />
|
<CustomizeShape />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
</ScrollArea>
|
</div>
|
||||||
|
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
|
|
||||||
|
|
|
||||||
211
src/components/ObjectShortcut.jsx
Normal file
211
src/components/ObjectShortcut.jsx
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
import { useCallback, useContext } from 'react'
|
||||||
|
import CanvasContext from './Context/canvasContext/CanvasContext';
|
||||||
|
import ActiveObjectContext from './Context/activeObject/ObjectContext';
|
||||||
|
import { fabric } from 'fabric';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { BringToFront, CopyPlus, GroupIcon, SquareX, Trash2, UngroupIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
export const ObjectShortcut = ({ value }) => {
|
||||||
|
const { canvas } = useContext(CanvasContext);
|
||||||
|
const { setActiveObject, activeObject } = useContext(ActiveObjectContext);
|
||||||
|
|
||||||
|
const groupSelectedObjects = () => {
|
||||||
|
const activeObjects = canvas.getActiveObjects(); // Get selected objects
|
||||||
|
if (activeObjects.length > 1) {
|
||||||
|
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
|
||||||
|
const group = new fabric.Group(activeObjects, {
|
||||||
|
left: canvas?.width / 2,
|
||||||
|
top: canvas?.height / 2,
|
||||||
|
originX: "center",
|
||||||
|
originY: "center",
|
||||||
|
selectable: true, // Allow group selection
|
||||||
|
subTargetCheck: true, // Allow individual object selection
|
||||||
|
hasControls: true, // Enable resizing/movement of the group
|
||||||
|
})
|
||||||
|
canvas.remove(...activeObjects);
|
||||||
|
canvas.add(group);
|
||||||
|
canvas.setActiveObject(group);
|
||||||
|
setActiveObject(group);
|
||||||
|
canvas.renderAll();
|
||||||
|
} else {
|
||||||
|
console.log("Select at least two objects")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ungroupSelectedObjects = () => {
|
||||||
|
const activeObject = canvas.getActiveObject();
|
||||||
|
if (activeObject && activeObject.type === "group") {
|
||||||
|
// Get the group
|
||||||
|
const group = activeObject;
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
// Remove the group from the canvas
|
||||||
|
canvas.remove(group);
|
||||||
|
setActiveObject(null);
|
||||||
|
|
||||||
|
// Iterate through each object in the group
|
||||||
|
group._objects.forEach((object) => {
|
||||||
|
// Calculate the absolute position based on the group's transformation
|
||||||
|
const objLeft = object.left * group.scaleX + group.left;
|
||||||
|
const objTop = object.top * group.scaleY + group.top;
|
||||||
|
|
||||||
|
// Reset transformations and positions
|
||||||
|
object.set({
|
||||||
|
left: objLeft,
|
||||||
|
top: objTop,
|
||||||
|
scaleX: object.scaleX * group.scaleX, // Adjust scale based on group
|
||||||
|
scaleY: object.scaleY * group.scaleY, // Adjust scale based on group
|
||||||
|
angle: object.angle + group.angle, // Adjust rotation
|
||||||
|
hasControls: true, // Allow resizing
|
||||||
|
selectable: true, // Make selectable
|
||||||
|
group: null, // Remove group reference
|
||||||
|
originX: "center",
|
||||||
|
originY: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update coordinates
|
||||||
|
object.setCoords();
|
||||||
|
|
||||||
|
// Add the object back to the canvas
|
||||||
|
canvas.add(object);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear the selection
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
|
||||||
|
// Render the canvas to reflect changes
|
||||||
|
canvas.renderAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bring Selected Object to Front
|
||||||
|
const bringToFront = () => {
|
||||||
|
if (activeObject) {
|
||||||
|
activeObject.bringToFront();
|
||||||
|
canvas.renderAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove Selected Element
|
||||||
|
const removeSelected = useCallback(() => {
|
||||||
|
const activeObject = canvas?.getActiveObject();
|
||||||
|
|
||||||
|
const allObjects = canvas?.getObjects();
|
||||||
|
|
||||||
|
const textObjects = allObjects.filter((obj) => obj.type === 'textbox' || obj.type === 'text' ||
|
||||||
|
obj.type === 'i-text');
|
||||||
|
|
||||||
|
if (activeObject) {
|
||||||
|
canvas.remove(activeObject);
|
||||||
|
setActiveObject(null);
|
||||||
|
}
|
||||||
|
if (activeObject && textObjects?.length === 1) {
|
||||||
|
canvas.remove(activeObject);
|
||||||
|
setActiveObject(null);
|
||||||
|
}
|
||||||
|
if (activeObject.length > 1) {
|
||||||
|
canvas.remove(...activeObject);
|
||||||
|
setActiveObject(null);
|
||||||
|
}
|
||||||
|
}, [canvas, setActiveObject]);
|
||||||
|
|
||||||
|
// duplicating current objects
|
||||||
|
const duplicating = () => {
|
||||||
|
// Clone the active object to create a true deep copy
|
||||||
|
activeObject.clone((clonedObject) => {
|
||||||
|
// Add the cloned object to the canvas
|
||||||
|
clonedObject.set("left", clonedObject?.left + 30)
|
||||||
|
canvas.add(clonedObject);
|
||||||
|
canvas.renderAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// for clear canvas
|
||||||
|
const clearCanvas = () => {
|
||||||
|
canvas.clear();
|
||||||
|
canvas.renderAll();
|
||||||
|
setActiveObject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TooltipProvider>
|
||||||
|
<div className={`grid grid-cols-3 gap-2 ${value === "default" ? "xl:grid-cols-6 lg:grid-cols-6 md:grid-cols-6" : "xl:grid-cols-3 lg:grid-cols-3 md:grid-cols-3"}`}>
|
||||||
|
<ActionButton
|
||||||
|
icon={<GroupIcon className="h-4 w-4" />}
|
||||||
|
label="Group"
|
||||||
|
onClick={groupSelectedObjects}
|
||||||
|
tooltipContent={
|
||||||
|
<div className="text-sm">
|
||||||
|
<p className="font-semibold mb-1">Group selected objects</p>
|
||||||
|
<p>To select multiple objects:</p>
|
||||||
|
<ol className="list-decimal list-inside mt-1">
|
||||||
|
<li>Hold down the Shift key</li>
|
||||||
|
<li>Click and drag with the left mouse button to select objects</li>
|
||||||
|
<li>Release the Shift key and mouse button</li>
|
||||||
|
</ol>
|
||||||
|
<p className="mt-1">Then click this button to group the selected objects.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon={<UngroupIcon className="h-4 w-4" />}
|
||||||
|
label="Ungroup"
|
||||||
|
onClick={ungroupSelectedObjects}
|
||||||
|
tooltipContent="Ungroup selected objects"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon={<CopyPlus className="h-4 w-4" />}
|
||||||
|
label="Duplicate"
|
||||||
|
onClick={duplicating}
|
||||||
|
tooltipContent="Duplicate selected objects"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon={<BringToFront className="h-4 w-4" />}
|
||||||
|
label="To Front"
|
||||||
|
onClick={bringToFront}
|
||||||
|
tooltipContent="Bring selected objects to front"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon={<Trash2 className="h-4 w-4" />}
|
||||||
|
label="Remove"
|
||||||
|
onClick={removeSelected}
|
||||||
|
tooltipContent="Remove selected objects"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon={<SquareX className="h-4 w-4" />}
|
||||||
|
label="Clear"
|
||||||
|
onClick={clearCanvas}
|
||||||
|
tooltipContent="Clear entire canvas"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ActionButton({ icon, label, onClick, tooltipContent }) {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="md" className="w-full" onClick={onClick}>
|
||||||
|
<div className="flex items-center gap-1 p-1">
|
||||||
|
{icon}
|
||||||
|
<span className="text-[10px] font-bold">{label}</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" align="center" className="max-w-xs">
|
||||||
|
<p>{tooltipContent}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue