From 3f18ac62fabe51e8829f964c17cb3c0853a23002 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 18 Nov 2025 15:31:09 +0330 Subject: [PATCH] Create shapes square + circle + .... --- src/index.css | 4 + .../components/canvas/ObjectRenderer.tsx | 11 +++ .../editor/components/canvas/drawingUtils.ts | 3 + .../instructions/RectangleInstruction.tsx | 91 ++++++++++--------- .../editor/components/tools/AbstractShape.tsx | 69 ++++++++++++++ .../editor/components/tools/TriangleShape.tsx | 67 ++++++++++++++ src/pages/editor/components/tools/index.ts | 2 + src/pages/editor/store/editorStore.ts | 27 +++++- src/pages/editor/store/shapeStore.ts | 15 +++ 9 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 src/pages/editor/components/tools/AbstractShape.tsx create mode 100644 src/pages/editor/components/tools/TriangleShape.tsx create mode 100644 src/pages/editor/store/shapeStore.ts 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" }, +]; -
-
- square -
- مربع -
+const RectangleInstruction: FC = () => { + const { activeShape, setActiveShape } = useShapeStore(); + + return ( +
+

اشکال

+ +
+ {shapeOptions.map((shape) => { + const isActive = activeShape === shape.id; + return ( + + ); + })}
-
- circle -
- دایره -
-
-
- triangle -
- مثلث -
-
-
- star -
- انتزاعی -
+ +
+
استفاده برای ماسک
+
- -
-
- استفاده برای ماسک -
- - -
-
-); + ); +}; 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 }), +})); + +