add konva js to project + create some components with ai

This commit is contained in:
hamid zarghami
2025-11-16 16:33:54 +03:30
parent c1c6b0ebf7
commit 1a0c869101
38 changed files with 1855 additions and 42 deletions
@@ -0,0 +1,67 @@
import Konva from "konva";
import type { EditorObject } from "../../store/editorStore";
import {
RectangleShape,
CircleShape,
TextShape,
LineShape,
ArrowShape,
ImageObject,
LinkShape,
VideoShape,
} from "../tools";
type ObjectRendererProps = {
obj: EditorObject;
isSelected: boolean;
transformerRef: React.RefObject<Konva.Transformer | null>;
layerRef: React.RefObject<Konva.Layer | null>;
onSelect: (id: string, node: Konva.Node) => void;
onUpdate: (id: string, updates: Partial<EditorObject>) => void;
draggable: boolean;
};
const ObjectRenderer = ({
obj,
isSelected,
transformerRef,
layerRef,
onSelect,
onUpdate,
draggable,
}: ObjectRendererProps) => {
const commonProps = {
obj,
isSelected,
transformerRef,
onSelect,
onUpdate,
draggable,
};
switch (obj.type) {
case "rectangle":
return <RectangleShape key={obj.id} {...commonProps} />;
case "circle":
return <CircleShape key={obj.id} {...commonProps} />;
case "text":
return <TextShape key={obj.id} {...commonProps} layerRef={layerRef} />;
case "line":
return <LineShape key={obj.id} {...commonProps} />;
case "arrow":
return <ArrowShape key={obj.id} {...commonProps} />;
case "image":
return <ImageObject key={obj.id} {...commonProps} />;
case "link":
return <LinkShape key={obj.id} {...commonProps} />;
case "video":
return <VideoShape key={obj.id} {...commonProps} />;
case "document":
return <ImageObject key={obj.id} {...commonProps} />;
default:
return null;
}
};
export default ObjectRenderer;
@@ -0,0 +1,75 @@
import type { EditorObject, ToolType } from "../../store/editorStore";
type DrawingParams = {
tool: ToolType;
startPos: { x: number; y: number };
pointerPos: { x: number; y: number };
tempObject: EditorObject | null;
};
export const createDrawingObject = ({
tool,
startPos,
pointerPos,
tempObject,
}: DrawingParams): EditorObject | null => {
const width = pointerPos.x - startPos.x;
const height = pointerPos.y - startPos.y;
if (tool === "rectangle") {
return {
id: tempObject?.id || `rect-${Date.now()}`,
type: "rectangle",
x: Math.min(startPos.x, pointerPos.x),
y: Math.min(startPos.y, pointerPos.y),
width: Math.abs(width),
height: Math.abs(height),
fill: "#3b82f6",
stroke: "#1e40af",
strokeWidth: 2,
};
}
if (tool === "circle") {
const radius = Math.sqrt(width * width + height * height);
return {
id: tempObject?.id || `circle-${Date.now()}`,
type: "circle",
x: startPos.x,
y: startPos.y,
width: radius * 2,
height: radius * 2,
fill: "#10b981",
stroke: "#059669",
strokeWidth: 2,
};
}
if (tool === "line") {
return {
id: tempObject?.id || `line-${Date.now()}`,
type: "line",
x: startPos.x,
y: startPos.y,
width: pointerPos.x,
height: pointerPos.y,
stroke: "#000000",
strokeWidth: 2,
};
}
if (tool === "arrow") {
return {
id: tempObject?.id || `arrow-${Date.now()}`,
type: "arrow",
x: startPos.x,
y: startPos.y,
width: pointerPos.x,
height: pointerPos.y,
stroke: "#000000",
strokeWidth: 2,
};
}
return null;
};
@@ -0,0 +1,97 @@
import { useState, useRef, useEffect } from "react";
import Konva from "konva";
import { useEditorStore, type EditorObject } from "../../store/editorStore";
import { createDrawingObject } from "./drawingUtils";
export const useDrawingHandlers = () => {
const [isDrawing, setIsDrawing] = useState(false);
const [startPos, setStartPos] = useState({ x: 0, y: 0 });
const [tempObject, setTempObject] = useState<EditorObject | null>(null);
const transformerRef = useRef<Konva.Transformer>(null);
const { tool, addObject, updateObject, setSelectedObjectId } = useEditorStore();
useEffect(() => {
setTempObject(null);
setIsDrawing(false);
}, [tool]);
const handleStageMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
const stage = e.target.getStage();
if (!stage) return;
const pointerPos = stage.getPointerPosition();
if (!pointerPos) return;
if (tool === "select") {
const clickedOnEmpty = e.target === stage;
if (clickedOnEmpty) {
setSelectedObjectId(null);
if (transformerRef.current) {
transformerRef.current.nodes([]);
}
}
return;
}
setIsDrawing(true);
setStartPos(pointerPos);
if (tool === "text") {
const newText: EditorObject = {
id: `text-${Date.now()}`,
type: "text",
x: pointerPos.x,
y: pointerPos.y,
text: "متن جدید",
fontSize: 24,
fill: "#000000",
};
addObject(newText);
setSelectedObjectId(newText.id);
}
};
const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
if (!isDrawing || tool === "select" || tool === "text") return;
const stage = e.target.getStage();
if (!stage) return;
const pointerPos = stage.getPointerPosition();
if (!pointerPos) return;
const newObject = createDrawingObject({ tool, startPos, pointerPos, tempObject });
if (!newObject) return;
if (!tempObject) {
addObject(newObject);
setTempObject(newObject);
} else {
const updates: Partial<EditorObject> =
tool === "circle"
? { width: newObject.width, height: newObject.height }
: {
x: newObject.x,
y: newObject.y,
width: newObject.width,
height: newObject.height,
};
updateObject(tempObject.id, updates);
}
};
const handleStageMouseUp = () => {
setIsDrawing(false);
setTempObject(null);
};
return {
isDrawing,
transformerRef,
handleStageMouseDown,
handleStageMouseMove,
handleStageMouseUp,
};
};
@@ -0,0 +1,34 @@
import { useState, useEffect } from "react";
const A4_WIDTH = 794;
const A4_HEIGHT = 1123;
const SCALE = 0.8;
export const useStageSize = () => {
const [stageSize, setStageSize] = useState({
width: A4_WIDTH * SCALE,
height: A4_HEIGHT * SCALE,
});
useEffect(() => {
const handleResize = () => {
const maxWidth = window.innerWidth - 400;
const maxHeight = window.innerHeight - 100;
const scaleX = maxWidth / A4_WIDTH;
const scaleY = maxHeight / A4_HEIGHT;
const newScale = Math.min(scaleX, scaleY, SCALE);
setStageSize({
width: A4_WIDTH * newScale,
height: A4_HEIGHT * newScale,
});
};
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return stageSize;
};