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 &&
|
||||
<>
|
||||
<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 />
|
||||
</div>
|
||||
|
||||
|
|
@ -79,7 +81,7 @@ export const Home = () => {
|
|||
<EditorPanel />
|
||||
</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">
|
||||
<Canvas />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ const aspectRatios = [
|
|||
{ value: "4:3", label: "Standard (4:3)" },
|
||||
{ value: "3:2", label: "Classic (3:2)" },
|
||||
{ value: "16:9", label: "Widescreen (16:9)" },
|
||||
{ value: "9:16", label: "Portrait (9:16)" },
|
||||
{ value: "21:9", label: "Ultrawide (21:9)" },
|
||||
{ value: "32:9", label: "Super Ultrawide (32:9)" },
|
||||
{ value: "1.85:1", label: "Cinema Standard (1.85:1)" },
|
||||
|
|
@ -24,7 +23,9 @@ const aspectRatios = [
|
|||
{ value: "5:4", label: "Large Format (5:4)" },
|
||||
{ value: "7:5", label: "Artistic Format (7:5)" },
|
||||
{ value: "11:8.5", label: "Letter Size (11:8.5)" },
|
||||
{ value: "3:4", label: "Portrait (3:4)" },
|
||||
{ value: "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)" },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -236,22 +236,9 @@ export default function Canvas() {
|
|||
}, [canvas, setActiveObject]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 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>
|
||||
|
||||
<div>
|
||||
{/* 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
|
||||
className={`w-full rounded-none flex-1 flex flex-col
|
||||
mx-auto border-0 shadow-none bg-transparent
|
||||
|
|
@ -282,6 +269,19 @@ export default function Canvas() {
|
|||
</CardContent>
|
||||
</Card>
|
||||
</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>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="w-40 p-2">
|
||||
<PopoverContent className="w-40 p-2 z-[9999]">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -340,7 +340,7 @@ function ActionButton({ icon, label, onClick, tooltipContent }) {
|
|||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="center" className="max-w-xs">
|
||||
<TooltipContent side="bottom" align="center" className="max-w-xs z-[9999]">
|
||||
<p>{tooltipContent}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { deleteProject, getProjects } from '@/api/projectApi';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
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 = () => {
|
||||
const { setSelectedPanel, canvas } = useContext(CanvasContext);
|
||||
|
|
@ -63,12 +63,11 @@ export const ProjectPanel = () => {
|
|||
}
|
||||
});
|
||||
|
||||
// Row renderer for react-window
|
||||
const Row = ({ index, style }) => {
|
||||
const project = projects?.data?.[index]; // Get project by index
|
||||
const project = filteredProjects[index]; // Use filtered projects
|
||||
|
||||
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}`)}>
|
||||
<p className='font-bold text-sm truncate'>{project?.name}</p>
|
||||
<p className='text-xs truncate'>{project?.description} </p>
|
||||
|
|
@ -77,11 +76,16 @@ export const ProjectPanel = () => {
|
|||
|
||||
<Button
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
// Filter projects where preview_url is not empty or null
|
||||
const filteredProjects = projects?.data?.filter(project => project?.preview_url) || [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center p-4 border-b">
|
||||
|
|
@ -102,9 +106,9 @@ export const ProjectPanel = () => {
|
|||
{
|
||||
!projectLoading && projectSuccess && projects?.status === 200 &&
|
||||
<List
|
||||
height={500} // Set the height of the viewport
|
||||
itemCount={projects?.data?.length} // Total number of items
|
||||
itemSize={300} // Height of each row
|
||||
height={400} // Set the height of the viewport
|
||||
itemCount={filteredProjects.length} // Use length of filtered projects
|
||||
itemSize={280} // Height of each row
|
||||
width="100%" // Full width of the container
|
||||
>
|
||||
{Row}
|
||||
|
|
|
|||
|
|
@ -21,220 +21,124 @@ export function TopBar() {
|
|||
const activeObjectType = activeObject?.type;
|
||||
const hasClipPath = !!activeObject?.clipPath;
|
||||
const customClipPath = activeObject?.isClipPath;
|
||||
const scrollContainerRef = useRef(null);
|
||||
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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// w - [calc(100 % -80px)] h - full
|
||||
return (
|
||||
<div
|
||||
className={`!absolute top-2 -translate-x-1/2 z-40 ${selectedPanel !== ""
|
||||
? "md:w-[465px] left-[41%] lg:w-[700px] lg:left-[43%] mdxl:left-[45%] xl:w-[900px] xl:left-[46%] 2xl:left-[50%]"
|
||||
: "w-[500px] lg:w-[600px] xl:w-[900px] lg:left-[41%] xl:left-[50%]"
|
||||
}`}
|
||||
onMouseEnter={() => setShowScrollButtons(true)}
|
||||
onMouseLeave={() => setShowScrollButtons(false)}
|
||||
>
|
||||
<div className="relative h-full hidden xl:block">
|
||||
{showScrollButtons && (
|
||||
<>
|
||||
<div className="w-full h-full overflow-x-scroll p-1">
|
||||
<div className="flex item-center justify-center w-fit mx-auto">
|
||||
<div className="bg-white mx-auto flex justify-center items-center gap-2">
|
||||
<div className="flex-1">
|
||||
<TextCustomization />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 px-2">
|
||||
<Button
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-accent"
|
||||
onClick={() => scroll(-1)}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="flex items-center gap-2 border-dashed border-2 rounded-md hover:bg-gray-50"
|
||||
onClick={() => setSelectedPanel("image-insert")}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<ImagePlus className="w-5 h-5" />
|
||||
<span>Add image</span>
|
||||
</Button>
|
||||
<Button
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-accent"
|
||||
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">
|
||||
|
||||
{/* Canvas settings */}
|
||||
<div>
|
||||
<TextCustomization />
|
||||
</div>
|
||||
<div className="flex items-center gap-4 px-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex items-center gap-2 border-dashed border-2 rounded-md hover:bg-gray-50"
|
||||
onClick={() => setSelectedPanel("image-insert")}
|
||||
>
|
||||
<ImagePlus className="w-5 h-5" />
|
||||
<span>Add image</span>
|
||||
</Button>
|
||||
|
||||
{/* canvas settings */}
|
||||
<div>
|
||||
<a data-tooltip-id="canvas">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-10 h-10"
|
||||
onClick={() => setSelectedPanel("canvas")}
|
||||
<a data-tooltip-id="canvas">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-10 h-10"
|
||||
onClick={() => setSelectedPanel("canvas")}
|
||||
>
|
||||
<svg
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="3"
|
||||
y="5"
|
||||
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>
|
||||
<rect x="3" y="5" 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">
|
||||
{activeObjectType !== "image" &&
|
||||
activeObject?.type !== "i-text" &&
|
||||
!hasClipPath &&
|
||||
!customClipPath && (
|
||||
<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">
|
||||
<div className="flex items-center gap-2">
|
||||
{activeObjectType !== "image" &&
|
||||
activeObject?.type !== "i-text" &&
|
||||
!hasClipPath &&
|
||||
!customClipPath && (
|
||||
<a data-tooltip-id="color-gr">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-10 h-10"
|
||||
onClick={() => setSelectedPanel("stroke")}
|
||||
>
|
||||
<RxBorderWidth className="text-lg" />
|
||||
</Button>
|
||||
className="rounded-full"
|
||||
onClick={() => setSelectedPanel("color")}
|
||||
style={{ backgroundColor: textColor?.fill || "black" }}
|
||||
></Button>
|
||||
</a>
|
||||
)}
|
||||
<Tooltip id="stroke" content="Stroke" 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>
|
||||
<Tooltip id="color-gr" content="Color" place="bottom" />
|
||||
|
||||
<div className="h-6 w-px bg-gray-200" />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<a data-tooltip-id="flip">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setSelectedPanel("flip")}
|
||||
>
|
||||
<LuFlipVertical />
|
||||
{!customClipPath && (
|
||||
<a data-tooltip-id="stroke">
|
||||
<Button variant="ghost" size="icon" className="w-10 h-10" onClick={() => setSelectedPanel("stroke")}>
|
||||
<RxBorderWidth className="text-lg" />
|
||||
</Button>
|
||||
</a>
|
||||
)}
|
||||
<Tooltip id="stroke" content="Stroke" place="bottom" />
|
||||
|
||||
<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 />
|
||||
{(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="shadow" content="Shadow color" place="bottom" />
|
||||
</div>
|
||||
)}
|
||||
<Tooltip id="group-obj" content="Group Object" place="bottom" />
|
||||
</div>
|
||||
<OpacityCustomization />
|
||||
<div className="h-4 w-px bg-border mx-2" />
|
||||
|
||||
<div>
|
||||
<ObjectShortcut value="default" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<LockObject />
|
||||
<div className="h-6 w-px bg-gray-200" />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<a data-tooltip-id="flip">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
@ -152,4 +152,24 @@
|
|||
body {
|
||||
@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} */
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
textColor: {
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary-text))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
}
|
||||
},
|
||||
screens: {
|
||||
mdxl: '1100px'
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
selection: {
|
||||
DEFAULT: 'hsl(var(--selection))',
|
||||
foreground: 'hsl(var(--selection-foreground))'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
},
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [scrollbar, scrollbarHide, require("tailwindcss-animate")],
|
||||
darkMode: ["class"],
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
textColor: {
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary-text))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
}
|
||||
},
|
||||
screens: {
|
||||
mdxl: '1100px'
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
selection: {
|
||||
DEFAULT: 'hsl(var(--selection))',
|
||||
foreground: 'hsl(var(--selection-foreground))'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
},
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [scrollbar, scrollbarHide, require("tailwindcss-animate")],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue