diff --git a/src/index.css b/src/index.css
index c27ba1b..79228d0 100644
--- a/src/index.css
+++ b/src/index.css
@@ -85,3 +85,7 @@ html {
.dltr {
direction: ltr;
}
+
+.filterBlack {
+ filter: brightness(0) invert(0);
+}
diff --git a/src/pages/editor/components/canvas/ObjectRenderer.tsx b/src/pages/editor/components/canvas/ObjectRenderer.tsx
index c86bc35..9441d8d 100644
--- a/src/pages/editor/components/canvas/ObjectRenderer.tsx
+++ b/src/pages/editor/components/canvas/ObjectRenderer.tsx
@@ -3,6 +3,8 @@ import type { EditorObject } from "../../store/editorStore";
import {
RectangleShape,
CircleShape,
+ TriangleShape,
+ AbstractShape,
TextShape,
LineShape,
ArrowShape,
@@ -41,6 +43,15 @@ const ObjectRenderer = ({
switch (obj.type) {
case "rectangle":
+ if (obj.shapeType === "circle") {
+ return ;
+ }
+ if (obj.shapeType === "triangle") {
+ return ;
+ }
+ if (obj.shapeType === "abstract") {
+ return ;
+ }
return ;
case "circle":
return ;
diff --git a/src/pages/editor/components/canvas/drawingUtils.ts b/src/pages/editor/components/canvas/drawingUtils.ts
index 0a5ed2d..9dede29 100644
--- a/src/pages/editor/components/canvas/drawingUtils.ts
+++ b/src/pages/editor/components/canvas/drawingUtils.ts
@@ -1,4 +1,5 @@
import type { EditorObject, ToolType } from "../../store/editorStore";
+import { useShapeStore } from "../../store/shapeStore";
type DrawingParams = {
tool: ToolType;
@@ -17,6 +18,7 @@ export const createDrawingObject = ({
const height = pointerPos.y - startPos.y;
if (tool === "rectangle") {
+ const activeShape = useShapeStore.getState().activeShape;
return {
id: tempObject?.id || `rect-${Date.now()}`,
type: "rectangle",
@@ -27,6 +29,7 @@ export const createDrawingObject = ({
fill: "#3b82f6",
stroke: "#1e40af",
strokeWidth: 2,
+ shapeType: activeShape,
};
}
diff --git a/src/pages/editor/components/sidebar/instructions/RectangleInstruction.tsx b/src/pages/editor/components/sidebar/instructions/RectangleInstruction.tsx
index fda7ff7..146006a 100644
--- a/src/pages/editor/components/sidebar/instructions/RectangleInstruction.tsx
+++ b/src/pages/editor/components/sidebar/instructions/RectangleInstruction.tsx
@@ -1,53 +1,54 @@
-import { type FC } from 'react'
-import SquareIcon from '@/assets/icons/square.svg'
-import CircleIcon from '@/assets/icons/circle.svg'
-import TriangleIcon from '@/assets/icons/triangle.svg'
-import StarIcon from '@/assets/icons/Star.svg'
-import { Switch } from '@/components/ui/switch'
+import { type FC } from "react";
+import SquareIcon from "@/assets/icons/square.svg";
+import CircleIcon from "@/assets/icons/circle.svg";
+import TriangleIcon from "@/assets/icons/triangle.svg";
+import StarIcon from "@/assets/icons/Star.svg";
+import { Switch } from "@/components/ui/switch";
+import { clx } from "@/helpers/utils";
+import { useShapeStore, type ShapeType } from "@/pages/editor/store/shapeStore";
-const RectangleInstruction: FC = () => (
-
-
- اشکال
-
+const shapeOptions: Array<{ id: ShapeType; label: string; icon: string; alt: string }> = [
+ { id: "square", label: "مربع", icon: SquareIcon, alt: "square" },
+ { id: "circle", label: "دایره", icon: CircleIcon, alt: "circle" },
+ { id: "triangle", label: "مثلث", icon: TriangleIcon, alt: "triangle" },
+ { id: "abstract", label: "انتزاعی", icon: StarIcon, alt: "abstract" },
+];
-
-
-

-
- مربع
-
+const RectangleInstruction: FC = () => {
+ const { activeShape, setActiveShape } = useShapeStore();
+
+ return (
+
+
اشکال
+
+
+ {shapeOptions.map((shape) => {
+ const isActive = activeShape === shape.id;
+ return (
+
+ );
+ })}
-
-

-
- دایره
-
-
-
-

-
- مثلث
-
-
-
-

-
- انتزاعی
-
+
+
-
-
-
- استفاده برای ماسک
-
-
-
-
-
-);
+ );
+};
export default RectangleInstruction;
diff --git a/src/pages/editor/components/tools/AbstractShape.tsx b/src/pages/editor/components/tools/AbstractShape.tsx
new file mode 100644
index 0000000..83ff9be
--- /dev/null
+++ b/src/pages/editor/components/tools/AbstractShape.tsx
@@ -0,0 +1,69 @@
+import { useEffect, useRef } from "react";
+import { Star } from "react-konva";
+import Konva from "konva";
+import type { ShapeProps } from "./types";
+
+const AbstractShape = ({ obj, isSelected, transformerRef, onSelect, onUpdate, draggable }: ShapeProps) => {
+ const shapeRef = useRef
(null);
+
+ useEffect(() => {
+ if (isSelected && shapeRef.current && transformerRef.current) {
+ transformerRef.current.nodes([shapeRef.current]);
+ transformerRef.current.getLayer()?.batchDraw();
+ }
+ }, [isSelected, transformerRef]);
+
+ const radius = Math.min((obj.width || 100) / 2, (obj.height || 100) / 2);
+ const innerRadius = radius * 0.5;
+
+ return (
+ {
+ if (shapeRef.current) {
+ onSelect(obj.id, shapeRef.current);
+ }
+ }}
+ onDragEnd={(e) => {
+ const node = e.target;
+ onUpdate(obj.id, {
+ x: node.x() - (obj.width || 100) / 2,
+ y: node.y() - (obj.height || 100) / 2,
+ });
+ }}
+ onTransformEnd={() => {
+ const node = shapeRef.current;
+ if (!node) return;
+
+ const scaleX = node.scaleX();
+ const scaleY = node.scaleY();
+
+ node.scaleX(1);
+ node.scaleY(1);
+
+ const newRadius = Math.max(5, radius * Math.min(scaleX, scaleY));
+ const newSize = newRadius * 2;
+ onUpdate(obj.id, {
+ x: node.x() - newSize / 2,
+ y: node.y() - newSize / 2,
+ rotation: node.rotation(),
+ width: newSize,
+ height: newSize,
+ });
+ }}
+ />
+ );
+};
+
+export default AbstractShape;
+
diff --git a/src/pages/editor/components/tools/TriangleShape.tsx b/src/pages/editor/components/tools/TriangleShape.tsx
new file mode 100644
index 0000000..540507a
--- /dev/null
+++ b/src/pages/editor/components/tools/TriangleShape.tsx
@@ -0,0 +1,67 @@
+import { useEffect, useRef } from "react";
+import { RegularPolygon } from "react-konva";
+import Konva from "konva";
+import type { ShapeProps } from "./types";
+
+const TriangleShape = ({ obj, isSelected, transformerRef, onSelect, onUpdate, draggable }: ShapeProps) => {
+ const shapeRef = useRef(null);
+
+ useEffect(() => {
+ if (isSelected && shapeRef.current && transformerRef.current) {
+ transformerRef.current.nodes([shapeRef.current]);
+ transformerRef.current.getLayer()?.batchDraw();
+ }
+ }, [isSelected, transformerRef]);
+
+ const radius = Math.min((obj.width || 100) / 2, (obj.height || 100) / 2);
+
+ return (
+ {
+ if (shapeRef.current) {
+ onSelect(obj.id, shapeRef.current);
+ }
+ }}
+ onDragEnd={(e) => {
+ const node = e.target;
+ onUpdate(obj.id, {
+ x: node.x() - (obj.width || 100) / 2,
+ y: node.y() - (obj.height || 100) / 2,
+ });
+ }}
+ onTransformEnd={() => {
+ const node = shapeRef.current;
+ if (!node) return;
+
+ const scaleX = node.scaleX();
+ const scaleY = node.scaleY();
+
+ node.scaleX(1);
+ node.scaleY(1);
+
+ const newRadius = Math.max(5, radius * Math.min(scaleX, scaleY));
+ const newSize = newRadius * 2;
+ onUpdate(obj.id, {
+ x: node.x() - newSize / 2,
+ y: node.y() - newSize / 2,
+ rotation: node.rotation(),
+ width: newSize,
+ height: newSize,
+ });
+ }}
+ />
+ );
+};
+
+export default TriangleShape;
+
diff --git a/src/pages/editor/components/tools/index.ts b/src/pages/editor/components/tools/index.ts
index 6006fc7..fb6dc1c 100644
--- a/src/pages/editor/components/tools/index.ts
+++ b/src/pages/editor/components/tools/index.ts
@@ -1,5 +1,7 @@
export { default as RectangleShape } from "./RectangleShape";
export { default as CircleShape } from "./CircleShape";
+export { default as TriangleShape } from "./TriangleShape";
+export { default as AbstractShape } from "./AbstractShape";
export { default as TextShape } from "./TextShape";
export { default as LineShape } from "./LineShape";
export { default as ArrowShape } from "./ArrowShape";
diff --git a/src/pages/editor/store/editorStore.ts b/src/pages/editor/store/editorStore.ts
index 53c1853..e92d33b 100644
--- a/src/pages/editor/store/editorStore.ts
+++ b/src/pages/editor/store/editorStore.ts
@@ -1,7 +1,20 @@
import { create } from "zustand";
import Konva from "konva";
+import type { ShapeType } from "./shapeStore";
-export type ToolType = "select" | "rectangle" | "circle" | "text" | "image" | "line" | "arrow" | "sticker" | "document" | "video" | "link" | "grid";
+export type ToolType =
+ | "select"
+ | "rectangle"
+ | "circle"
+ | "text"
+ | "image"
+ | "line"
+ | "arrow"
+ | "sticker"
+ | "document"
+ | "video"
+ | "link"
+ | "grid";
export type EditorObject = {
id: string;
@@ -21,6 +34,7 @@ export type EditorObject = {
rotation?: number;
scaleX?: number;
scaleY?: number;
+ shapeType?: ShapeType;
};
type EditorStoreType = {
@@ -42,15 +56,19 @@ export const useEditorStore = create((set) => ({
tool: "select",
setTool: (tool) => set({ tool }),
objects: [],
- addObject: (object) => set((state) => ({ objects: [...state.objects, object] })),
+ addObject: (object) =>
+ set((state) => ({ objects: [...state.objects, object] })),
updateObject: (id, updates) =>
set((state) => ({
- objects: state.objects.map((obj) => (obj.id === id ? { ...obj, ...updates } : obj)),
+ objects: state.objects.map((obj) =>
+ obj.id === id ? { ...obj, ...updates } : obj
+ ),
})),
deleteObject: (id) =>
set((state) => ({
objects: state.objects.filter((obj) => obj.id !== id),
- selectedObjectId: state.selectedObjectId === id ? null : state.selectedObjectId,
+ selectedObjectId:
+ state.selectedObjectId === id ? null : state.selectedObjectId,
})),
selectedObjectId: null,
setSelectedObjectId: (id) => set({ selectedObjectId: id }),
@@ -59,4 +77,3 @@ export const useEditorStore = create((set) => ({
layerRef: null,
setLayerRef: (ref) => set({ layerRef: ref }),
}));
-
diff --git a/src/pages/editor/store/shapeStore.ts b/src/pages/editor/store/shapeStore.ts
new file mode 100644
index 0000000..12ce1c2
--- /dev/null
+++ b/src/pages/editor/store/shapeStore.ts
@@ -0,0 +1,15 @@
+import { create } from "zustand";
+
+export type ShapeType = "square" | "circle" | "triangle" | "abstract";
+
+type ShapeStore = {
+ activeShape: ShapeType;
+ setActiveShape: (shape: ShapeType) => void;
+};
+
+export const useShapeStore = create((set) => ({
+ activeShape: "square",
+ setActiveShape: (shape) => set({ activeShape: shape }),
+}));
+
+