This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
|
|||||||
import { Group, Rect, Circle, RegularPolygon, Star } from "react-konva";
|
import { Group, Rect, Circle, RegularPolygon, Star } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { EditorObject } from "../../store/editorStore";
|
import type { EditorObject } from "../../store/editorStore";
|
||||||
|
import { getNodeTransformDimensions } from "../../utils/transformCommit";
|
||||||
import {
|
import {
|
||||||
RectangleShape,
|
RectangleShape,
|
||||||
CircleShape,
|
CircleShape,
|
||||||
@@ -404,9 +405,10 @@ const ObjectRenderer = ({
|
|||||||
newWidth = Math.max(5, (obj.width || 100) * scaleX);
|
newWidth = Math.max(5, (obj.width || 100) * scaleX);
|
||||||
newHeight = Math.max(5, (obj.height || 100) * scaleY);
|
newHeight = Math.max(5, (obj.height || 100) * scaleY);
|
||||||
} else {
|
} else {
|
||||||
// Default for other objects (image, etc.)
|
const { width: baseWidth, height: baseHeight } =
|
||||||
newWidth = Math.max(5, (obj.width || 100) * scaleX);
|
getNodeTransformDimensions(node);
|
||||||
newHeight = Math.max(5, (obj.height || 100) * scaleY);
|
newWidth = Math.max(5, baseWidth * scaleX);
|
||||||
|
newHeight = Math.max(5, baseHeight * scaleY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// بهروزرسانی object
|
// بهروزرسانی object
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { useEffect, type RefObject } from "react";
|
import { useEffect, useRef, type RefObject } from "react";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import { useEditorStore } from "../../store/editorStore";
|
import { useEditorStore } from "../../store/editorStore";
|
||||||
|
import {
|
||||||
|
getNodeTransformDimensions,
|
||||||
|
nodeHasPendingTransform,
|
||||||
|
resolveObjectFromNodeId,
|
||||||
|
} from "../../utils/transformCommit";
|
||||||
|
|
||||||
export const useTransformHandlers = (
|
export const useTransformHandlers = (
|
||||||
transformerRef: RefObject<Konva.Transformer | null>,
|
transformerRef: RefObject<Konva.Transformer | null>,
|
||||||
@@ -12,6 +17,7 @@ export const useTransformHandlers = (
|
|||||||
updateObject,
|
updateObject,
|
||||||
applyTableResize,
|
applyTableResize,
|
||||||
} = useEditorStore();
|
} = useEditorStore();
|
||||||
|
const commitTransformRef = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
// Attach transformer to selected objects when they change
|
// Attach transformer to selected objects when they change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -60,7 +66,15 @@ export const useTransformHandlers = (
|
|||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId);
|
return () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
const transformer = transformerRef.current;
|
||||||
|
if (!transformer) return;
|
||||||
|
const nodes = transformer.nodes();
|
||||||
|
if (nodes.some(nodeHasPendingTransform)) {
|
||||||
|
commitTransformRef.current?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [selectedObjectIds, objects, transformerRef, layerRef]);
|
}, [selectedObjectIds, objects, transformerRef, layerRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -148,7 +162,7 @@ export const useTransformHandlers = (
|
|||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
const nodeId = node.id();
|
const nodeId = node.id();
|
||||||
const selectedObject = objects.find((obj) => obj.id === nodeId);
|
const selectedObject = resolveObjectFromNodeId(nodeId, objects);
|
||||||
if (!selectedObject) return;
|
if (!selectedObject) return;
|
||||||
|
|
||||||
// Handle group objects
|
// Handle group objects
|
||||||
@@ -505,23 +519,28 @@ export const useTransformHandlers = (
|
|||||||
height: Math.max(1, newHeight),
|
height: Math.max(1, newHeight),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Default for other objects (image, etc.)
|
// Default for raster objects (image, sticker, document, video, …)
|
||||||
|
const { width: baseWidth, height: baseHeight } =
|
||||||
|
getNodeTransformDimensions(node);
|
||||||
updateObject(selectedObject.id, {
|
updateObject(selectedObject.id, {
|
||||||
x: node.x(),
|
x: node.x(),
|
||||||
y: node.y(),
|
y: node.y(),
|
||||||
rotation: node.rotation(),
|
rotation: node.rotation(),
|
||||||
width: Math.max(5, (selectedObject.width || 100) * scaleX),
|
width: Math.max(5, baseWidth * scaleX),
|
||||||
height: Math.max(5, (selectedObject.height || 100) * scaleY),
|
height: Math.max(5, baseHeight * scaleY),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
commitTransformRef.current = handleTransformEnd;
|
||||||
|
|
||||||
transformer.on("transform", handleTransform);
|
transformer.on("transform", handleTransform);
|
||||||
transformer.on("transformend", handleTransformEnd);
|
transformer.on("transformend", handleTransformEnd);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
commitTransformRef.current = null;
|
||||||
transformer.off("transform", handleTransform);
|
transformer.off("transform", handleTransform);
|
||||||
transformer.off("transformend", handleTransformEnd);
|
transformer.off("transformend", handleTransformEnd);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import Konva from "konva";
|
||||||
|
import type { EditorObject } from "../store/editorStore";
|
||||||
|
|
||||||
|
/** Resolve editor object from a Konva node id (supports masked-* groups). */
|
||||||
|
export const resolveObjectFromNodeId = (
|
||||||
|
nodeId: string,
|
||||||
|
objects: EditorObject[],
|
||||||
|
): EditorObject | undefined => {
|
||||||
|
if (nodeId.startsWith("masked-")) {
|
||||||
|
const objectId = nodeId.slice("masked-".length);
|
||||||
|
return objects.find((obj) => obj.id === objectId);
|
||||||
|
}
|
||||||
|
return objects.find((obj) => obj.id === nodeId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base width/height used for transform baking.
|
||||||
|
* For images, Konva may use natural image size while store width is still unset.
|
||||||
|
*/
|
||||||
|
export const getNodeTransformDimensions = (
|
||||||
|
node: Konva.Node,
|
||||||
|
): { width: number; height: number } => {
|
||||||
|
const nodeWidth = node.width();
|
||||||
|
const nodeHeight = node.height();
|
||||||
|
if (nodeWidth > 0 && nodeHeight > 0) {
|
||||||
|
return { width: nodeWidth, height: nodeHeight };
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageNode =
|
||||||
|
"findOne" in node
|
||||||
|
? (node as Konva.Container).findOne<Konva.Image>("Image")
|
||||||
|
: undefined;
|
||||||
|
if (imageNode && imageNode.width() > 0 && imageNode.height() > 0) {
|
||||||
|
return { width: imageNode.width(), height: imageNode.height() };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = node.getClientRect({ skipTransform: true });
|
||||||
|
if (rect.width > 0 && rect.height > 0) {
|
||||||
|
return { width: rect.width, height: rect.height };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width: 100, height: 100 };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nodeHasPendingTransform = (node: Konva.Node): boolean =>
|
||||||
|
Math.abs(node.scaleX() - 1) > 0.0001 ||
|
||||||
|
Math.abs(node.scaleY() - 1) > 0.0001;
|
||||||
@@ -14,6 +14,28 @@ import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimat
|
|||||||
import { getMaskImageStyle, getMaskedLayout } from '@/pages/viewer/utils/maskStyle';
|
import { getMaskImageStyle, getMaskedLayout } from '@/pages/viewer/utils/maskStyle';
|
||||||
import type { EntrancePhase } from '@/pages/viewer/hooks/useBookEntranceController';
|
import type { EntrancePhase } from '@/pages/viewer/hooks/useBookEntranceController';
|
||||||
|
|
||||||
|
const getRasterObjectStyle = (
|
||||||
|
obj: EditorObject,
|
||||||
|
scale: number,
|
||||||
|
baseStyle: React.CSSProperties,
|
||||||
|
index: number,
|
||||||
|
): React.CSSProperties => {
|
||||||
|
const scaleX = obj.scaleX ?? 1;
|
||||||
|
const scaleY = obj.scaleY ?? 1;
|
||||||
|
const width = obj.width != null ? obj.width * scaleX * scale : undefined;
|
||||||
|
const height = obj.height != null ? obj.height * scaleY * scale : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
display: 'block',
|
||||||
|
maxWidth: 'none',
|
||||||
|
width: width != null ? `${width}px` : 'auto',
|
||||||
|
height: height != null ? `${height}px` : 'auto',
|
||||||
|
objectFit: 'fill',
|
||||||
|
zIndex: index,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type RenderObjectOptions = {
|
type RenderObjectOptions = {
|
||||||
skipEntrance?: boolean;
|
skipEntrance?: boolean;
|
||||||
};
|
};
|
||||||
@@ -200,19 +222,14 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
|
|
||||||
case 'image':
|
case 'image':
|
||||||
case 'sticker':
|
case 'sticker':
|
||||||
|
case 'document':
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
key={obj.id || index}
|
key={obj.id || index}
|
||||||
src={obj.imageUrl || ''}
|
src={obj.imageUrl || ''}
|
||||||
alt=""
|
alt=""
|
||||||
style={applyStyle(
|
style={applyStyle(
|
||||||
{
|
getRasterObjectStyle(obj, scale, baseStyle, index),
|
||||||
...baseStyle,
|
|
||||||
width: obj.width ? `${obj.width * scale}px` : 'auto',
|
|
||||||
height: obj.height ? `${obj.height * scale}px` : 'auto',
|
|
||||||
objectFit: 'fill', // مشابه Konva که image را با width و height مشخص شده رندر میکند
|
|
||||||
zIndex: index, // برای حفظ ترتیب رندر
|
|
||||||
},
|
|
||||||
obj,
|
obj,
|
||||||
index,
|
index,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -128,7 +128,10 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
|||||||
baseObject.borderRadius = obj.borderRadius;
|
baseObject.borderRadius = obj.borderRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((obj.type === "image" || obj.type === "sticker") && obj.imageUrl) {
|
if (
|
||||||
|
(obj.type === "image" || obj.type === "sticker" || obj.type === "document") &&
|
||||||
|
obj.imageUrl
|
||||||
|
) {
|
||||||
baseObject.imageUrl = obj.imageUrl;
|
baseObject.imageUrl = obj.imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user