add konva js to project + create some components with ai
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user