@@ -11,9 +11,11 @@ import { useTransformHandlers } from "./canvas/useTransformHandlers";
|
|||||||
import { useObjectHandlers } from "./canvas/useObjectHandlers";
|
import { useObjectHandlers } from "./canvas/useObjectHandlers";
|
||||||
import { useMaskGroupState } from "./canvas/useMaskGroupState";
|
import { useMaskGroupState } from "./canvas/useMaskGroupState";
|
||||||
import { useCustomShapeDrawing } from "./canvas/useCustomShapeDrawing";
|
import { useCustomShapeDrawing } from "./canvas/useCustomShapeDrawing";
|
||||||
|
import { usePencilDrawing } from "./canvas/usePencilDrawing";
|
||||||
import ObjectsLayer from "./canvas/ObjectsLayer";
|
import ObjectsLayer from "./canvas/ObjectsLayer";
|
||||||
import GuidesLayer from "./canvas/GuidesLayer";
|
import GuidesLayer from "./canvas/GuidesLayer";
|
||||||
import CustomShapeDrawingLayer from "./canvas/CustomShapeDrawingLayer";
|
import CustomShapeDrawingLayer from "./canvas/CustomShapeDrawingLayer";
|
||||||
|
import PencilDrawingLayer from "./canvas/PencilDrawingLayer";
|
||||||
import {
|
import {
|
||||||
clearSmartGuides,
|
clearSmartGuides,
|
||||||
drawSmartGuides,
|
drawSmartGuides,
|
||||||
@@ -101,6 +103,12 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
const { transformerRef, handleStageMouseDown, handleStageMouseMove, handleStageMouseUp } = useDrawingHandlers();
|
const { transformerRef, handleStageMouseDown, handleStageMouseMove, handleStageMouseUp } = useDrawingHandlers();
|
||||||
const { drawingState, handleStageClick: handleCustomShapeClick, handleStageMouseMove: handleCustomShapeMouseMove } =
|
const { drawingState, handleStageClick: handleCustomShapeClick, handleStageMouseMove: handleCustomShapeMouseMove } =
|
||||||
useCustomShapeDrawing();
|
useCustomShapeDrawing();
|
||||||
|
const {
|
||||||
|
drawingState: pencilDrawingState,
|
||||||
|
handleStagePointerDown: handlePencilPointerDown,
|
||||||
|
handleStagePointerMove: handlePencilPointerMove,
|
||||||
|
handleStagePointerUp: handlePencilPointerUp,
|
||||||
|
} = usePencilDrawing(stageRef);
|
||||||
useKeyboardMovement();
|
useKeyboardMovement();
|
||||||
|
|
||||||
const { handleSelect, handleCellClick, handleCellDblClick } = useSelectionHandlers();
|
const { handleSelect, handleCellClick, handleCellDblClick } = useSelectionHandlers();
|
||||||
@@ -372,14 +380,32 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
if (e.target?.name() !== "guide-line") {
|
if (e.target?.name() !== "guide-line") {
|
||||||
setSelectedGuideId(null);
|
setSelectedGuideId(null);
|
||||||
}
|
}
|
||||||
|
if (tool === "pencil") return;
|
||||||
handleStageMouseDown(e);
|
handleStageMouseDown(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStagePointerDownWithGuideReset = (e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
if (e.target?.name() !== "guide-line") {
|
||||||
|
setSelectedGuideId(null);
|
||||||
|
}
|
||||||
|
if (tool === "pencil") {
|
||||||
|
handlePencilPointerDown(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCombinedMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
const handleCombinedMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||||
handleStageMouseMove(e);
|
handleStageMouseMove(e);
|
||||||
handleCustomShapeMouseMove(e);
|
handleCustomShapeMouseMove(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCombinedPointerMove = (e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
handlePencilPointerMove(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCombinedPointerUp = (e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
handlePencilPointerUp(e);
|
||||||
|
};
|
||||||
|
|
||||||
const scaledW = stageSize.width * finalScale;
|
const scaledW = stageSize.width * finalScale;
|
||||||
const scaledH = stageSize.height * finalScale;
|
const scaledH = stageSize.height * finalScale;
|
||||||
|
|
||||||
@@ -511,10 +537,13 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
height={stageSize.height * finalScale}
|
height={stageSize.height * finalScale}
|
||||||
scaleX={finalScale}
|
scaleX={finalScale}
|
||||||
scaleY={finalScale}
|
scaleY={finalScale}
|
||||||
style={{ cursor: tool === "custom-shape" ? "crosshair" : undefined }}
|
style={{ cursor: tool === "custom-shape" || tool === "pencil" ? "crosshair" : undefined }}
|
||||||
onMouseDown={handleStageMouseDownWithGuideReset}
|
onMouseDown={handleStageMouseDownWithGuideReset}
|
||||||
onMouseMove={handleCombinedMouseMove}
|
onMouseMove={handleCombinedMouseMove}
|
||||||
onMouseUp={handleStageMouseUp}
|
onMouseUp={handleStageMouseUp}
|
||||||
|
onPointerDown={handleStagePointerDownWithGuideReset}
|
||||||
|
onPointerMove={handleCombinedPointerMove}
|
||||||
|
onPointerUp={handleCombinedPointerUp}
|
||||||
onClick={handleCustomShapeClick}
|
onClick={handleCustomShapeClick}
|
||||||
onDragMove={handleDragMove}
|
onDragMove={handleDragMove}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
@@ -575,6 +604,7 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
/>
|
/>
|
||||||
<Layer ref={smartGuidesLayerRef} listening={false} visible={false} />
|
<Layer ref={smartGuidesLayerRef} listening={false} visible={false} />
|
||||||
<CustomShapeDrawingLayer drawingState={drawingState} />
|
<CustomShapeDrawingLayer drawingState={drawingState} />
|
||||||
|
<PencilDrawingLayer drawingState={pencilDrawingState} />
|
||||||
</Stage>
|
</Stage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
MouseSquare,
|
MouseSquare,
|
||||||
Sticker,
|
Sticker,
|
||||||
Polygon,
|
Polygon,
|
||||||
|
Brush,
|
||||||
} from 'iconsax-react'
|
} from 'iconsax-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useEditorStore } from '../store/editorStore'
|
import { useEditorStore } from '../store/editorStore'
|
||||||
@@ -66,6 +67,8 @@ const LayersList = () => {
|
|||||||
return <Sticker size={20} color="black" />
|
return <Sticker size={20} color="black" />
|
||||||
case 'custom-shape':
|
case 'custom-shape':
|
||||||
return <Polygon size={20} color="black" />
|
return <Polygon size={20} color="black" />
|
||||||
|
case 'pencil':
|
||||||
|
return <Brush size={20} color="black" />
|
||||||
default:
|
default:
|
||||||
return <Shapes size={20} color="black" />
|
return <Shapes size={20} color="black" />
|
||||||
}
|
}
|
||||||
@@ -117,6 +120,8 @@ const LayersList = () => {
|
|||||||
return 'استیکر'
|
return 'استیکر'
|
||||||
case 'custom-shape':
|
case 'custom-shape':
|
||||||
return 'شکل سفارشی'
|
return 'شکل سفارشی'
|
||||||
|
case 'pencil':
|
||||||
|
return 'نقاشی'
|
||||||
default:
|
default:
|
||||||
return 'لایه'
|
return 'لایه'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
VideoShape,
|
VideoShape,
|
||||||
AudioShape,
|
AudioShape,
|
||||||
CustomShape,
|
CustomShape,
|
||||||
|
PencilShape,
|
||||||
} from "../tools";
|
} from "../tools";
|
||||||
import EditorTable from "@/components/EditorTable";
|
import EditorTable from "@/components/EditorTable";
|
||||||
|
|
||||||
@@ -278,6 +279,9 @@ const ObjectRenderer = ({
|
|||||||
case "custom-shape":
|
case "custom-shape":
|
||||||
shapeElement = <CustomShape key={obj.id} {...commonProps} />;
|
shapeElement = <CustomShape key={obj.id} {...commonProps} />;
|
||||||
break;
|
break;
|
||||||
|
case "pencil":
|
||||||
|
shapeElement = <PencilShape key={obj.id} {...commonProps} />;
|
||||||
|
break;
|
||||||
case "group":
|
case "group":
|
||||||
// Render group as a simple selection box
|
// Render group as a simple selection box
|
||||||
// Objects inside group are rendered separately in EditorCanvas
|
// Objects inside group are rendered separately in EditorCanvas
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const ObjectsLayer = ({
|
|||||||
stageHeight,
|
stageHeight,
|
||||||
}: ObjectsLayerProps) => {
|
}: ObjectsLayerProps) => {
|
||||||
return (
|
return (
|
||||||
<Layer ref={layerRef}>
|
<Layer ref={layerRef} listening={tool !== "pencil"}>
|
||||||
{/* Render group objects first */}
|
{/* Render group objects first */}
|
||||||
{objects
|
{objects
|
||||||
.filter((obj) => obj.type === "group")
|
.filter((obj) => obj.type === "group")
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Layer, Line } from "react-konva";
|
||||||
|
import type { PencilDrawingState } from "./usePencilDrawing";
|
||||||
|
|
||||||
|
type PencilDrawingLayerProps = {
|
||||||
|
drawingState: PencilDrawingState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PencilDrawingLayer = ({ drawingState }: PencilDrawingLayerProps) => {
|
||||||
|
const { isDrawing, points, stroke, strokeWidth } = drawingState;
|
||||||
|
|
||||||
|
if (!isDrawing || points.length < 1) return null;
|
||||||
|
|
||||||
|
const linePoints = points.flatMap((p) => [p.x, p.y]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layer listening={false}>
|
||||||
|
<Line
|
||||||
|
points={linePoints}
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
tension={0.5}
|
||||||
|
lineCap="round"
|
||||||
|
lineJoin="round"
|
||||||
|
perfectDrawEnabled={false}
|
||||||
|
/>
|
||||||
|
</Layer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PencilDrawingLayer;
|
||||||
@@ -27,8 +27,8 @@ export const useDrawingHandlers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleStageMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
const handleStageMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||||
// custom-shape drawing is fully managed by useCustomShapeDrawing
|
// custom-shape and pencil drawing are managed by dedicated hooks
|
||||||
if (tool === "custom-shape") return;
|
if (tool === "custom-shape" || tool === "pencil") return;
|
||||||
|
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (!stage) return;
|
if (!stage) return;
|
||||||
@@ -98,7 +98,7 @@ export const useDrawingHandlers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||||
if (!isDrawing || tool === "select" || tool === "text" || tool === "custom-shape") return;
|
if (!isDrawing || tool === "select" || tool === "text" || tool === "custom-shape" || tool === "pencil") return;
|
||||||
|
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (!stage) return;
|
if (!stage) return;
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
import Konva from "konva";
|
||||||
|
import { useEditorStore } from "@/pages/editor/store/editorStore";
|
||||||
|
import type { EditorObject } from "@/pages/editor/store/editorStore";
|
||||||
|
import { usePencilDefaultsStore } from "@/pages/editor/store/pencilDefaultsStore";
|
||||||
|
|
||||||
|
const MIN_POINT_DISTANCE = 1.5;
|
||||||
|
const MIN_POINTS = 2;
|
||||||
|
const MIN_DRAW_DISTANCE = 4;
|
||||||
|
const DEFAULT_TENSION = 0.5;
|
||||||
|
|
||||||
|
export type DrawPoint = { x: number; y: number };
|
||||||
|
|
||||||
|
export type PencilDrawingState = {
|
||||||
|
isDrawing: boolean;
|
||||||
|
points: DrawPoint[];
|
||||||
|
stroke: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_STATE: PencilDrawingState = {
|
||||||
|
isDrawing: false,
|
||||||
|
points: [],
|
||||||
|
stroke: "#000000",
|
||||||
|
strokeWidth: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCanvasPos = (stage: Konva.Stage): DrawPoint | null => {
|
||||||
|
const pos = stage.getPointerPosition();
|
||||||
|
if (!pos) return null;
|
||||||
|
return {
|
||||||
|
x: pos.x / stage.scaleX(),
|
||||||
|
y: pos.y / stage.scaleY(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStagePosFromClient = (
|
||||||
|
stage: Konva.Stage,
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
): DrawPoint | null => {
|
||||||
|
const container = stage.container();
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const scaleX = stage.scaleX();
|
||||||
|
const scaleY = stage.scaleY();
|
||||||
|
return {
|
||||||
|
x: (clientX - rect.left) / scaleX,
|
||||||
|
y: (clientY - rect.top) / scaleY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFarEnough = (a: DrawPoint, b: DrawPoint, minDistance: number): boolean => {
|
||||||
|
const dx = a.x - b.x;
|
||||||
|
const dy = a.y - b.y;
|
||||||
|
return dx * dx + dy * dy >= minDistance * minDistance;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPathLength = (points: DrawPoint[]): number => {
|
||||||
|
let length = 0;
|
||||||
|
for (let i = 1; i < points.length; i += 1) {
|
||||||
|
length += Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y);
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildPencilObject = (
|
||||||
|
points: DrawPoint[],
|
||||||
|
stroke: string,
|
||||||
|
strokeWidth: number,
|
||||||
|
): EditorObject => {
|
||||||
|
const xs = points.map((p) => p.x);
|
||||||
|
const ys = points.map((p) => p.y);
|
||||||
|
const minX = Math.min(...xs);
|
||||||
|
const minY = Math.min(...ys);
|
||||||
|
const maxX = Math.max(...xs);
|
||||||
|
const maxY = Math.max(...ys);
|
||||||
|
|
||||||
|
const normalizedPoints = points.flatMap((p) => [p.x - minX, p.y - minY]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `pencil-${Date.now()}`,
|
||||||
|
type: "pencil",
|
||||||
|
x: minX,
|
||||||
|
y: minY,
|
||||||
|
width: Math.max(1, maxX - minX),
|
||||||
|
height: Math.max(1, maxY - minY),
|
||||||
|
points: normalizedPoints,
|
||||||
|
closed: false,
|
||||||
|
stroke,
|
||||||
|
strokeWidth,
|
||||||
|
tension: DEFAULT_TENSION,
|
||||||
|
opacity: 100,
|
||||||
|
rotation: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePencilDrawing = (stageRef: React.RefObject<Konva.Stage | null>) => {
|
||||||
|
const pointsRef = useRef<DrawPoint[]>([]);
|
||||||
|
const isDrawingRef = useRef(false);
|
||||||
|
const pointerIdRef = useRef<number | null>(null);
|
||||||
|
const strokeRef = useRef("#000000");
|
||||||
|
const strokeWidthRef = useRef(3);
|
||||||
|
|
||||||
|
const [drawingState, setDrawingState] = useState<PencilDrawingState>(INITIAL_STATE);
|
||||||
|
|
||||||
|
const { tool, addObject } = useEditorStore();
|
||||||
|
const { defaults } = usePencilDefaultsStore();
|
||||||
|
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
pointsRef.current = [];
|
||||||
|
isDrawingRef.current = false;
|
||||||
|
pointerIdRef.current = null;
|
||||||
|
setDrawingState(INITIAL_STATE);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tool !== "pencil") {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}, [tool, reset]);
|
||||||
|
|
||||||
|
const releasePointerCapture = useCallback(() => {
|
||||||
|
const stage = stageRef.current;
|
||||||
|
const pointerId = pointerIdRef.current;
|
||||||
|
if (!stage || pointerId === null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
stage.container().releasePointerCapture(pointerId);
|
||||||
|
} catch {
|
||||||
|
// pointer may already be released
|
||||||
|
}
|
||||||
|
pointerIdRef.current = null;
|
||||||
|
}, [stageRef]);
|
||||||
|
|
||||||
|
const commitStroke = useCallback(() => {
|
||||||
|
const points = pointsRef.current;
|
||||||
|
releasePointerCapture();
|
||||||
|
|
||||||
|
if (points.length < MIN_POINTS || getPathLength(points) < MIN_DRAW_DISTANCE) {
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = buildPencilObject(points, strokeRef.current, strokeWidthRef.current);
|
||||||
|
useEditorStore.getState().commitObjectHistoryBeforeChange();
|
||||||
|
addObject(obj);
|
||||||
|
reset();
|
||||||
|
}, [addObject, releasePointerCapture, reset]);
|
||||||
|
|
||||||
|
const appendPoint = useCallback((pos: DrawPoint) => {
|
||||||
|
const points = pointsRef.current;
|
||||||
|
const last = points[points.length - 1];
|
||||||
|
if (last && !isFarEnough(last, pos, MIN_POINT_DISTANCE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointsRef.current = [...points, pos];
|
||||||
|
setDrawingState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
points: pointsRef.current,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const startDrawing = useCallback(
|
||||||
|
(pos: DrawPoint, pointerId: number) => {
|
||||||
|
strokeRef.current = defaults.stroke;
|
||||||
|
strokeWidthRef.current = defaults.strokeWidth;
|
||||||
|
isDrawingRef.current = true;
|
||||||
|
pointerIdRef.current = pointerId;
|
||||||
|
pointsRef.current = [pos];
|
||||||
|
|
||||||
|
setDrawingState({
|
||||||
|
isDrawing: true,
|
||||||
|
points: [pos],
|
||||||
|
stroke: defaults.stroke,
|
||||||
|
strokeWidth: defaults.strokeWidth,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[defaults.stroke, defaults.strokeWidth],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleStagePointerDown = useCallback(
|
||||||
|
(e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
if (tool !== "pencil") return;
|
||||||
|
if (e.evt.button !== 0) return;
|
||||||
|
|
||||||
|
e.evt.preventDefault();
|
||||||
|
e.cancelBubble = true;
|
||||||
|
|
||||||
|
const stage = stageRef.current ?? e.target.getStage();
|
||||||
|
if (!stage) return;
|
||||||
|
|
||||||
|
const pos = getCanvasPos(stage);
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
stage.container().setPointerCapture(e.evt.pointerId);
|
||||||
|
} catch {
|
||||||
|
// ignore if capture is unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
startDrawing(pos, e.evt.pointerId);
|
||||||
|
},
|
||||||
|
[tool, stageRef, startDrawing],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleStagePointerMove = useCallback(
|
||||||
|
(e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
if (tool !== "pencil" || !isDrawingRef.current) return;
|
||||||
|
if (pointerIdRef.current !== null && e.evt.pointerId !== pointerIdRef.current) return;
|
||||||
|
|
||||||
|
const stage = stageRef.current ?? e.target.getStage();
|
||||||
|
if (!stage) return;
|
||||||
|
|
||||||
|
const pos = getCanvasPos(stage);
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
appendPoint(pos);
|
||||||
|
},
|
||||||
|
[tool, appendPoint, stageRef],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleStagePointerUp = useCallback(
|
||||||
|
(e: Konva.KonvaEventObject<PointerEvent>) => {
|
||||||
|
if (tool !== "pencil" || !isDrawingRef.current) return;
|
||||||
|
if (pointerIdRef.current !== null && e.evt.pointerId !== pointerIdRef.current) return;
|
||||||
|
|
||||||
|
e.evt.preventDefault();
|
||||||
|
e.cancelBubble = true;
|
||||||
|
commitStroke();
|
||||||
|
},
|
||||||
|
[tool, commitStroke],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Keep drawing smooth when pointer leaves the canvas while still pressed
|
||||||
|
useEffect(() => {
|
||||||
|
if (tool !== "pencil") return;
|
||||||
|
|
||||||
|
const handleWindowPointerMove = (e: PointerEvent) => {
|
||||||
|
if (!isDrawingRef.current) return;
|
||||||
|
if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current) return;
|
||||||
|
|
||||||
|
const stage = stageRef.current;
|
||||||
|
if (!stage) return;
|
||||||
|
|
||||||
|
const pos = getStagePosFromClient(stage, e.clientX, e.clientY);
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
appendPoint(pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWindowPointerUp = (e: PointerEvent) => {
|
||||||
|
if (!isDrawingRef.current) return;
|
||||||
|
if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current) return;
|
||||||
|
|
||||||
|
commitStroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("pointermove", handleWindowPointerMove);
|
||||||
|
window.addEventListener("pointerup", handleWindowPointerUp);
|
||||||
|
window.addEventListener("pointercancel", handleWindowPointerUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("pointermove", handleWindowPointerMove);
|
||||||
|
window.removeEventListener("pointerup", handleWindowPointerUp);
|
||||||
|
window.removeEventListener("pointercancel", handleWindowPointerUp);
|
||||||
|
};
|
||||||
|
}, [tool, appendPoint, commitStroke, stageRef]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
drawingState,
|
||||||
|
handleStagePointerDown,
|
||||||
|
handleStagePointerMove,
|
||||||
|
handleStagePointerUp,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -20,6 +20,10 @@ export const useSelectionHandlers = () => {
|
|||||||
setSelectedCellId,
|
setSelectedCellId,
|
||||||
} = useEditorStore.getState();
|
} = useEditorStore.getState();
|
||||||
|
|
||||||
|
if (tool === "pencil" || tool === "custom-shape") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (tool !== "select") {
|
if (tool !== "select") {
|
||||||
setTool("select");
|
setTool("select");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ export const useTransformHandlers = (
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (selectedObject.type === "custom-shape") {
|
} else if (selectedObject.type === "custom-shape" || selectedObject.type === "pencil") {
|
||||||
// Scale each vertex in-place; no width/height anchor needed
|
// Scale each vertex in-place; no width/height anchor needed
|
||||||
const oldPoints = selectedObject.points ?? [];
|
const oldPoints = selectedObject.points ?? [];
|
||||||
const newPoints = oldPoints.map((val, i) =>
|
const newPoints = oldPoints.map((val, i) =>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const ObjectSettings = ({ selectedObject, pageWidth, pageHeight, onUpdate, onDel
|
|||||||
|
|
||||||
{roundedSelectedObject.type === "rectangle" && <ShapeSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
{roundedSelectedObject.type === "rectangle" && <ShapeSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
||||||
|
|
||||||
{(roundedSelectedObject.type === "line" || roundedSelectedObject.type === "arrow") && <LineSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
{(roundedSelectedObject.type === "line" || roundedSelectedObject.type === "arrow" || roundedSelectedObject.type === "pencil") && <LineSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
||||||
|
|
||||||
{roundedSelectedObject.type === "image" && <SizeSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
{roundedSelectedObject.type === "image" && <SizeSettings selectedObject={roundedSelectedObject} onUpdate={handleRoundedUpdate} />}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const ToolInstructions = ({ tool }: ToolInstructionsProps) => {
|
|||||||
rectangle: <RectangleInstruction />,
|
rectangle: <RectangleInstruction />,
|
||||||
line: <RectangleInstruction />,
|
line: <RectangleInstruction />,
|
||||||
arrow: <RectangleInstruction />,
|
arrow: <RectangleInstruction />,
|
||||||
|
pencil: <RectangleInstruction />,
|
||||||
"custom-shape": <RectangleInstruction />,
|
"custom-shape": <RectangleInstruction />,
|
||||||
text: <TextInstruction />,
|
text: <TextInstruction />,
|
||||||
sticker: <StickerInstruction />,
|
sticker: <StickerInstruction />,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const ToolsBar = ({ tool, onToolClick }: ToolsBarProps) => {
|
|||||||
{tools.map((item, index) => {
|
{tools.map((item, index) => {
|
||||||
const isActive =
|
const isActive =
|
||||||
item.tool === "rectangle"
|
item.tool === "rectangle"
|
||||||
? tool === "rectangle" || tool === "line" || tool === "arrow" || tool === "custom-shape"
|
? tool === "rectangle" || tool === "line" || tool === "arrow" || tool === "custom-shape" || tool === "pencil"
|
||||||
: tool === item.tool;
|
: tool === item.tool;
|
||||||
const iconColor = "black";
|
const iconColor = "black";
|
||||||
const iconVariant: "Bold" | "Outline" | "Broken" | "Bulk" | "Linear" | "TwoTone" | undefined = isActive ? "Bold" : "Outline";
|
const iconVariant: "Bold" | "Outline" | "Broken" | "Bulk" | "Linear" | "TwoTone" | undefined = isActive ? "Bold" : "Outline";
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
const CustomShapeInstruction = () => (
|
const CustomShapeInstruction = () => (
|
||||||
<div className="space-y-2 text-sm text-gray-600">
|
<div className="space-y-2 text-sm text-gray-600">
|
||||||
<p className="font-medium text-gray-700">رسم شکل سفارشی</p>
|
<p className="font-medium text-gray-700">رسم نقطهبهنقطه (Pen)</p>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
<li>• روی کانوس کلیک کنید تا نقاط اضافه شود</li>
|
<li>• روی کانوس کلیک کنید تا نقاط اضافه شود</li>
|
||||||
<li>• روی نقطه اول کلیک کنید تا شکل بسته شود</li>
|
<li>• روی نقطه اول کلیک کنید تا شکل بسته شود</li>
|
||||||
<li>• دوبار کلیک کنید تا شکل تکمیل شود</li>
|
<li>• دوبار کلیک کنید تا شکل تکمیل شود</li>
|
||||||
<li>• Escape را بزنید تا لغو شود</li>
|
<li>• Escape را بزنید تا لغو شود</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p className="text-xs text-gray-500 pt-1">
|
||||||
|
برای نقاشی آزاد (کلیک و بکش) از ابزار «قلم» استفاده کنید.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import ColorPicker from "@/components/ColorPicker";
|
||||||
|
import Input from "@/components/Input";
|
||||||
|
import { usePencilDefaultsStore } from "@/pages/editor/store/pencilDefaultsStore";
|
||||||
|
import { Brush } from "iconsax-react";
|
||||||
|
import { type FC } from "react";
|
||||||
|
|
||||||
|
const PencilInstruction: FC = () => {
|
||||||
|
const { defaults, updateDefaults } = usePencilDefaultsStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-9 space-y-4">
|
||||||
|
<div className="flex items-start gap-2 text-sm text-gray-600">
|
||||||
|
<Brush size={18} color="#666" variant="Outline" className="mt-0.5 shrink-0" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p>کلیک کرده و بکشید تا آزاد نقاشی کنید (مانند Pencil در Figma).</p>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
برای رسم نقطهبهنقطه از ابزار «سفارشی» استفاده کنید.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ColorPicker
|
||||||
|
label="رنگ قلم"
|
||||||
|
value={defaults.stroke}
|
||||||
|
onChange={(value) => updateDefaults({ stroke: value })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="ضخامت قلم"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={50}
|
||||||
|
value={defaults.strokeWidth}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateDefaults({
|
||||||
|
strokeWidth: Math.max(1, parseInt(e.target.value) || 1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PencilInstruction;
|
||||||
@@ -6,9 +6,10 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { clx } from "@/helpers/utils";
|
import { clx } from "@/helpers/utils";
|
||||||
import { useEditorStore } from "@/pages/editor/store/editorStore";
|
import { useEditorStore } from "@/pages/editor/store/editorStore";
|
||||||
import { useShapeStore, type ShapeType } from "@/pages/editor/store/shapeStore";
|
import { useShapeStore, type ShapeType } from "@/pages/editor/store/shapeStore";
|
||||||
import { ArrowLeft, Minus, PenTool } from "iconsax-react";
|
import { ArrowLeft, Brush, Minus, PenTool } from "iconsax-react";
|
||||||
import { type FC } from "react";
|
import { type FC } from "react";
|
||||||
import CustomShapeInstruction from "./CustomShapeInstruction";
|
import CustomShapeInstruction from "./CustomShapeInstruction";
|
||||||
|
import PencilInstruction from "./PencilInstruction";
|
||||||
|
|
||||||
const shapeOptions: Array<{ id: ShapeType; label: string; icon: string; alt: string }> = [
|
const shapeOptions: Array<{ id: ShapeType; label: string; icon: string; alt: string }> = [
|
||||||
{ id: "square", label: "مربع", icon: SquareIcon, alt: "square" },
|
{ id: "square", label: "مربع", icon: SquareIcon, alt: "square" },
|
||||||
@@ -18,6 +19,7 @@ const shapeOptions: Array<{ id: ShapeType; label: string; icon: string; alt: str
|
|||||||
];
|
];
|
||||||
|
|
||||||
const drawOptions = [
|
const drawOptions = [
|
||||||
|
{ tool: "pencil" as const, label: "قلم", Icon: Brush },
|
||||||
{ tool: "line" as const, label: "خط", Icon: Minus },
|
{ tool: "line" as const, label: "خط", Icon: Minus },
|
||||||
{ tool: "arrow" as const, label: "پیکان", Icon: ArrowLeft },
|
{ tool: "arrow" as const, label: "پیکان", Icon: ArrowLeft },
|
||||||
{ tool: "custom-shape" as const, label: "سفارشی", Icon: PenTool },
|
{ tool: "custom-shape" as const, label: "سفارشی", Icon: PenTool },
|
||||||
@@ -81,6 +83,8 @@ const RectangleInstruction: FC = () => {
|
|||||||
<p className="mt-9 text-sm text-gray-600">{tool === "line" ? "برای رسم خط، روی کانوس کلیک کرده و بکشید" : "برای رسم پیکان، روی کانوس کلیک کرده و بکشید"}</p>
|
<p className="mt-9 text-sm text-gray-600">{tool === "line" ? "برای رسم خط، روی کانوس کلیک کرده و بکشید" : "برای رسم پیکان، روی کانوس کلیک کرده و بکشید"}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tool === "pencil" && <PencilInstruction />}
|
||||||
|
|
||||||
{tool === "custom-shape" && (
|
{tool === "custom-shape" && (
|
||||||
<div className="mt-9">
|
<div className="mt-9">
|
||||||
<CustomShapeInstruction />
|
<CustomShapeInstruction />
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
import { Line } from "react-konva";
|
||||||
|
import Konva from "konva";
|
||||||
|
import type { ShapeProps } from "./types";
|
||||||
|
|
||||||
|
const PencilShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
||||||
|
const shapeRef = useRef<Konva.Line>(null);
|
||||||
|
|
||||||
|
const points = obj.points ?? [];
|
||||||
|
if (points.length < 4) return null;
|
||||||
|
|
||||||
|
const actualStrokeWidth = obj.strokeWidth ?? 3;
|
||||||
|
const hasStroke = actualStrokeWidth > 0;
|
||||||
|
|
||||||
|
const displayStroke = isSelected
|
||||||
|
? (hasStroke ? obj.stroke : "#3b82f6")
|
||||||
|
: (hasStroke ? obj.stroke : "#000000");
|
||||||
|
|
||||||
|
const displayStrokeWidth = isSelected
|
||||||
|
? (hasStroke ? actualStrokeWidth : 3)
|
||||||
|
: actualStrokeWidth;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Line
|
||||||
|
ref={shapeRef}
|
||||||
|
id={obj.id}
|
||||||
|
name="canvas-object"
|
||||||
|
x={obj.x}
|
||||||
|
y={obj.y}
|
||||||
|
points={points}
|
||||||
|
closed={false}
|
||||||
|
stroke={displayStroke}
|
||||||
|
strokeWidth={displayStrokeWidth}
|
||||||
|
tension={obj.tension ?? 0.5}
|
||||||
|
lineCap="round"
|
||||||
|
lineJoin="round"
|
||||||
|
hitStrokeWidth={Math.max(20, displayStrokeWidth + 10)}
|
||||||
|
perfectDrawEnabled={false}
|
||||||
|
rotation={obj.rotation ?? 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
|
draggable={draggable}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (shapeRef.current) {
|
||||||
|
onSelect(obj.id, shapeRef.current, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragMove={(e) => {
|
||||||
|
e.target.getLayer()?.batchDraw();
|
||||||
|
}}
|
||||||
|
onDragEnd={(e) => {
|
||||||
|
const node = e.target;
|
||||||
|
onUpdate(obj.id, {
|
||||||
|
x: node.x(),
|
||||||
|
y: node.y(),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PencilShape;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export { default as RectangleShape } from "./RectangleShape";
|
export { default as RectangleShape } from "./RectangleShape";
|
||||||
export { default as CustomShape } from "./CustomShape";
|
export { default as CustomShape } from "./CustomShape";
|
||||||
|
export { default as PencilShape } from "./PencilShape";
|
||||||
export { default as CircleShape } from "./CircleShape";
|
export { default as CircleShape } from "./CircleShape";
|
||||||
export { default as TriangleShape } from "./TriangleShape";
|
export { default as TriangleShape } from "./TriangleShape";
|
||||||
export { default as AbstractShape } from "./AbstractShape";
|
export { default as AbstractShape } from "./AbstractShape";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { EditorObject, Page, TableCell } from "./editorStore.types";
|
import type { EditorObject, Page, TableCell } from "./editorStore.types";
|
||||||
import { normalizeCustomShapeObject } from "../utils/customShape";
|
import { normalizeCustomShapeObject, normalizePencilObject } from "../utils/customShape";
|
||||||
|
|
||||||
export const createTableCellId = (row: number, col: number) =>
|
export const createTableCellId = (row: number, col: number) =>
|
||||||
`cell-${row}-${col}`;
|
`cell-${row}-${col}`;
|
||||||
@@ -390,4 +390,6 @@ export const ensureMissingGroupObjects = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const normalizePageObjects = (objects: EditorObject[]): EditorObject[] =>
|
export const normalizePageObjects = (objects: EditorObject[]): EditorObject[] =>
|
||||||
ensureMissingGroupObjects(objects).map(normalizeCustomShapeObject);
|
ensureMissingGroupObjects(objects)
|
||||||
|
.map(normalizeCustomShapeObject)
|
||||||
|
.map(normalizePencilObject);
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export type ToolType =
|
|||||||
| "link"
|
| "link"
|
||||||
| "grid"
|
| "grid"
|
||||||
| "group"
|
| "group"
|
||||||
| "custom-shape";
|
| "custom-shape"
|
||||||
|
| "pencil";
|
||||||
|
|
||||||
export type TableCell = {
|
export type TableCell = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -99,6 +100,8 @@ export type EditorObject = {
|
|||||||
points?: number[];
|
points?: number[];
|
||||||
/** آیا شکل چندضلعی بسته است (پیشفرض true برای custom-shape کاملشده) */
|
/** آیا شکل چندضلعی بسته است (پیشفرض true برای custom-shape کاملشده) */
|
||||||
closed?: boolean;
|
closed?: boolean;
|
||||||
|
/** نرمی منحنی برای مسیرهای آزاد (pencil) — مقدار Konva Line.tension */
|
||||||
|
tension?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageGuide = {
|
export type PageGuide = {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export type PencilDefaults = {
|
||||||
|
stroke: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PencilDefaultsStoreType = {
|
||||||
|
defaults: PencilDefaults;
|
||||||
|
updateDefaults: (updates: Partial<PencilDefaults>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePencilDefaultsStore = create<PencilDefaultsStoreType>((set) => ({
|
||||||
|
defaults: {
|
||||||
|
stroke: "#000000",
|
||||||
|
strokeWidth: 3,
|
||||||
|
},
|
||||||
|
updateDefaults: (updates) =>
|
||||||
|
set((state) => ({
|
||||||
|
defaults: { ...state.defaults, ...updates },
|
||||||
|
})),
|
||||||
|
}));
|
||||||
@@ -71,3 +71,44 @@ export const normalizeCustomShapeObject = (
|
|||||||
|
|
||||||
return next;
|
return next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MIN_PENCIL_POINTS = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures pencil stroke points are relative to obj.x/obj.y and bbox matches width/height.
|
||||||
|
*/
|
||||||
|
export const normalizePencilObject = (obj: EditorObject): EditorObject => {
|
||||||
|
if (obj.type !== "pencil" || !obj.points || obj.points.length < MIN_PENCIL_POINTS) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next: EditorObject = { ...obj, closed: false };
|
||||||
|
|
||||||
|
if (
|
||||||
|
next.opacity !== undefined &&
|
||||||
|
next.opacity > 0 &&
|
||||||
|
next.opacity <= 1
|
||||||
|
) {
|
||||||
|
next = { ...next, opacity: next.opacity * 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = next.points!;
|
||||||
|
const { minX, minY, width, height } = getCustomShapePointBounds(points);
|
||||||
|
|
||||||
|
if (minX !== 0 || minY !== 0) {
|
||||||
|
next = {
|
||||||
|
...next,
|
||||||
|
x: (next.x ?? 0) + minX,
|
||||||
|
y: (next.y ?? 0) + minY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
points: points.map((val, i) =>
|
||||||
|
i % 2 === 0 ? val - minX : val - minY,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (next.width !== width || next.height !== height) {
|
||||||
|
next = { ...next, width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|||||||
@@ -720,6 +720,56 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'pencil': {
|
||||||
|
const rawPoints = obj.points ?? [];
|
||||||
|
if (rawPoints.length < 4) return null;
|
||||||
|
|
||||||
|
const { minX, minY, width: bboxW, height: bboxH } =
|
||||||
|
getCustomShapePointBounds(rawPoints);
|
||||||
|
const width = bboxW * scale;
|
||||||
|
const height = bboxH * scale;
|
||||||
|
const pointsStr = Array.from(
|
||||||
|
{ length: rawPoints.length / 2 },
|
||||||
|
(_, i) =>
|
||||||
|
`${(rawPoints[i * 2] - minX) * scale},${(rawPoints[i * 2 + 1] - minY) * scale}`,
|
||||||
|
).join(' ');
|
||||||
|
|
||||||
|
const strokeWidth = actualStrokeWidth * scale;
|
||||||
|
const strokeColor = obj.stroke || '#000000';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
key={obj.id || index}
|
||||||
|
style={applyStyle(
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${((obj.x || 0) + minX) * scale}px`,
|
||||||
|
top: `${((obj.y || 0) + minY) * scale}px`,
|
||||||
|
width: `${width}px`,
|
||||||
|
height: `${height}px`,
|
||||||
|
opacity: (obj.opacity ?? 100) / 100,
|
||||||
|
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||||
|
transformOrigin: 'top left',
|
||||||
|
zIndex: index,
|
||||||
|
overflow: 'visible',
|
||||||
|
},
|
||||||
|
obj,
|
||||||
|
index,
|
||||||
|
)}
|
||||||
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
points={pointsStr}
|
||||||
|
fill="none"
|
||||||
|
stroke={strokeColor}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
case 'grid': {
|
case 'grid': {
|
||||||
if (!obj.tableData) return null;
|
if (!obj.tableData) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { EditorObject, TableData } from "@/pages/editor/store/editorStore";
|
import type { EditorObject, TableData } from "@/pages/editor/store/editorStore";
|
||||||
import { normalizeCustomShapeObject } from "@/pages/editor/utils/customShape";
|
import { normalizeCustomShapeObject, normalizePencilObject } from "@/pages/editor/utils/customShape";
|
||||||
import type { PageData } from "../types";
|
import type { PageData } from "../types";
|
||||||
|
|
||||||
type ViewerDataPage = {
|
type ViewerDataPage = {
|
||||||
@@ -53,6 +53,7 @@ type ViewerDataPage = {
|
|||||||
maskInvert?: boolean;
|
maskInvert?: boolean;
|
||||||
points?: number[];
|
points?: number[];
|
||||||
closed?: boolean;
|
closed?: boolean;
|
||||||
|
tension?: number;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +177,18 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
|||||||
if (obj.closed !== undefined) baseObject.closed = obj.closed;
|
if (obj.closed !== undefined) baseObject.closed = obj.closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizeCustomShapeObject(baseObject);
|
if (obj.type === "pencil") {
|
||||||
|
if (obj.points !== undefined) baseObject.points = obj.points;
|
||||||
|
if (obj.closed !== undefined) baseObject.closed = obj.closed;
|
||||||
|
if (obj.tension !== undefined) baseObject.tension = obj.tension;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized =
|
||||||
|
obj.type === "pencil"
|
||||||
|
? normalizePencilObject(baseObject)
|
||||||
|
: normalizeCustomShapeObject(baseObject);
|
||||||
|
|
||||||
|
return normalized;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user