new design added
This commit is contained in:
parent
2c0ab8b736
commit
e2b8d7368f
10 changed files with 225 additions and 422 deletions
16
src/Home.jsx
16
src/Home.jsx
|
|
@ -61,13 +61,15 @@ export const Home = () => {
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
<>
|
<>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div>
|
|
||||||
{
|
|
||||||
activeObject && <TopBar />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fixed z-[999] right-0">
|
{
|
||||||
|
activeObject &&
|
||||||
|
<div className="absolute left-[90px] right-[90px] z-[9999] rounded-md p-1 h-fit bg-white border-t border-gray-200 shadow-md my-1 w-[80%] mx-auto">
|
||||||
|
<TopBar />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className="absolute z-[9999] right-0 bottom-0 flex justify-center items-center h-20 bg-white border-t border-gray-200 shadow-md w-fit">
|
||||||
<ActionButtons />
|
<ActionButtons />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -79,7 +81,7 @@ export const Home = () => {
|
||||||
<EditorPanel />
|
<EditorPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
<div className="flex-1 flex flex-col h-full overflow-hidden my-2">
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<Canvas />
|
<Canvas />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const aspectRatios = [
|
||||||
{ value: "4:3", label: "Standard (4:3)" },
|
{ value: "4:3", label: "Standard (4:3)" },
|
||||||
{ value: "3:2", label: "Classic (3:2)" },
|
{ value: "3:2", label: "Classic (3:2)" },
|
||||||
{ value: "16:9", label: "Widescreen (16:9)" },
|
{ value: "16:9", label: "Widescreen (16:9)" },
|
||||||
{ value: "9:16", label: "Portrait (9:16)" },
|
|
||||||
{ value: "21:9", label: "Ultrawide (21:9)" },
|
{ value: "21:9", label: "Ultrawide (21:9)" },
|
||||||
{ value: "32:9", label: "Super Ultrawide (32:9)" },
|
{ value: "32:9", label: "Super Ultrawide (32:9)" },
|
||||||
{ value: "1.85:1", label: "Cinema Standard (1.85:1)" },
|
{ value: "1.85:1", label: "Cinema Standard (1.85:1)" },
|
||||||
|
|
@ -24,7 +23,9 @@ const aspectRatios = [
|
||||||
{ value: "5:4", label: "Large Format (5:4)" },
|
{ value: "5:4", label: "Large Format (5:4)" },
|
||||||
{ value: "7:5", label: "Artistic Format (7:5)" },
|
{ value: "7:5", label: "Artistic Format (7:5)" },
|
||||||
{ value: "11:8.5", label: "Letter Size (11:8.5)" },
|
{ value: "11:8.5", label: "Letter Size (11:8.5)" },
|
||||||
{ value: "3:4", label: "Portrait (3:4)" },
|
{ value: "3:4", label: "Portrait (4:4)" },
|
||||||
|
{ value: "9:16", label: "Vertical (9:16)" },
|
||||||
|
{ value: "1.33:1", label: "Instagram Stories (1.33:1)" },
|
||||||
{ value: "1.91:1", label: "Facebook Ads (1.91:1)" },
|
{ value: "1.91:1", label: "Facebook Ads (1.91:1)" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -236,22 +236,9 @@ export default function Canvas() {
|
||||||
}, [canvas, setActiveObject]);
|
}, [canvas, setActiveObject]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{/* Zoom Controls */}
|
|
||||||
<div className="fixed bottom-4 right-4 flex items-center gap-4 bg-white p-3 rounded-lg shadow-lg z-50">
|
|
||||||
<span className="text-sm font-medium min-w-[45px]">{zoomLevel}%</span>
|
|
||||||
<Slider
|
|
||||||
value={[zoomLevel]}
|
|
||||||
onValueChange={(value) => handleZoom(value[0])}
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
className="w-32"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Canvas Container */}
|
{/* Canvas Container */}
|
||||||
<div className="w-full max-w-4xl mx-auto mt-20">
|
<div className="w-full max-w-2xl mx-auto mt-20">
|
||||||
<Card
|
<Card
|
||||||
className={`w-full rounded-none flex-1 flex flex-col
|
className={`w-full rounded-none flex-1 flex flex-col
|
||||||
mx-auto border-0 shadow-none bg-transparent
|
mx-auto border-0 shadow-none bg-transparent
|
||||||
|
|
@ -282,6 +269,19 @@ export default function Canvas() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
|
{/* Zoom Controls */}
|
||||||
|
<div className="flex my-2 mb-4 bg-white p-3 rounded-lg shadow-lg z-50 w-fit">
|
||||||
|
<span className="text-sm font-medium min-w-[45px]">{zoomLevel}%</span>
|
||||||
|
<Slider
|
||||||
|
value={[zoomLevel]}
|
||||||
|
onValueChange={(value) => handleZoom(value[0])}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
className="w-32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { AlignLeft, AlignRight } from 'lucide-react';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import OpenContext from '../Context/openContext/OpenContext';
|
|
||||||
|
|
||||||
const Header = () => {
|
|
||||||
const { setRightPanelOpen, setLeftPanelOpen } = useContext(OpenContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='bg-[#f2e6f8] fixed top-0 w-[60px] z-[900] min-w-full flex items-center p-4 shadow-md'>
|
|
||||||
|
|
||||||
<p className='mr-auto flex-wrap gap-1 cursor-pointer text-xs items-center font-semibold xl:flex lg:flex md:flex hidden' onClick={() => setLeftPanelOpen(true)}><AlignLeft /><span className='hidden xl:block lg:block md:block sm:block'>Add Element</span></p>
|
|
||||||
|
|
||||||
<h1 className='font-semibold'>PlanPostAi Canvas</h1>
|
|
||||||
|
|
||||||
<p className='ml-auto flex-wrap gap-1 cursor-pointer text-xs items-center font-semibold xl:flex lg:flex md:flex hidden' onClick={() => setRightPanelOpen(true)}> <span className='hidden xl:block lg:block md:block sm:block'>Edit Panel</span><AlignRight /></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header
|
|
||||||
|
|
@ -281,7 +281,7 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
<p>Change object position</p>
|
<p>Change object position</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<PopoverContent className="w-40 p-2">
|
<PopoverContent className="w-40 p-2 z-[9999]">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -340,7 +340,7 @@ function ActionButton({ icon, label, onClick, tooltipContent }) {
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom" align="center" className="max-w-xs">
|
<TooltipContent side="bottom" align="center" className="max-w-xs z-[9999]">
|
||||||
<p>{tooltipContent}</p>
|
<p>{tooltipContent}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { deleteProject, getProjects } from '@/api/projectApi';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import ActiveObjectContext from '../Context/activeObject/ObjectContext';
|
import ActiveObjectContext from '../Context/activeObject/ObjectContext';
|
||||||
import { FixedSizeList as List } from 'react-window'; // Import FixedSizeList from react-window
|
import { FixedSizeList as List } from 'react-window';
|
||||||
|
|
||||||
export const ProjectPanel = () => {
|
export const ProjectPanel = () => {
|
||||||
const { setSelectedPanel, canvas } = useContext(CanvasContext);
|
const { setSelectedPanel, canvas } = useContext(CanvasContext);
|
||||||
|
|
@ -63,12 +63,11 @@ export const ProjectPanel = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Row renderer for react-window
|
|
||||||
const Row = ({ index, style }) => {
|
const Row = ({ index, style }) => {
|
||||||
const project = projects?.data?.[index]; // Get project by index
|
const project = filteredProjects[index]; // Use filtered projects
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={project?.id} className='flex flex-col gap-1 bg-red-50 p-1 rounded-md' style={style}>
|
<div key={project?.id} className='flex flex-col gap-1 bg-red-50 p-1 rounded-md border border-red-100' style={style}>
|
||||||
<div className='rounded-md flex p-1 flex-col gap-1 hover:bg-red-100 cursor-pointer transition-all' onClick={() => navigate(`/${project.id}`)}>
|
<div className='rounded-md flex p-1 flex-col gap-1 hover:bg-red-100 cursor-pointer transition-all' onClick={() => navigate(`/${project.id}`)}>
|
||||||
<p className='font-bold text-sm truncate'>{project?.name}</p>
|
<p className='font-bold text-sm truncate'>{project?.name}</p>
|
||||||
<p className='text-xs truncate'>{project?.description} </p>
|
<p className='text-xs truncate'>{project?.description} </p>
|
||||||
|
|
@ -77,11 +76,16 @@ export const ProjectPanel = () => {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={deletePending}
|
disabled={deletePending}
|
||||||
className="w-fit p-1 ml-auto" size="small" onClick={() => { projectDelete(project?.id) }}><Trash className="h-4 w-4" /></Button>
|
className="w-fit p-1 ml-auto" size="small" onClick={() => { projectDelete(project?.id) }}>
|
||||||
|
<Trash className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filter projects where preview_url is not empty or null
|
||||||
|
const filteredProjects = projects?.data?.filter(project => project?.preview_url) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center p-4 border-b">
|
<div className="flex justify-between items-center p-4 border-b">
|
||||||
|
|
@ -102,9 +106,9 @@ export const ProjectPanel = () => {
|
||||||
{
|
{
|
||||||
!projectLoading && projectSuccess && projects?.status === 200 &&
|
!projectLoading && projectSuccess && projects?.status === 200 &&
|
||||||
<List
|
<List
|
||||||
height={500} // Set the height of the viewport
|
height={400} // Set the height of the viewport
|
||||||
itemCount={projects?.data?.length} // Total number of items
|
itemCount={filteredProjects.length} // Use length of filtered projects
|
||||||
itemSize={300} // Height of each row
|
itemSize={280} // Height of each row
|
||||||
width="100%" // Full width of the container
|
width="100%" // Full width of the container
|
||||||
>
|
>
|
||||||
{Row}
|
{Row}
|
||||||
|
|
|
||||||
|
|
@ -21,220 +21,124 @@ export function TopBar() {
|
||||||
const activeObjectType = activeObject?.type;
|
const activeObjectType = activeObject?.type;
|
||||||
const hasClipPath = !!activeObject?.clipPath;
|
const hasClipPath = !!activeObject?.clipPath;
|
||||||
const customClipPath = activeObject?.isClipPath;
|
const customClipPath = activeObject?.isClipPath;
|
||||||
const scrollContainerRef = useRef(null);
|
// w - [calc(100 % -80px)] h - full
|
||||||
const [showScrollButtons, setShowScrollButtons] = useState(false);
|
|
||||||
|
|
||||||
const scroll = (direction) => {
|
|
||||||
const container = scrollContainerRef.current;
|
|
||||||
if (container) {
|
|
||||||
const scrollAmount = 200; // Adjust this value to change scroll distance
|
|
||||||
container.scrollBy({
|
|
||||||
left: direction * scrollAmount,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="w-full h-full overflow-x-scroll p-1">
|
||||||
className={`!absolute top-2 -translate-x-1/2 z-40 ${selectedPanel !== ""
|
<div className="flex item-center justify-center w-fit mx-auto">
|
||||||
? "md:w-[465px] left-[41%] lg:w-[700px] lg:left-[43%] mdxl:left-[45%] xl:w-[900px] xl:left-[46%] 2xl:left-[50%]"
|
<div className="bg-white mx-auto flex justify-center items-center gap-2">
|
||||||
: "w-[500px] lg:w-[600px] xl:w-[900px] lg:left-[41%] xl:left-[50%]"
|
<div className="flex-1">
|
||||||
}`}
|
<TextCustomization />
|
||||||
onMouseEnter={() => setShowScrollButtons(true)}
|
</div>
|
||||||
onMouseLeave={() => setShowScrollButtons(false)}
|
|
||||||
>
|
<div className="flex items-center gap-4 px-2">
|
||||||
<div className="relative h-full hidden xl:block">
|
|
||||||
{showScrollButtons && (
|
|
||||||
<>
|
|
||||||
<Button
|
<Button
|
||||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-accent"
|
variant="outline"
|
||||||
onClick={() => scroll(-1)}
|
className="flex items-center gap-2 border-dashed border-2 rounded-md hover:bg-gray-50"
|
||||||
variant="ghost"
|
onClick={() => setSelectedPanel("image-insert")}
|
||||||
size="icon"
|
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ImagePlus className="w-5 h-5" />
|
||||||
|
<span>Add image</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-accent"
|
{/* Canvas settings */}
|
||||||
onClick={() => scroll(1)}
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
>
|
|
||||||
<ChevronRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
ref={scrollContainerRef}
|
|
||||||
className="w-full h-full overflow-x-auto scrollbar-hide"
|
|
||||||
>
|
|
||||||
<div className="bg-white shadow-sm mx-auto px-4 xl:px-10 py-2 flex justify-start items-center gap-2 min-w-max h-full">
|
|
||||||
<div>
|
<div>
|
||||||
<TextCustomization />
|
<a data-tooltip-id="canvas">
|
||||||
</div>
|
<Button
|
||||||
<div className="flex items-center gap-4 px-2">
|
variant="ghost"
|
||||||
<Button
|
size="icon"
|
||||||
variant="outline"
|
className="w-10 h-10"
|
||||||
className="flex items-center gap-2 border-dashed border-2 rounded-md hover:bg-gray-50"
|
onClick={() => setSelectedPanel("canvas")}
|
||||||
onClick={() => setSelectedPanel("image-insert")}
|
>
|
||||||
>
|
<svg
|
||||||
<ImagePlus className="w-5 h-5" />
|
width="100"
|
||||||
<span>Add image</span>
|
height="100"
|
||||||
</Button>
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
{/* canvas settings */}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<div>
|
|
||||||
<a data-tooltip-id="canvas">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="w-10 h-10"
|
|
||||||
onClick={() => setSelectedPanel("canvas")}
|
|
||||||
>
|
>
|
||||||
<svg
|
<rect x="3" y="5" width="18" height="12" stroke="black" strokeWidth="2" fill="none" />
|
||||||
width="100"
|
<path d="M14 14 L19 19" stroke="black" strokeWidth="2" />
|
||||||
height="100"
|
<path d="M15 15 Q16 12, 19 11" stroke="black" strokeWidth="2" fill="none" />
|
||||||
viewBox="0 0 24 24"
|
<line x1="3" y1="17" x2="7" y2="21" stroke="black" strokeWidth="2" />
|
||||||
fill="none"
|
<line x1="21" y1="17" x2="17" y2="21" stroke="black" strokeWidth="2" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</svg>
|
||||||
>
|
</Button>
|
||||||
<rect
|
</a>
|
||||||
x="3"
|
<Tooltip id="canvas" content="Canvas Settings" place="bottom" />
|
||||||
y="5"
|
</div>
|
||||||
width="18"
|
|
||||||
height="12"
|
|
||||||
stroke="black"
|
|
||||||
strokeWidth="2"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
<path d="M14 14 L19 19" stroke="black" strokeWidth="2" />
|
|
||||||
<path
|
|
||||||
d="M15 15 Q16 12, 19 11"
|
|
||||||
stroke="black"
|
|
||||||
strokeWidth="2"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="3"
|
|
||||||
y1="17"
|
|
||||||
x2="7"
|
|
||||||
y2="21"
|
|
||||||
stroke="black"
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="21"
|
|
||||||
y1="17"
|
|
||||||
x2="17"
|
|
||||||
y2="21"
|
|
||||||
stroke="black"
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
<Tooltip id="canvas" content="Canvas Settings" place="bottom" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{activeObjectType !== "image" &&
|
{activeObjectType !== "image" &&
|
||||||
activeObject?.type !== "i-text" &&
|
activeObject?.type !== "i-text" &&
|
||||||
!hasClipPath &&
|
!hasClipPath &&
|
||||||
!customClipPath && (
|
!customClipPath && (
|
||||||
<a data-tooltip-id="color-gr">
|
<a data-tooltip-id="color-gr">
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="rounded-full"
|
|
||||||
onClick={() => setSelectedPanel("color")}
|
|
||||||
style={{ backgroundColor: textColor?.fill || "black" }}
|
|
||||||
></Button>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<Tooltip id="color-gr" content="Color" place="bottom" />
|
|
||||||
|
|
||||||
{!customClipPath && (
|
|
||||||
<a data-tooltip-id="stroke">
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="w-10 h-10"
|
className="rounded-full"
|
||||||
onClick={() => setSelectedPanel("stroke")}
|
onClick={() => setSelectedPanel("color")}
|
||||||
>
|
style={{ backgroundColor: textColor?.fill || "black" }}
|
||||||
<RxBorderWidth className="text-lg" />
|
></Button>
|
||||||
</Button>
|
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
<Tooltip id="stroke" content="Stroke" place="bottom" />
|
<Tooltip id="color-gr" content="Color" place="bottom" />
|
||||||
{(activeObject || activeObject.type === "group") && (
|
|
||||||
<a data-tooltip-id="group-obj">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="w-10 h-10"
|
|
||||||
onClick={() => setSelectedPanel("group-obj")}
|
|
||||||
>
|
|
||||||
<FaRegObjectGroup />
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<Tooltip id="group-obj" content="Group Object" place="bottom" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-6 w-px bg-gray-200" />
|
{!customClipPath && (
|
||||||
|
<a data-tooltip-id="stroke">
|
||||||
<div className="flex items-center gap-2">
|
<Button variant="ghost" size="icon" className="w-10 h-10" onClick={() => setSelectedPanel("stroke")}>
|
||||||
<a data-tooltip-id="flip">
|
<RxBorderWidth className="text-lg" />
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => setSelectedPanel("flip")}
|
|
||||||
>
|
|
||||||
<LuFlipVertical />
|
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
|
<Tooltip id="stroke" content="Stroke" place="bottom" />
|
||||||
|
|
||||||
<Tooltip id="flip" content="Object flip" place="bottom" />
|
{(activeObject || activeObject.type === "group") && (
|
||||||
{activeObject?.type !== "group" && (
|
<a data-tooltip-id="group-obj">
|
||||||
<a data-tooltip-id="position">
|
<Button variant="ghost" size="icon" className="w-10 h-10" onClick={() => setSelectedPanel("group-obj")}>
|
||||||
<Button
|
<FaRegObjectGroup />
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => setSelectedPanel("position")}
|
|
||||||
>
|
|
||||||
<SlTarget />
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
id="position"
|
|
||||||
content="Object position"
|
|
||||||
place="bottom"
|
|
||||||
/>
|
|
||||||
<a data-tooltip-id="shadow">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => setSelectedPanel("shadow")}
|
|
||||||
>
|
|
||||||
<RiShadowLine />
|
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
<Tooltip id="shadow" content="Shadow color" place="bottom" />
|
)}
|
||||||
</div>
|
<Tooltip id="group-obj" content="Group Object" place="bottom" />
|
||||||
</div>
|
</div>
|
||||||
<OpacityCustomization />
|
|
||||||
<div className="h-4 w-px bg-border mx-2" />
|
|
||||||
|
|
||||||
<div>
|
<div className="h-6 w-px bg-gray-200" />
|
||||||
<ObjectShortcut value="default" />
|
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="ml-4">
|
<a data-tooltip-id="flip">
|
||||||
<LockObject />
|
<Button variant="ghost" size="icon" onClick={() => setSelectedPanel("flip")}>
|
||||||
|
<LuFlipVertical />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<Tooltip id="flip" content="Object flip" place="bottom" />
|
||||||
|
{activeObject?.type !== "group" && (
|
||||||
|
<a data-tooltip-id="position">
|
||||||
|
<Button variant="ghost" size="icon" onClick={() => setSelectedPanel("position")}>
|
||||||
|
<SlTarget />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip id="position" content="Object position" place="bottom" />
|
||||||
|
<a data-tooltip-id="shadow">
|
||||||
|
<Button variant="ghost" size="icon" onClick={() => setSelectedPanel("shadow")}>
|
||||||
|
<RiShadowLine />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Tooltip id="shadow" content="Shadow color" place="bottom" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<OpacityCustomization />
|
||||||
|
<div className="h-4 w-px bg-border mx-2" />
|
||||||
|
<div>
|
||||||
|
<ObjectShortcut value="default" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
<LockObject />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
const loadCanvasState = (state) => {
|
|
||||||
if (!canvas2) {
|
|
||||||
console.error('Canvas2 is not initialized!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const canvasData = typeof state === 'string' ? JSON.parse(state) : state;
|
|
||||||
|
|
||||||
// Clear the existing canvas
|
|
||||||
canvas2.clear();
|
|
||||||
|
|
||||||
// Set the background color if it exists
|
|
||||||
if (canvasData.backgroundColor) {
|
|
||||||
canvas2.set({ backgroundColor: canvasData.backgroundColor });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to load background image
|
|
||||||
const loadBackgroundImage = () => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (!canvasData.background) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imgElement = new Image();
|
|
||||||
imgElement.crossOrigin = 'anonymous'; // Handle CORS if needed
|
|
||||||
|
|
||||||
imgElement.onload = () => {
|
|
||||||
const imgInstance = new fabric.Image(imgElement, {
|
|
||||||
originX: canvasData.background.originX || 'left',
|
|
||||||
originY: canvasData.background.originY || 'top',
|
|
||||||
scaleX: canvasData.background.scaleX || canvas2.width / imgElement.width,
|
|
||||||
scaleY: canvasData.background.scaleY || canvas2.height / imgElement.height,
|
|
||||||
opacity: canvasData.background.opacity || 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set background image using the set method
|
|
||||||
canvas2.set({ backgroundImage: imgInstance });
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
imgElement.onerror = () => {
|
|
||||||
console.error("Failed to load the background image");
|
|
||||||
resolve(); // Resolve anyway to continue loading objects
|
|
||||||
};
|
|
||||||
|
|
||||||
imgElement.src = canvasData.background.src;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to load objects
|
|
||||||
const loadObjects = () => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
canvas2.loadFromJSON({ objects: canvasData.objects }, () => {
|
|
||||||
resolve();
|
|
||||||
}, (obj, element) => {
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load background image and objects if they exist
|
|
||||||
const loadTasks = [];
|
|
||||||
if (canvasData.background || canvasData.objects?.length) {
|
|
||||||
if (canvasData.background) {
|
|
||||||
loadTasks.push(loadBackgroundImage());
|
|
||||||
}
|
|
||||||
if (canvasData.objects?.length) {
|
|
||||||
loadTasks.push(loadObjects());
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(loadTasks)
|
|
||||||
.then(() => {
|
|
||||||
canvas2.requestRenderAll(); // Ensure re-rendering
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error during canvas loading:', error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If only background color exists
|
|
||||||
if (canvasData.backgroundColor) {
|
|
||||||
canvas2.requestRenderAll(); // Apply background color and render
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading canvas state:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// for download image/canvas
|
|
||||||
const exportScaledCanvasImage = (scale = 2) => {
|
|
||||||
if (canvas) {
|
|
||||||
const imageBase64 = canvas.toDataURL({
|
|
||||||
format: 'png', // or 'jpeg'
|
|
||||||
quality: 1, // (0 to 1) for JPEG compression, ignored for PNG
|
|
||||||
multiplier: scale, // Scale the canvas size
|
|
||||||
});
|
|
||||||
|
|
||||||
// Download the scaled image
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = imageBase64;
|
|
||||||
link.download = `canvas-image-${scale}x.png`;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -153,3 +153,23 @@
|
||||||
@apply bg-[#f5f0ff] text-foreground;
|
@apply bg-[#f5f0ff] text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
/* background-color: var(--primary-600); */
|
||||||
|
@apply bg-red-50
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: var(--primary-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 5px;
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
@ -3,71 +3,71 @@ import scrollbarHide from "tailwind-scrollbar-hide";
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
textColor: {
|
textColor: {
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary-text))',
|
DEFAULT: 'hsl(var(--primary-text))',
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
mdxl: '1100px'
|
mdxl: '1100px'
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
selection: {
|
selection: {
|
||||||
DEFAULT: 'hsl(var(--selection))',
|
DEFAULT: 'hsl(var(--selection))',
|
||||||
foreground: 'hsl(var(--selection-foreground))'
|
foreground: 'hsl(var(--selection-foreground))'
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
'1': 'hsl(var(--chart-1))',
|
'1': 'hsl(var(--chart-1))',
|
||||||
'2': 'hsl(var(--chart-2))',
|
'2': 'hsl(var(--chart-2))',
|
||||||
'3': 'hsl(var(--chart-3))',
|
'3': 'hsl(var(--chart-3))',
|
||||||
'4': 'hsl(var(--chart-4))',
|
'4': 'hsl(var(--chart-4))',
|
||||||
'5': 'hsl(var(--chart-5))'
|
'5': 'hsl(var(--chart-5))'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [scrollbar, scrollbarHide, require("tailwindcss-animate")],
|
plugins: [scrollbar, scrollbarHide, require("tailwindcss-animate")],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue