This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
|
||||
import { Group, Rect, Circle, RegularPolygon, Star } from "react-konva";
|
||||
import Konva from "konva";
|
||||
import type { EditorObject } from "../../store/editorStore";
|
||||
import { getNodeTransformDimensions } from "../../utils/transformCommit";
|
||||
import {
|
||||
RectangleShape,
|
||||
CircleShape,
|
||||
@@ -404,9 +405,10 @@ const ObjectRenderer = ({
|
||||
newWidth = Math.max(5, (obj.width || 100) * scaleX);
|
||||
newHeight = Math.max(5, (obj.height || 100) * scaleY);
|
||||
} else {
|
||||
// Default for other objects (image, etc.)
|
||||
newWidth = Math.max(5, (obj.width || 100) * scaleX);
|
||||
newHeight = Math.max(5, (obj.height || 100) * scaleY);
|
||||
const { width: baseWidth, height: baseHeight } =
|
||||
getNodeTransformDimensions(node);
|
||||
newWidth = Math.max(5, baseWidth * scaleX);
|
||||
newHeight = Math.max(5, baseHeight * scaleY);
|
||||
}
|
||||
|
||||
// بهروزرسانی object
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useEffect, type RefObject } from "react";
|
||||
import { useEffect, useRef, type RefObject } from "react";
|
||||
import Konva from "konva";
|
||||
import { useEditorStore } from "../../store/editorStore";
|
||||
import {
|
||||
getNodeTransformDimensions,
|
||||
nodeHasPendingTransform,
|
||||
resolveObjectFromNodeId,
|
||||
} from "../../utils/transformCommit";
|
||||
|
||||
export const useTransformHandlers = (
|
||||
transformerRef: RefObject<Konva.Transformer | null>,
|
||||
@@ -12,6 +17,7 @@ export const useTransformHandlers = (
|
||||
updateObject,
|
||||
applyTableResize,
|
||||
} = useEditorStore();
|
||||
const commitTransformRef = useRef<(() => void) | null>(null);
|
||||
|
||||
// Attach transformer to selected objects when they change
|
||||
useEffect(() => {
|
||||
@@ -60,7 +66,15 @@ export const useTransformHandlers = (
|
||||
}
|
||||
}, 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]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -148,7 +162,7 @@ export const useTransformHandlers = (
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const nodeId = node.id();
|
||||
const selectedObject = objects.find((obj) => obj.id === nodeId);
|
||||
const selectedObject = resolveObjectFromNodeId(nodeId, objects);
|
||||
if (!selectedObject) return;
|
||||
|
||||
// Handle group objects
|
||||
@@ -505,23 +519,28 @@ export const useTransformHandlers = (
|
||||
height: Math.max(1, newHeight),
|
||||
});
|
||||
} 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, {
|
||||
x: node.x(),
|
||||
y: node.y(),
|
||||
rotation: node.rotation(),
|
||||
width: Math.max(5, (selectedObject.width || 100) * scaleX),
|
||||
height: Math.max(5, (selectedObject.height || 100) * scaleY),
|
||||
width: Math.max(5, baseWidth * scaleX),
|
||||
height: Math.max(5, baseHeight * scaleY),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
commitTransformRef.current = handleTransformEnd;
|
||||
|
||||
transformer.on("transform", handleTransform);
|
||||
transformer.on("transformend", handleTransformEnd);
|
||||
|
||||
return () => {
|
||||
commitTransformRef.current = null;
|
||||
transformer.off("transform", handleTransform);
|
||||
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 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 = {
|
||||
skipEntrance?: boolean;
|
||||
};
|
||||
@@ -200,19 +222,14 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
|
||||
case 'image':
|
||||
case 'sticker':
|
||||
case 'document':
|
||||
return (
|
||||
<img
|
||||
key={obj.id || index}
|
||||
src={obj.imageUrl || ''}
|
||||
alt=""
|
||||
style={applyStyle(
|
||||
{
|
||||
...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, // برای حفظ ترتیب رندر
|
||||
},
|
||||
getRasterObjectStyle(obj, scale, baseStyle, index),
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
|
||||
@@ -128,7 +128,10 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user