added fucntionality for text
This commit is contained in:
parent
954ac950b0
commit
79ca662a08
18 changed files with 826 additions and 656 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -31,6 +31,7 @@
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.3.5",
|
"react-dropzone": "^14.3.5",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-rnd": "^10.4.13",
|
"react-rnd": "^10.4.13",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
|
|
@ -7128,6 +7129,15 @@
|
||||||
"react": ">= 16.8 || 18.0.0"
|
"react": ">= 16.8 || 18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-image-file-resizer": {
|
"node_modules/react-image-file-resizer": {
|
||||||
"version": "0.4.8",
|
"version": "0.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-image-file-resizer/-/react-image-file-resizer-0.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-image-file-resizer/-/react-image-file-resizer-0.4.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.3.5",
|
"react-dropzone": "^14.3.5",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-rnd": "^10.4.13",
|
"react-rnd": "^10.4.13",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
|
|
|
||||||
21
src/App.jsx
21
src/App.jsx
|
|
@ -1,19 +1,20 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
// import Canvas from "./components/Canvas";
|
// import Canvas from "./components/Canvas";
|
||||||
import WebFont from "webfontloader";
|
import WebFont from "webfontloader";
|
||||||
import Header from "./components/Layouts/Header";
|
// import Header from "./components/Layouts/Header";
|
||||||
import SheetRightPanel from "./components/Layouts/SheetRightPanel";
|
// import SheetRightPanel from "./components/Layouts/SheetRightPanel";
|
||||||
import SheetLeftPanel from "./components/Layouts/SheetLeftPanel";
|
// import SheetLeftPanel from "./components/Layouts/SheetLeftPanel";
|
||||||
import CanvasCapture from "./components/CanvasCapture";
|
// import CanvasCapture from "./components/CanvasCapture";
|
||||||
import { Toaster } from "./components/ui/toaster";
|
// import { Toaster } from "./components/ui/toaster";
|
||||||
import { Sidebar } from "./components/Layouts/LeftSidebar";
|
import { Sidebar } from "./components/Layouts/LeftSidebar";
|
||||||
import { Canvas } from "./components/Panel/Canvas";
|
// import TextPanel from "./components/Panel/TextPanel";
|
||||||
import TextPanel from "./components/Panel/TextPanel";
|
|
||||||
import { TopBar } from "./components/Panel/TopBar";
|
import { TopBar } from "./components/Panel/TopBar";
|
||||||
import { ActionButtons } from "./components/ActionButtons";
|
import { ActionButtons } from "./components/ActionButtons";
|
||||||
import EditorPanel from "./components/Panel/EditorPanel";
|
import EditorPanel from "./components/Panel/EditorPanel";
|
||||||
import CanvasContext from "./components/Context/canvasContext/CanvasContext";
|
import CanvasContext from "./components/Context/canvasContext/CanvasContext";
|
||||||
|
import Canvas from "./components/Canvas/Canvas";
|
||||||
|
import ActiveObjectContext from "./components/Context/activeObject/ObjectContext";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -75,7 +76,7 @@ function App() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { selectedPanel } = useContext(CanvasContext);
|
const { selectedPanel } = useContext(CanvasContext);
|
||||||
const [hasSelectedObject, setHasSelectedObject] = useState(true);
|
const { activeObject } = useContext(ActiveObjectContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <div className="relative flex flex-col h-screen overflow-hidden">
|
// <div className="relative flex flex-col h-screen overflow-hidden">
|
||||||
|
|
@ -96,7 +97,7 @@ function App() {
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
{selectedPanel !== "" && <EditorPanel />}
|
{selectedPanel !== "" && <EditorPanel />}
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<TopBar isVisible={hasSelectedObject} />
|
{activeObject && <TopBar />}
|
||||||
<ActionButtons />
|
<ActionButtons />
|
||||||
<Canvas />
|
<Canvas />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||||
import OpenContext from "../Context/openContext/OpenContext";
|
import OpenContext from "../Context/openContext/OpenContext";
|
||||||
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
import { Card, CardContent } from "../ui/card";
|
import { Card, CardContent } from "../ui/card";
|
||||||
|
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
|
||||||
|
|
||||||
export function Canvas() {
|
export default function Canvas() {
|
||||||
const {
|
const {
|
||||||
setLeftPanelOpen,
|
setLeftPanelOpen,
|
||||||
setRightPanelOpen,
|
setRightPanelOpen,
|
||||||
|
|
@ -24,6 +25,36 @@ export function Canvas() {
|
||||||
setScreenWidth,
|
setScreenWidth,
|
||||||
} = useContext(CanvasContext);
|
} = useContext(CanvasContext);
|
||||||
|
|
||||||
|
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!canvas) return; // Ensure canvas is available
|
||||||
|
|
||||||
|
// Event handler for mouse down
|
||||||
|
const handleMouseDown = (event) => {
|
||||||
|
const target = event.target; // Get the clicked target
|
||||||
|
const activeObject = canvas.getActiveObject(); // Get the active object
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
if (target.type === "group") {
|
||||||
|
setActiveObject(activeObject);
|
||||||
|
} else {
|
||||||
|
setActiveObject(activeObject);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setActiveObject(activeObject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach the event listener
|
||||||
|
canvas.on("mouse:down", handleMouseDown);
|
||||||
|
|
||||||
|
// Cleanup function to remove the event listener
|
||||||
|
return () => {
|
||||||
|
canvas.off("mouse:down", handleMouseDown); // Remove the listener on unmount
|
||||||
|
};
|
||||||
|
}, [canvas, setActiveObject]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
import("fabric").then((fabricModule) => {
|
import("fabric").then((fabricModule) => {
|
||||||
window.fabric = fabricModule.fabric;
|
window.fabric = fabricModule.fabric;
|
||||||
|
|
@ -127,7 +158,11 @@ export function Canvas() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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 flex-1 flex flex-col mt-24 mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-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 flex-1 flex flex-col ${
|
||||||
|
activeObject ? "mt-5" : "mt-20"
|
||||||
|
} mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none`}
|
||||||
|
>
|
||||||
<CardContent className="p-0 space-y-2">
|
<CardContent className="p-0 space-y-2">
|
||||||
<AspectRatio
|
<AspectRatio
|
||||||
ratio={getRatioValue(canvasRatio)}
|
ratio={getRatioValue(canvasRatio)}
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { useState } from 'react'
|
import { useState } from "react";
|
||||||
import ActiveObjectContext from './ObjectContext'
|
import ActiveObjectContext from "./ObjectContext";
|
||||||
|
|
||||||
export const ObjectProvider = ({ children }) => {
|
const ObjectProvider = ({ children }) => {
|
||||||
const [activeObject, setActiveObject] = useState(null);
|
const [activeObject, setActiveObject] = useState(null);
|
||||||
return (
|
|
||||||
<ActiveObjectContext.Provider value={{ activeObject, setActiveObject }}>
|
return (
|
||||||
{children}
|
<ActiveObjectContext.Provider value={{ activeObject, setActiveObject }}>
|
||||||
</ActiveObjectContext.Provider>
|
{children}
|
||||||
)
|
</ActiveObjectContext.Provider>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ObjectProvider;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const CanvasContextProvider = ({ children }) => {
|
||||||
const [screenWidth, setScreenWidth] = useState(0);
|
const [screenWidth, setScreenWidth] = useState(0);
|
||||||
const [canvasRatio, setCanvasRatio] = useState("4:3");
|
const [canvasRatio, setCanvasRatio] = useState("4:3");
|
||||||
const [selectedPanel, setSelectedPanel] = useState("");
|
const [selectedPanel, setSelectedPanel] = useState("");
|
||||||
|
const [textColor, setTextColor] = useState(null);
|
||||||
const fabricCanvasRef = useRef(null);
|
const fabricCanvasRef = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -21,6 +22,8 @@ const CanvasContextProvider = ({ children }) => {
|
||||||
canvasHeight,
|
canvasHeight,
|
||||||
canvasRatio,
|
canvasRatio,
|
||||||
setCanvasRatio,
|
setCanvasRatio,
|
||||||
|
textColor,
|
||||||
|
setTextColor,
|
||||||
selectedPanel,
|
selectedPanel,
|
||||||
setSelectedPanel,
|
setSelectedPanel,
|
||||||
setCanvasHeight,
|
setCanvasHeight,
|
||||||
|
|
|
||||||
|
|
@ -1,289 +1,358 @@
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import ActiveObjectContext from '../Context/activeObject/ObjectContext';
|
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
|
||||||
import CanvasContext from '../Context/canvasContext/CanvasContext';
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
import { fabric } from 'fabric';
|
import { fabric } from "fabric";
|
||||||
import { debounce } from "lodash";
|
import { Separator } from "../ui/separator";
|
||||||
import { Separator } from '../ui/separator';
|
import { Label } from "../ui/label";
|
||||||
import { Label } from '../ui/label';
|
import {
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
Select,
|
||||||
import { Input } from '../ui/input';
|
SelectContent,
|
||||||
import { Card } from '../ui/card';
|
SelectItem,
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
SelectTrigger,
|
||||||
import { Button } from '../ui/button';
|
SelectValue,
|
||||||
import CollapsibleComponent from './Customization/CollapsibleComponent';
|
} from "../ui/select";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Card } from "../ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
|
import CollapsibleComponent from "./Customization/CollapsibleComponent";
|
||||||
|
|
||||||
const ApplyColor = () => {
|
const ApplyColor = () => {
|
||||||
const [colorField, setColorField] = useState("fill");
|
const [colorField, setColorField] = useState("fill");
|
||||||
const [fillColor, setFillColor] = useState("");
|
const [fillColor, setFillColor] = useState("");
|
||||||
const [backgroundColor, setBackgroundColor] = useState("");
|
const [backgroundColor, setBackgroundColor] = useState("");
|
||||||
const [gradientFillColors, setGradientFillColors] = useState({
|
const [gradientFillColors, setGradientFillColors] = useState({
|
||||||
color1: "#f97316",
|
color1: "#f97316",
|
||||||
color2: "#e26286",
|
color2: "#e26286",
|
||||||
});
|
});
|
||||||
const [colorType, setColorType] = useState("color"); // 'color' or 'gradient'
|
const [colorType, setColorType] = useState("color"); // 'color' or 'gradient'
|
||||||
const [gradientDirection, setGradientDirection] = useState("top-to-bottom");
|
const [gradientDirection, setGradientDirection] = useState("top-to-bottom");
|
||||||
// get values from context
|
|
||||||
const { activeObject } = useContext(ActiveObjectContext);
|
|
||||||
const { canvas } = useContext(CanvasContext);
|
|
||||||
|
|
||||||
// to get previous values from active object
|
// Get values from context
|
||||||
useEffect(() => {
|
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
|
||||||
const handleObjectStyle = (object) => {
|
const { canvas, setTextColor } = useContext(CanvasContext);
|
||||||
if (object.fill) {
|
|
||||||
if (typeof object.fill === "string") {
|
|
||||||
setColorType("color");
|
|
||||||
} else if (object.fill instanceof fabric.Gradient) {
|
|
||||||
setColorType("gradient");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle solid colors
|
// To get previous values from active object
|
||||||
if (object.fill && !object.fill.colorStops) {
|
useEffect(() => {
|
||||||
setFillColor(object.fill); // Solid fill
|
const handleObjectStyle = (object) => {
|
||||||
}
|
if (object.fill) {
|
||||||
if (object.backgroundColor) {
|
if (typeof object.fill === "string") {
|
||||||
setBackgroundColor(object.backgroundColor); // Solid background color
|
setColorType("color");
|
||||||
}
|
} else if (object.fill instanceof fabric.Gradient) {
|
||||||
|
setColorType("gradient");
|
||||||
// Handle gradients
|
|
||||||
if (object.fill?.colorStops) {
|
|
||||||
setGradientFillColors({
|
|
||||||
color1: object.fill.colorStops[0]?.color || "",
|
|
||||||
color2: object.fill.colorStops[1]?.color || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const processGroupObjects = (group) => {
|
|
||||||
group._objects.forEach((obj) => {
|
|
||||||
if (obj.type === "group") {
|
|
||||||
processGroupObjects(obj); // Recursively handle nested groups
|
|
||||||
} else {
|
|
||||||
handleObjectStyle(obj); // Apply styles to child objects
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (activeObject) {
|
|
||||||
if (activeObject.type === "group") {
|
|
||||||
processGroupObjects(activeObject); // Process all objects in the group
|
|
||||||
} else {
|
|
||||||
handleObjectStyle(activeObject); // Handle single object
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [activeObject, colorField]);
|
}
|
||||||
|
|
||||||
const handleSolidColorChange = debounce((newColor) => {
|
// Handle solid colors
|
||||||
if (colorField === "fill") {
|
if (object.fill && !object.fill.colorStops) {
|
||||||
setFillColor(newColor)
|
setFillColor(object.fill); // Solid fill
|
||||||
}
|
}
|
||||||
else {
|
if (object.backgroundColor) {
|
||||||
setBackgroundColor(newColor)
|
setBackgroundColor(object.backgroundColor); // Solid background color
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// Common debounce handler for updating colors
|
// Handle gradients
|
||||||
const debouncedSetGradientColors = debounce((key, value) => {
|
if (object.fill?.colorStops) {
|
||||||
if (colorField === "fill") {
|
setGradientFillColors({
|
||||||
setGradientFillColors((prev) => ({
|
color1: object.fill.colorStops[0]?.color || "",
|
||||||
...prev,
|
color2: object.fill.colorStops[1]?.color || "",
|
||||||
[key]: value,
|
});
|
||||||
}))
|
}
|
||||||
}
|
|
||||||
}, 300); // Adjust debounce delay as needed
|
|
||||||
|
|
||||||
const handleColorChange = (key) => (e) => {
|
|
||||||
const newColor = e.target.value;
|
|
||||||
debouncedSetGradientColors(key, newColor);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply color/gradient style to the selected object on the canvas
|
const processGroupObjects = (group) => {
|
||||||
const applyColor = () => {
|
group._objects.forEach((obj) => {
|
||||||
const applyStyleToObject = (object, style) => {
|
if (obj.type === "group") {
|
||||||
if (colorType === "color") {
|
processGroupObjects(obj); // Recursively handle nested groups
|
||||||
if (colorField === "fill" && object.fill !== style.fill) {
|
} else {
|
||||||
object.set("fill", style.fill);
|
handleObjectStyle(obj); // Apply styles to child objects
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (colorField === "background" && object.backgroundColor !== style.backgroundColor) {
|
if (activeObject) {
|
||||||
object.set("backgroundColor", style.backgroundColor);
|
if (activeObject.type === "group") {
|
||||||
}
|
processGroupObjects(activeObject); // Process all objects in the group
|
||||||
}
|
} else {
|
||||||
|
handleObjectStyle(activeObject); // Handle single object
|
||||||
if (colorType === "gradient" && colorField === "fill") {
|
}
|
||||||
const width = object?.width || 0;
|
|
||||||
const height = object?.height || 0;
|
|
||||||
|
|
||||||
const coords = {
|
|
||||||
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
|
|
||||||
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
|
|
||||||
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
|
|
||||||
"right-to-left": { x1: width, y1: 0, x2: 0, y2: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const directionCoords = coords[gradientDirection];
|
|
||||||
const gradient = new fabric.Gradient({
|
|
||||||
type: "linear",
|
|
||||||
gradientUnits: "pixels",
|
|
||||||
coords: directionCoords,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: gradientFillColors.color1 },
|
|
||||||
{ offset: 1, color: gradientFillColors.color2 },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
object.set("fill", gradient);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyStyleRecursively = (object, style) => {
|
|
||||||
if (object.type === "group" && object._objects) {
|
|
||||||
// If the object is a group, iterate through its children
|
|
||||||
object._objects.forEach((child) => applyStyleRecursively(child, style));
|
|
||||||
} else {
|
|
||||||
// If the object is not a group, apply the style directly
|
|
||||||
applyStyleToObject(object, style);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
fill: fillColor,
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
};
|
|
||||||
|
|
||||||
applyStyleRecursively(activeObject, style);
|
|
||||||
|
|
||||||
// Trigger re-render for the canvas
|
|
||||||
canvas.renderAll();
|
|
||||||
}
|
}
|
||||||
|
}, [activeObject, colorField]);
|
||||||
|
|
||||||
const content = () => {
|
// Apply color/gradient style to the selected object on the canvas
|
||||||
return (
|
const applyColor = useCallback(() => {
|
||||||
<div>
|
const applyStyleToObject = (object, style) => {
|
||||||
<div className="grid">
|
if (colorType === "color") {
|
||||||
<div className="space-y-2">
|
if (colorField === "fill" && object.fill !== style.fill) {
|
||||||
<div className="space-y-2">
|
object.set("fill", style.fill);
|
||||||
<Label htmlFor="colorField">Color Field</Label>
|
}
|
||||||
<Select value={colorField} onValueChange={(value) => setColorField(value)}>
|
|
||||||
<SelectTrigger id="colorField">
|
|
||||||
<SelectValue placeholder="Select color field type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="fill">Fill</SelectItem>
|
|
||||||
<SelectItem value="background">Background</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs value={colorType} onValueChange={(value) => setColorType(value)}>
|
if (
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
colorField === "background" &&
|
||||||
<TabsTrigger value="color">Solid Color</TabsTrigger>
|
object.backgroundColor !== style.backgroundColor
|
||||||
<TabsTrigger value="gradient" disabled={colorField === 'background'}>Gradient</TabsTrigger>
|
) {
|
||||||
</TabsList>
|
object.set("backgroundColor", style.backgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<TabsContent value="color" className="space-y-4">
|
if (colorType === "gradient" && colorField === "fill") {
|
||||||
<div className="space-y-2">
|
const width = object?.width || 0;
|
||||||
<Label htmlFor="solidColor">Color</Label>
|
const height = object?.height || 0;
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Input
|
|
||||||
id="solidColor"
|
|
||||||
type="color"
|
|
||||||
value={colorField === "fill" ? fillColor : backgroundColor}
|
|
||||||
onChange={(e) => handleSolidColorChange(e.target.value)}
|
|
||||||
className="w-12 h-12 p-1 rounded-md"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={colorField === "fill" ? fillColor : backgroundColor}
|
|
||||||
onChange={(e) => handleSolidColorChange(e.target.value)}
|
|
||||||
className="flex-grow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-24 rounded-md" style={{
|
const coords = {
|
||||||
backgroundColor: colorField === "fill" ? fillColor : backgroundColor,
|
"top-to-bottom": { x1: 0, y1: 0, x2: 0, y2: height },
|
||||||
}}></div>
|
"bottom-to-top": { x1: 0, y1: height, x2: 0, y2: 0 },
|
||||||
</TabsContent>
|
"left-to-right": { x1: 0, y1: 0, x2: width, y2: 0 },
|
||||||
|
"right-to-left": { x1: width, y1: 0, x2: 0, y2: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
<TabsContent value="gradient" className="space-y-4">
|
const directionCoords = coords[gradientDirection];
|
||||||
<div className="space-y-2">
|
const gradient = new fabric.Gradient({
|
||||||
<Label htmlFor="gradientDirection">Direction</Label>
|
type: "linear",
|
||||||
<Select value={gradientDirection} onValueChange={setGradientDirection}>
|
gradientUnits: "pixels",
|
||||||
<SelectTrigger id="gradientDirection">
|
coords: directionCoords,
|
||||||
<SelectValue placeholder="Select direction" />
|
colorStops: [
|
||||||
</SelectTrigger>
|
{ offset: 0, color: gradientFillColors.color1 },
|
||||||
<SelectContent>
|
{ offset: 1, color: gradientFillColors.color2 },
|
||||||
<SelectItem value="top-to-bottom">Top to Bottom</SelectItem>
|
],
|
||||||
<SelectItem value="bottom-to-top">Bottom to Top</SelectItem>
|
});
|
||||||
<SelectItem value="left-to-right">Left to Right</SelectItem>
|
|
||||||
<SelectItem value="right-to-left">Right to Left</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="gradientColor1">Color 1</Label>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Input
|
|
||||||
id="gradientColor1"
|
|
||||||
type="color"
|
|
||||||
value={gradientFillColors.color1}
|
|
||||||
onChange={handleColorChange('color1')}
|
|
||||||
className="w-12 h-12 p-1 rounded-md"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={gradientFillColors.color1}
|
|
||||||
onChange={handleColorChange('color1')}
|
|
||||||
className="flex-grow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="gradientColor2">Color 2</Label>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Input
|
|
||||||
id="gradientColor2"
|
|
||||||
type="color"
|
|
||||||
value={gradientFillColors.color2}
|
|
||||||
onChange={handleColorChange('color2')}
|
|
||||||
className="w-12 h-12 p-1 rounded-md"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={gradientFillColors.color2}
|
|
||||||
onChange={handleColorChange('color2')}
|
|
||||||
className="flex-grow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="h-24 rounded-md" style={{
|
|
||||||
background: `linear-gradient(${gradientDirection === 'top-to-bottom' ? 'to bottom' :
|
|
||||||
gradientDirection === 'bottom-to-top' ? 'to top' :
|
|
||||||
gradientDirection === 'left-to-right' ? 'to right' :
|
|
||||||
'to left'
|
|
||||||
}, ${gradientFillColors.color1}, ${gradientFillColors.color2})`,
|
|
||||||
}}></div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className="my-2" onClick={() => { applyColor() }}>Apply</Button>
|
object.set("fill", gradient);
|
||||||
</div>
|
}
|
||||||
</div>
|
};
|
||||||
)
|
|
||||||
|
const applyStyleRecursively = (object, style) => {
|
||||||
|
if (object.type === "group" && object._objects) {
|
||||||
|
// If the object is a group, iterate through its children
|
||||||
|
object._objects.forEach((child) => applyStyleRecursively(child, style));
|
||||||
|
} else {
|
||||||
|
// If the object is not a group, apply the style directly
|
||||||
|
applyStyleToObject(object, style);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
fill: fillColor,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (activeObject) {
|
||||||
|
applyStyleRecursively(activeObject, style);
|
||||||
|
setActiveObject(activeObject);
|
||||||
|
setTextColor(style);
|
||||||
|
canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
}, [
|
||||||
|
activeObject,
|
||||||
|
fillColor,
|
||||||
|
backgroundColor,
|
||||||
|
gradientFillColors,
|
||||||
|
colorType,
|
||||||
|
gradientDirection,
|
||||||
|
setActiveObject,
|
||||||
|
canvas,
|
||||||
|
colorField,
|
||||||
|
setTextColor,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Watch for changes in color-related states and apply them instantly
|
||||||
|
useEffect(() => {
|
||||||
|
applyColor();
|
||||||
|
}, [
|
||||||
|
applyColor, // Now included in dependencies
|
||||||
|
fillColor,
|
||||||
|
backgroundColor,
|
||||||
|
gradientFillColors,
|
||||||
|
colorType,
|
||||||
|
gradientDirection,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleSolidColorChange = (newColor) => {
|
||||||
|
if (colorField === "fill") {
|
||||||
|
setFillColor(newColor);
|
||||||
|
} else {
|
||||||
|
setBackgroundColor(newColor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGradientColorChange = (key, value) => {
|
||||||
|
setGradientFillColors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Card className="p-2">
|
<div className="grid">
|
||||||
<CollapsibleComponent text={"Color Control"}>
|
<div className="space-y-2">
|
||||||
{content()}
|
<div className="space-y-2">
|
||||||
</CollapsibleComponent>
|
<Label htmlFor="colorField">Color Field</Label>
|
||||||
</Card>
|
<Select
|
||||||
<Separator className="my-2" />
|
value={colorField}
|
||||||
</div>
|
onValueChange={(value) => setColorField(value)}
|
||||||
)
|
>
|
||||||
}
|
<SelectTrigger id="colorField">
|
||||||
|
<SelectValue placeholder="Select color field type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="fill">Fill</SelectItem>
|
||||||
|
<SelectItem value="background">Background</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
export default ApplyColor
|
<Tabs
|
||||||
|
value={colorType}
|
||||||
|
onValueChange={(value) => setColorType(value)}
|
||||||
|
>
|
||||||
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
<TabsTrigger value="color">Solid Color</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="gradient"
|
||||||
|
disabled={colorField === "background"}
|
||||||
|
>
|
||||||
|
Gradient
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="color" className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="solidColor">Color</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
id="solidColor"
|
||||||
|
type="color"
|
||||||
|
value={
|
||||||
|
colorField === "fill" ? fillColor : backgroundColor
|
||||||
|
}
|
||||||
|
onChange={(e) => handleSolidColorChange(e.target.value)}
|
||||||
|
className="w-12 h-12 p-1 rounded-md"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={
|
||||||
|
colorField === "fill" ? fillColor : backgroundColor
|
||||||
|
}
|
||||||
|
onChange={(e) => handleSolidColorChange(e.target.value)}
|
||||||
|
className="flex-grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="h-24 rounded-md"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
colorField === "fill" ? fillColor : backgroundColor,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="gradient" className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="gradientDirection">Direction</Label>
|
||||||
|
<Select
|
||||||
|
value={gradientDirection}
|
||||||
|
onValueChange={setGradientDirection}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="gradientDirection">
|
||||||
|
<SelectValue placeholder="Select direction" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="top-to-bottom">
|
||||||
|
Top to Bottom
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="bottom-to-top">
|
||||||
|
Bottom to Top
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="left-to-right">
|
||||||
|
Left to Right
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="right-to-left">
|
||||||
|
Right to Left
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="gradientColor1">Color 1</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
id="gradientColor1"
|
||||||
|
type="color"
|
||||||
|
value={gradientFillColors.color1}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color1", e.target.value)
|
||||||
|
}
|
||||||
|
className="w-12 h-12 p-1 rounded-md"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={gradientFillColors.color1}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color1", e.target.value)
|
||||||
|
}
|
||||||
|
className="flex-grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="gradientColor2">Color 2</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
id="gradientColor2"
|
||||||
|
type="color"
|
||||||
|
value={gradientFillColors.color2}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color2", e.target.value)
|
||||||
|
}
|
||||||
|
className="w-12 h-12 p-1 rounded-md"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={gradientFillColors.color2}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color2", e.target.value)
|
||||||
|
}
|
||||||
|
className="flex-grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="h-24 rounded-md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(${
|
||||||
|
gradientDirection === "top-to-bottom"
|
||||||
|
? "to bottom"
|
||||||
|
: gradientDirection === "bottom-to-top"
|
||||||
|
? "to top"
|
||||||
|
: gradientDirection === "left-to-right"
|
||||||
|
? "to right"
|
||||||
|
: "to left"
|
||||||
|
}, ${gradientFillColors.color1}, ${
|
||||||
|
gradientFillColors.color2
|
||||||
|
})`,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card className="border-0 shadow-none">
|
||||||
|
<CollapsibleComponent text={"Color Control"}>
|
||||||
|
{content()}
|
||||||
|
</CollapsibleComponent>
|
||||||
|
</Card>
|
||||||
|
<Separator className="my-2" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApplyColor;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const CollapsibleComponent = ({ children, text }) => {
|
||||||
}, [text]);
|
}, [text]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
<Collapsible open={isOpen}>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-between cursor-pointer ${
|
className={`flex items-center justify-between cursor-pointer ${
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { Lock, Unlock } from "lucide-react";
|
import { Lock, Unlock } from "lucide-react";
|
||||||
import { Card } from "@/components/ui/card";
|
|
||||||
|
|
||||||
const LockObject = () => {
|
const LockObject = () => {
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext);
|
||||||
|
|
@ -51,8 +50,7 @@ const LockObject = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="shadow-none border-0">
|
<div className="shadow-none border-0">
|
||||||
<h2 className="font-bold">{!isLocked ? "Lock" : "Unlock"} Object</h2>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={toggleLock}
|
onClick={toggleLock}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -66,7 +64,7 @@ const LockObject = () => {
|
||||||
<Lock className="h-4 w-4" />
|
<Lock className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,61 @@
|
||||||
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext';
|
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
||||||
import CanvasContext from '@/components/Context/canvasContext/CanvasContext';
|
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from "@/components/ui/label";
|
||||||
import { Slider } from '@/components/ui/slider';
|
import { Slider } from "@/components/ui/slider";
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { BsTransparency } from "react-icons/bs";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const OpacityCustomization = () => {
|
const OpacityCustomization = () => {
|
||||||
const { activeObject } = useContext(ActiveObjectContext);
|
const { activeObject } = useContext(ActiveObjectContext);
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext);
|
||||||
|
const [opacity, setOpacity] = useState(0);
|
||||||
|
|
||||||
const [opacity, setOpacity] = useState(0);
|
useEffect(() => {
|
||||||
|
if (activeObject) {
|
||||||
|
setOpacity(activeObject?.opacity);
|
||||||
|
}
|
||||||
|
}, [activeObject]);
|
||||||
|
|
||||||
useEffect(() => {
|
const adjustBackgroundOpacity = (newOpacity) => {
|
||||||
if (activeObject) {
|
setOpacity(newOpacity);
|
||||||
setOpacity(activeObject?.opacity);
|
if (activeObject) {
|
||||||
}
|
activeObject.set("opacity", newOpacity);
|
||||||
}, [activeObject])
|
canvas.renderAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const adjustBackgroundOpacity = (value) => {
|
return (
|
||||||
setOpacity(value);
|
<Popover>
|
||||||
if (activeObject) {
|
<PopoverTrigger asChild>
|
||||||
activeObject.set("opacity", opacity); // Update the opacity
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
canvas.renderAll(); // Re-render the canvas
|
<BsTransparency className="h-4 w-4" size={20} />
|
||||||
}
|
</Button>
|
||||||
};
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-64 mt-3">
|
||||||
return (
|
<div className="grid gap-4">
|
||||||
<div className='grid gap-2 pt-1'>
|
<div className="flex items-center justify-between">
|
||||||
<Label>
|
<Label className="text-sm font-medium">Opacity</Label>
|
||||||
Adjust Opacity:
|
<span className="text-sm text-gray-500">
|
||||||
</Label>
|
{Math.round(opacity * 100)}%
|
||||||
<Slider
|
</span>
|
||||||
value={[opacity]}
|
</div>
|
||||||
min={0.0}
|
<Slider
|
||||||
max={1.0}
|
value={[opacity]}
|
||||||
step={0.01} // Step size for fine control
|
min={0}
|
||||||
onValueChange={(value) => {
|
max={1}
|
||||||
const newOpacity = value[0]; // Extract slider value
|
step={0.01}
|
||||||
adjustBackgroundOpacity(newOpacity); // Adjust Fabric.js background opacity
|
onValueChange={(value) => adjustBackgroundOpacity(value[0])}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</PopoverContent>
|
||||||
}
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default OpacityCustomization
|
export default OpacityCustomization;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const StrokeCustomization = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (object.strokeWidth) {
|
if (object.strokeWidth) {
|
||||||
setStrokeWidth(object.strokeWidth || 0);
|
setStrokeWidth(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
||||||
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,9 +12,12 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Slider } from "@/components/ui/slider";
|
import {
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
Popover,
|
||||||
import { useContext, useEffect, useState } from "react";
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AlignLeft,
|
AlignLeft,
|
||||||
AlignCenter,
|
AlignCenter,
|
||||||
|
|
@ -23,8 +26,11 @@ import {
|
||||||
Italic,
|
Italic,
|
||||||
Underline,
|
Underline,
|
||||||
Strikethrough,
|
Strikethrough,
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import CollapsibleComponent from "./CollapsibleComponent";
|
import { RiLineHeight } from "react-icons/ri";
|
||||||
|
import { Slider } from "@/components/ui/slider";
|
||||||
|
|
||||||
const fonts = [
|
const fonts = [
|
||||||
"Roboto",
|
"Roboto",
|
||||||
|
|
@ -79,9 +85,8 @@ const fonts = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const TextCustomization = () => {
|
const TextCustomization = () => {
|
||||||
// get values from context
|
|
||||||
const { activeObject } = useContext(ActiveObjectContext);
|
const { activeObject } = useContext(ActiveObjectContext);
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext);
|
||||||
|
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const [fontFamily, setFontFamily] = useState("Arial");
|
const [fontFamily, setFontFamily] = useState("Arial");
|
||||||
|
|
@ -93,7 +98,7 @@ const TextCustomization = () => {
|
||||||
const [underline, setUnderline] = useState(false);
|
const [underline, setUnderline] = useState(false);
|
||||||
const [linethrough, setLinethrough] = useState(false);
|
const [linethrough, setLinethrough] = useState(false);
|
||||||
const [textAlign, setTextAlign] = useState("left");
|
const [textAlign, setTextAlign] = useState("left");
|
||||||
const [previewText, setPreviewText] = useState("");
|
const [fillColor, setFillColor] = useState("black");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeObject?.type === "i-text") {
|
if (activeObject?.type === "i-text") {
|
||||||
|
|
@ -107,18 +112,21 @@ const TextCustomization = () => {
|
||||||
setUnderline(activeObject?.underline || false);
|
setUnderline(activeObject?.underline || false);
|
||||||
setLinethrough(activeObject?.linethrough || false);
|
setLinethrough(activeObject?.linethrough || false);
|
||||||
setTextAlign(activeObject?.textAlign || "left");
|
setTextAlign(activeObject?.textAlign || "left");
|
||||||
setPreviewText(activeObject?.text || "");
|
setFillColor(textColor?.fill || "black");
|
||||||
}
|
}
|
||||||
}, [activeObject]);
|
}, [activeObject, textColor]);
|
||||||
|
|
||||||
const updateActiveObject = (properties) => {
|
const updateActiveObject = useCallback(
|
||||||
if (activeObject?.type === "i-text") {
|
(properties) => {
|
||||||
activeObject.set(properties);
|
if (activeObject?.type === "i-text") {
|
||||||
canvas?.renderAll();
|
activeObject.set(properties);
|
||||||
}
|
canvas?.renderAll();
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[activeObject, canvas]
|
||||||
|
); // Add dependencies
|
||||||
|
|
||||||
const applyChanges = () => {
|
const applyChanges = useCallback(() => {
|
||||||
updateActiveObject({
|
updateActiveObject({
|
||||||
text,
|
text,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
|
|
@ -131,12 +139,29 @@ const TextCustomization = () => {
|
||||||
linethrough,
|
linethrough,
|
||||||
textAlign,
|
textAlign,
|
||||||
});
|
});
|
||||||
};
|
}, [
|
||||||
|
text,
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
fontStyle,
|
||||||
|
fontWeight,
|
||||||
|
lineHeight,
|
||||||
|
charSpacing,
|
||||||
|
underline,
|
||||||
|
linethrough,
|
||||||
|
textAlign,
|
||||||
|
updateActiveObject, // Add this dependency
|
||||||
|
]);
|
||||||
|
|
||||||
const handleTextChange = (newText) => {
|
// Automatically apply changes when state updates
|
||||||
setText(newText);
|
useEffect(() => {
|
||||||
setPreviewText(newText);
|
if (activeObject?.type === "i-text") {
|
||||||
};
|
applyChanges();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
applyChanges, // Now included in dependencies
|
||||||
|
activeObject?.type, // Track active object type
|
||||||
|
]);
|
||||||
|
|
||||||
const handleFontFamilyChange = (newFontFamily) => {
|
const handleFontFamilyChange = (newFontFamily) => {
|
||||||
setFontFamily(newFontFamily);
|
setFontFamily(newFontFamily);
|
||||||
|
|
@ -151,204 +176,217 @@ const TextCustomization = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFontStyleChange = () => {
|
const handleFontStyleChange = () => {
|
||||||
const newFontStyle = fontStyle === "normal" ? "italic" : "normal";
|
setFontStyle(fontStyle === "normal" ? "italic" : "normal");
|
||||||
setFontStyle(newFontStyle);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFontWeightChange = () => {
|
const handleFontWeightChange = () => {
|
||||||
const newFontWeight = fontWeight === "normal" ? "bold" : "normal";
|
setFontWeight(fontWeight === "normal" ? "bold" : "normal");
|
||||||
setFontWeight(newFontWeight);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLineHeightChange = (newLineHeight) => {
|
const handleLineHeightChange = (newLineHeight) => {
|
||||||
setLineHeight(newLineHeight);
|
setLineHeight(parseFloat(newLineHeight));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCharSpacingChange = (newCharSpacing) => {
|
const handleCharSpacingChange = (newCharSpacing) => {
|
||||||
setCharSpacing(newCharSpacing);
|
setCharSpacing(parseInt(newCharSpacing));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnderlineChange = () => {
|
const handleUnderlineChange = () => {
|
||||||
const newUnderline = !underline;
|
setUnderline(!underline);
|
||||||
setUnderline(newUnderline);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLinethroughChange = () => {
|
const handleLinethroughChange = () => {
|
||||||
const newLinethrough = !linethrough;
|
setLinethrough(!linethrough);
|
||||||
setLinethrough(newLinethrough);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!(activeObject?.type === "i-text")) {
|
if (!(activeObject?.type === "i-text")) {
|
||||||
return (
|
return <div></div>;
|
||||||
<div className="mt-2">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<p className="text-center text-gray-500">
|
|
||||||
Select a text object to customize
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<CardContent className="p-2">
|
|
||||||
<Tabs defaultValue="text" className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
|
||||||
<TabsTrigger value="text">Text</TabsTrigger>
|
|
||||||
<TabsTrigger value="style">Style</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="text" className="space-y-1">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="text-content">Text Content</Label>
|
|
||||||
<Input
|
|
||||||
id="text-content"
|
|
||||||
value={text}
|
|
||||||
onChange={(e) => handleTextChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label>Font Family</Label>
|
|
||||||
<Select
|
|
||||||
value={fontFamily}
|
|
||||||
onValueChange={handleFontFamilyChange}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a font" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent className="h-[250px]">
|
|
||||||
<SelectGroup>
|
|
||||||
{fonts.map((font) => (
|
|
||||||
<SelectItem
|
|
||||||
style={{ fontFamily: font }}
|
|
||||||
key={font}
|
|
||||||
value={font}
|
|
||||||
>
|
|
||||||
{font}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label>Font Size</Label>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Slider
|
|
||||||
value={[fontSize]}
|
|
||||||
onValueChange={([value]) => handleFontSizeChange(value)}
|
|
||||||
min={8}
|
|
||||||
max={72}
|
|
||||||
step={1}
|
|
||||||
className="flex-grow"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={fontSize}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleFontSizeChange(parseInt(e.target.value, 10) || 16)
|
|
||||||
}
|
|
||||||
className="w-16"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="style" className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label>Text Alignment</Label>
|
|
||||||
<div className="flex space-x-1">
|
|
||||||
<Button
|
|
||||||
variant={textAlign === "left" ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={() => handleTextAlignChange("left")}
|
|
||||||
>
|
|
||||||
<AlignLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={textAlign === "center" ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={() => handleTextAlignChange("center")}
|
|
||||||
>
|
|
||||||
<AlignCenter className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={textAlign === "right" ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={() => handleTextAlignChange("right")}
|
|
||||||
>
|
|
||||||
<AlignRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label>Text Style</Label>
|
|
||||||
<div className="flex space-x-1">
|
|
||||||
<Button
|
|
||||||
variant={fontWeight === "bold" ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={handleFontWeightChange}
|
|
||||||
>
|
|
||||||
<Bold className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={fontStyle === "italic" ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={handleFontStyleChange}
|
|
||||||
>
|
|
||||||
<Italic className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={underline ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={handleUnderlineChange}
|
|
||||||
>
|
|
||||||
<Underline className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={linethrough ? "default" : "outline"}
|
|
||||||
size="icon"
|
|
||||||
onClick={handleLinethroughChange}
|
|
||||||
>
|
|
||||||
<Strikethrough className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Line Height</Label>
|
|
||||||
<Slider
|
|
||||||
value={[lineHeight]}
|
|
||||||
onValueChange={([value]) => handleLineHeightChange(value)}
|
|
||||||
min={0.5}
|
|
||||||
max={3}
|
|
||||||
step={0.1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Character Spacing</Label>
|
|
||||||
<Slider
|
|
||||||
value={[charSpacing]}
|
|
||||||
onValueChange={([value]) => handleCharSpacingChange(value)}
|
|
||||||
min={-20}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-2">
|
<div className="space-y-4">
|
||||||
<CollapsibleComponent text={"Text Control"}>
|
{/* New Toolbar Design */}
|
||||||
{content()}
|
<div className="flex w-full items-center space-x-2 rounded-lg p-1 bg-white">
|
||||||
</CollapsibleComponent>
|
{/* Font Family Select */}
|
||||||
<div className="mt-4 space-y-4">
|
<Select value={fontFamily} onValueChange={handleFontFamilyChange}>
|
||||||
{previewText && (
|
<SelectTrigger className="min-w-[140px] h-8">
|
||||||
|
<SelectValue placeholder="Select a font" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="h-[250px]">
|
||||||
|
<SelectGroup>
|
||||||
|
{fonts.map((font) => (
|
||||||
|
<SelectItem
|
||||||
|
key={font}
|
||||||
|
value={font}
|
||||||
|
style={{ fontFamily: font }}
|
||||||
|
>
|
||||||
|
{font}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{/* Font Size Controls */}
|
||||||
|
<div className="flex items-center border rounded-md">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-none"
|
||||||
|
onClick={() => handleFontSizeChange(Math.max(8, fontSize - 1))}
|
||||||
|
>
|
||||||
|
<Minus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={fontSize}
|
||||||
|
onChange={(e) => {
|
||||||
|
const numericValue = e.target.value.replace(/\D/g, "");
|
||||||
|
handleFontSizeChange(parseInt(numericValue) || 12);
|
||||||
|
}}
|
||||||
|
className="w-12 h-8 border-0 text-center"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-none"
|
||||||
|
onClick={() => handleFontSizeChange(Math.min(72, fontSize + 1))}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vertical Separator */}
|
||||||
|
<div className="h-6 w-px bg-gray-200" />
|
||||||
|
|
||||||
|
{/* Text Formatting Controls */}
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="relative"
|
||||||
|
onClick={() => setSelectedPanel("color")}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<span className="text-lg font-semibold">A</span>
|
||||||
|
<div
|
||||||
|
className="absolute -bottom-0.5 -left-1 right-0 h-1 w-5 transition-all duration-200"
|
||||||
|
style={{ backgroundColor: fillColor }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={textAlign === "left" ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleTextAlignChange("left")}
|
||||||
|
>
|
||||||
|
<AlignLeft className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={textAlign === "center" ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleTextAlignChange("center")}
|
||||||
|
>
|
||||||
|
<AlignCenter className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={textAlign === "right" ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleTextAlignChange("right")}
|
||||||
|
>
|
||||||
|
<AlignRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={fontWeight === "bold" ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={handleFontWeightChange}
|
||||||
|
>
|
||||||
|
<Bold className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={underline ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={handleUnderlineChange}
|
||||||
|
>
|
||||||
|
<Underline className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={fontStyle === "italic" ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={handleFontStyleChange}
|
||||||
|
>
|
||||||
|
<Italic className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={linethrough ? "secondary" : "ghost"}
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={handleLinethroughChange}
|
||||||
|
>
|
||||||
|
<Strikethrough className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vertical Separator */}
|
||||||
|
<div className="h-6 w-px bg-gray-200" />
|
||||||
|
|
||||||
|
{/* Spacing Controls */}
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
|
<RiLineHeight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-44 mt-3">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Line Spacing</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Slider
|
||||||
|
value={[lineHeight]}
|
||||||
|
onValueChange={([value]) => handleLineHeightChange(value)}
|
||||||
|
min={0.5}
|
||||||
|
max={3}
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Letter Spacing</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Slider
|
||||||
|
value={[charSpacing]}
|
||||||
|
onValueChange={([value]) =>
|
||||||
|
handleCharSpacingChange(value)
|
||||||
|
}
|
||||||
|
min={-20}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text Input */}
|
||||||
|
{/* <div className="space-y-2">
|
||||||
|
<Label htmlFor="text-content">Text Content</Label>
|
||||||
|
<Input
|
||||||
|
id="text-content"
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => handleTextChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* Preview */}
|
||||||
|
{/* {previewText && (
|
||||||
<div className="p-4 border rounded-md overflow-hidden">
|
<div className="p-4 border rounded-md overflow-hidden">
|
||||||
<p className="font-bold mb-2">Preview:</p>
|
<p className="font-bold mb-2">Preview:</p>
|
||||||
<p
|
<p
|
||||||
|
|
@ -369,14 +407,17 @@ const TextCustomization = () => {
|
||||||
{previewText}
|
{previewText}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<Button onClick={applyChanges} className="w-full">
|
{/* Apply Changes Button */}
|
||||||
|
{/* <Button onClick={applyChanges} className="w-full">
|
||||||
Apply Changes
|
Apply Changes
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
|
return <div className="">{content()}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextCustomization;
|
export default TextCustomization;
|
||||||
|
|
|
||||||
29
src/components/Panel/ColorPanel.jsx
Normal file
29
src/components/Panel/ColorPanel.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import ApplyColor from "../EachComponent/ApplyColor";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
|
|
||||||
|
const ColorPanel = () => {
|
||||||
|
const { setSelectedPanel } = useContext(CanvasContext);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center p-4 border-b">
|
||||||
|
<h2 className="text-lg font-semibold">Color</h2>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSelectedPanel("")}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
|
||||||
|
<ApplyColor />
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorPanel;
|
||||||
|
|
@ -5,23 +5,31 @@ import StrokeCustomization from "../EachComponent/Customization/StrokeCustomizat
|
||||||
import PositionCustomization from "../EachComponent/Customization/PositionCustomization";
|
import PositionCustomization from "../EachComponent/Customization/PositionCustomization";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "../ui/card";
|
||||||
import CollapsibleComponent from "../EachComponent/Customization/CollapsibleComponent";
|
import CollapsibleComponent from "../EachComponent/Customization/CollapsibleComponent";
|
||||||
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
|
|
||||||
import FlipCustomization from "../EachComponent/Customization/FlipCustomization";
|
import FlipCustomization from "../EachComponent/Customization/FlipCustomization";
|
||||||
import RotateCustomization from "../EachComponent/Customization/RotateCustomization";
|
import RotateCustomization from "../EachComponent/Customization/RotateCustomization";
|
||||||
import SkewCustomization from "../EachComponent/Customization/SkewCustomization";
|
import SkewCustomization from "../EachComponent/Customization/SkewCustomization";
|
||||||
import ScaleObjects from "../EachComponent/Customization/ScaleObjects";
|
import ScaleObjects from "../EachComponent/Customization/ScaleObjects";
|
||||||
import ShadowCustomization from "../EachComponent/Customization/ShadowCustomization";
|
import ShadowCustomization from "../EachComponent/Customization/ShadowCustomization";
|
||||||
import AddImageIntoShape from "../EachComponent/Customization/AddImageIntoShape";
|
import AddImageIntoShape from "../EachComponent/Customization/AddImageIntoShape";
|
||||||
|
import ApplyColor from "../EachComponent/ApplyColor";
|
||||||
|
|
||||||
const CommonPanel = () => {
|
const CommonPanel = () => {
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext);
|
||||||
const activeObject = canvas?.getActiveObject();
|
const activeObject = canvas?.getActiveObject();
|
||||||
|
const activeObjectType = activeObject?.type;
|
||||||
|
const hasClipPath = !!activeObject?.clipPath;
|
||||||
const customClipPath = activeObject?.isClipPath;
|
const customClipPath = activeObject?.isClipPath;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<SelectObjectFromGroup />
|
<SelectObjectFromGroup />
|
||||||
|
|
||||||
|
{/* Apply fill and background color */}
|
||||||
|
{activeObjectType !== "image" && !hasClipPath && !customClipPath && (
|
||||||
|
<ApplyColor />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Apply stroke and stroke color */}
|
{/* Apply stroke and stroke color */}
|
||||||
{!customClipPath && (
|
{!customClipPath && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -37,9 +45,8 @@ const CommonPanel = () => {
|
||||||
|
|
||||||
{/* Controls for opacity, flip, and rotation */}
|
{/* Controls for opacity, flip, and rotation */}
|
||||||
<Card className="shadow-none border-0">
|
<Card className="shadow-none border-0">
|
||||||
<CollapsibleComponent text={"Opacity, Flip, Rotate Control"}>
|
<CollapsibleComponent text={"Flip, Rotate Control"}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<OpacityCustomization />
|
|
||||||
<FlipCustomization />
|
<FlipCustomization />
|
||||||
<RotateCustomization />
|
<RotateCustomization />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
import TextPanel from "./TextPanel";
|
import TextPanel from "./TextPanel";
|
||||||
|
import ColorPanel from "./ColorPanel";
|
||||||
|
|
||||||
const EditorPanel = () => {
|
const EditorPanel = () => {
|
||||||
const { selectedPanel } = useContext(CanvasContext);
|
const { selectedPanel } = useContext(CanvasContext);
|
||||||
|
|
@ -9,6 +10,8 @@ const EditorPanel = () => {
|
||||||
switch (selectedPanel) {
|
switch (selectedPanel) {
|
||||||
case "text":
|
case "text":
|
||||||
return <TextPanel />;
|
return <TextPanel />;
|
||||||
|
case "color":
|
||||||
|
return <ColorPanel />;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +20,7 @@ const EditorPanel = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedPanel !== "" && (
|
{selectedPanel !== "" && (
|
||||||
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-xl shadow-lg mx-4 my-4">
|
<div className="w-80 lg:h-[calc(90vh-100px)] xl:h-[calc(100vh-100px)] bg-background rounded-xl shadow-lg mx-4 my-auto">
|
||||||
{renderPanel()}
|
{renderPanel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
import { Button } from "../ui/Button";
|
import { Button } from "../ui/Button";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { ScrollArea } from "../ui/scroll-area";
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
import { useContext } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
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 CommonPanel from "./CommonPanel";
|
import CommonPanel from "./CommonPanel";
|
||||||
import TextCustomization from "../EachComponent/Customization/TextCustomization";
|
|
||||||
|
|
||||||
export default function TextPanel() {
|
export default function TextPanel() {
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas, setSelectedPanel } = useContext(CanvasContext);
|
||||||
const { setActiveObject } = useContext(ActiveObjectContext);
|
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
|
||||||
const activeObject = canvas?.getActiveObject();
|
|
||||||
const activeObjectType = activeObject?.type;
|
|
||||||
const hasClipPath = !!activeObject?.clipPath;
|
|
||||||
const customClipPath = activeObject?.isClipPath;
|
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeObject) {
|
||||||
|
setOpen(true);
|
||||||
|
} else {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}, [activeObject]);
|
||||||
const addText = () => {
|
const addText = () => {
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
const text = new fabric.IText("Editable Text", {
|
const text = new fabric.IText("Editable Text", {
|
||||||
|
|
@ -37,12 +41,16 @@ export default function TextPanel() {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center p-4 border-b">
|
<div className="flex justify-between items-center p-4 border-b">
|
||||||
<h2 className="text-lg font-semibold">Text</h2>
|
<h2 className="text-lg font-semibold">Text</h2>
|
||||||
<Button variant="ghost" size="icon">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSelectedPanel("")}
|
||||||
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="h-[calc(100vh-115px)] px-4 py-4">
|
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
|
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -77,15 +85,14 @@ export default function TextPanel() {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button>
|
||||||
{activeObject ? (
|
{!open ? (
|
||||||
<div className="space-y-4">
|
|
||||||
<CommonPanel />
|
|
||||||
<TextCustomization />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm font-semibold text-center">
|
<p className="text-sm font-semibold text-center">
|
||||||
No active object found
|
No active object found
|
||||||
</p>
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<CommonPanel />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,3 @@
|
||||||
import {
|
|
||||||
AlignCenter,
|
|
||||||
AlignLeft,
|
|
||||||
AlignRight,
|
|
||||||
Bold,
|
|
||||||
Italic,
|
|
||||||
Lock,
|
|
||||||
Underline,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Button } from "../ui/Button";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -15,87 +5,46 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../ui/Select";
|
} from "../ui/Select";
|
||||||
|
import TextCustomization from "../EachComponent/Customization/TextCustomization";
|
||||||
|
import LockObject from "../EachComponent/Customization/LockObject";
|
||||||
|
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
|
||||||
|
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
|
||||||
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
export function TopBar({ isVisible = false }) {
|
export function TopBar() {
|
||||||
if (!isVisible) return null;
|
const { selectedPanel } = useContext(CanvasContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-4 left-[40%] -translate-x-1/2 z-40">
|
<div>
|
||||||
<div className="bg-white rounded-[16px] shadow-sm px-4 py-2 flex items-center gap-2">
|
<ScrollArea
|
||||||
<Select defaultValue="Albert Sans">
|
className={`absolute top-2 lg:left-[20%] ${
|
||||||
<SelectTrigger className="w-[140px] h-9">
|
selectedPanel !== ""
|
||||||
<SelectValue />
|
? "lg:w-[600px] xl:w-[820px] xl:left-[40%]"
|
||||||
</SelectTrigger>
|
: "w-[70%] xl:left-[50%]"
|
||||||
<SelectContent>
|
} -translate-x-1/2 z-40 scrollbar-hide`}
|
||||||
<SelectItem value="Albert Sans">Albert Sans</SelectItem>
|
>
|
||||||
<SelectItem value="Arial">Arial</SelectItem>
|
<div className="bg-white rounded-[16px] shadow-sm px-4 py-2 flex items-center gap-2">
|
||||||
<SelectItem value="Helvetica">Helvetica</SelectItem>
|
<div>
|
||||||
</SelectContent>
|
<TextCustomization />
|
||||||
</Select>
|
</div>
|
||||||
|
<OpacityCustomization />
|
||||||
|
<div className="h-4 w-px bg-border mx-2" />
|
||||||
|
|
||||||
<div className="flex items-center border rounded-lg h-9">
|
<Select defaultValue="position">
|
||||||
<Button
|
<SelectTrigger className="w-[100px] h-9">
|
||||||
variant="ghost"
|
<SelectValue placeholder="Position" />
|
||||||
size="icon"
|
</SelectTrigger>
|
||||||
className="h-9 w-9 rounded-none rounded-l-lg"
|
<SelectContent>
|
||||||
>
|
<SelectItem value="position">Position</SelectItem>
|
||||||
-
|
<SelectItem value="front">Bring to Front</SelectItem>
|
||||||
</Button>
|
<SelectItem value="back">Send to Back</SelectItem>
|
||||||
<div className="px-3 py-1">12</div>
|
</SelectContent>
|
||||||
<Button
|
</Select>
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
<LockObject />
|
||||||
className="h-9 w-9 rounded-none rounded-r-lg"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
<div className="h-4 w-px bg-border mx-2" />
|
</ScrollArea>
|
||||||
|
|
||||||
<div className="flex items-center gap-0.5">
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<Bold className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<Italic className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<Underline className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-4 w-px bg-border mx-2" />
|
|
||||||
|
|
||||||
<div className="flex items-center gap-0.5">
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<AlignLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<AlignCenter className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<AlignRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-4 w-px bg-border mx-2" />
|
|
||||||
|
|
||||||
<Select defaultValue="position">
|
|
||||||
<SelectTrigger className="w-[100px] h-9">
|
|
||||||
<SelectValue placeholder="Position" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="position">Position</SelectItem>
|
|
||||||
<SelectItem value="front">Bring to Front</SelectItem>
|
|
||||||
<SelectItem value="back">Send to Back</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
||||||
<Lock className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/main.jsx
19
src/main.jsx
|
|
@ -1,13 +1,12 @@
|
||||||
import { StrictMode } from 'react'
|
import { createRoot } from "react-dom/client";
|
||||||
import { createRoot } from 'react-dom/client'
|
import "./index.css";
|
||||||
import './index.css'
|
import App from "./App.jsx";
|
||||||
import App from './App.jsx'
|
import CanvasContextProvider from "./components/Context/canvasContext/CanvasContextProvider";
|
||||||
import CanvasContextProvider from './components/Context/canvasContext/CanvasContextProvider'
|
import ColorContextProvider from "./components/Context/colorContext/ColorContextProvider";
|
||||||
import ColorContextProvider from './components/Context/colorContext/ColorContextProvider'
|
import ObjectProvider from "./components/Context/activeObject/ObjectProvider";
|
||||||
import { ObjectProvider } from './components/Context/activeObject/ObjectProvider'
|
import OpenContextProvider from "./components/Context/openContext/OpenContextProvider";
|
||||||
import OpenContextProvider from './components/Context/openContext/OpenContextProvider'
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
// <StrictMode>
|
// <StrictMode>
|
||||||
<CanvasContextProvider>
|
<CanvasContextProvider>
|
||||||
<ColorContextProvider>
|
<ColorContextProvider>
|
||||||
|
|
@ -19,4 +18,4 @@ createRoot(document.getElementById('root')).render(
|
||||||
</ColorContextProvider>
|
</ColorContextProvider>
|
||||||
</CanvasContextProvider>
|
</CanvasContextProvider>
|
||||||
// </StrictMode>,
|
// </StrictMode>,
|
||||||
)
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue