Merge branch 'canvas_v1' into 'main'

canvas layout change, customization alignment changed

See merge request planpostai/planpostai_canvas!3
This commit is contained in:
Md. Latiful Kabir 2025-01-12 10:53:43 +00:00
commit 52a3b80478
13 changed files with 482 additions and 351 deletions

View file

@ -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,7 +147,8 @@ 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">
<div className="block xl:hidden lg:hidden md:hidden">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@ -168,6 +161,8 @@ export function AspectCanvas() {
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </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">

View file

@ -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>
</RndComponent>
) )
}
return screenWidth <= 768 ? (
<RndComponent value={rndValue}>
{content()}
</RndComponent>
) : (
<div>
{content()}
</div>
);
} }
export default CanvasSetting export default CanvasSetting

View file

@ -1,77 +1,87 @@
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 = () => { useEffect(() => {
const width = canvas?.width || 0; setPreviewBackgroundType(backgroundType)
const height = canvas?.height || 0; setPreviewSolidColor(solidColor)
setPreviewGradientColors(gradientColors)
setPreviewGradientDirection(gradientDirection)
}, [backgroundType, solidColor, gradientColors, gradientDirection])
useEffect(() => {
if (previewBackgroundType === "gradient" && canvas) {
const width = canvas.width || 0
const height = canvas.height || 0
// Define coordinates for linear gradient
const linearCoords = { const linearCoords = {
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height }, "top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 }, "bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 }, "left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
"right-to-left": { x1: width, y1: 0, x2: 0, 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(() => { const coords = linearCoords[previewGradientDirection]
if (gradientDirection && backgroundType === "gradient") { if (coords) {
applyGradient(); setDirectionCoords(coords)
} }
}, [gradientDirection, backgroundType]); }
}, [previewBackgroundType, previewGradientDirection, canvas, setDirectionCoords])
useEffect(() => { const applyChanges = () => {
const object = canvas?.getActiveObject(); setBackgroundType(previewBackgroundType)
if (!object) { setSolidColor(previewSolidColor)
if (canvas && solidColor && backgroundType === "color") { setGradientColors(previewGradientColors)
canvas.backgroundColor = solidColor; setGradientDirection(previewGradientDirection)
canvas.renderAll(); applyToCanvas()
} }
if (canvas && directionCoords && gradientColors && backgroundType === "gradient") {
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",
@ -84,21 +94,41 @@ const ColorComponent = () => {
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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
/> />
<div className="block xl:hidden lg:hidden md:hidden">
<ActionButton <ActionButton
icon={<Settings className="h-4 w-4" />} icon={<Settings className="h-4 w-4" />}
label="Settings" label="Settings"
onClick={() => setOpenSetting(true)} onClick={() => setOpenSetting(true)}
tooltipContent="Open canvas settings" 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"

View file

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

View file

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

View file

@ -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,17 +55,31 @@ 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" />
<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" /> <Separator className="my-2" />
{ {
open ? <CustomizeShape /> : <p className='text-sm font-semibold'>No active object found</p> open ? <CustomizeShape /> : <p className='text-sm font-semibold'>No active object found</p>
} }
</div> </div>
</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
) )

View file

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

View 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>
)
}