base animation
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useCallback } from 'react';
|
||||
import { type PageData } from '../types';
|
||||
import type { EditorObject } from '@/pages/editor/store/editorStore';
|
||||
import { toCssLinearGradient } from '@/pages/editor/utils/gradient';
|
||||
import '@/pages/viewer/styles/entranceAnimations.css';
|
||||
import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle';
|
||||
import { useBookPageEntranceSequence } from '@/pages/viewer/hooks/useBookPageEntranceSequence';
|
||||
|
||||
type BookPageProps = {
|
||||
page: PageData;
|
||||
@@ -17,6 +20,8 @@ type BookPageProps = {
|
||||
angle: number;
|
||||
};
|
||||
backgroundImageUrl?: string;
|
||||
/** در پیشنمایش کاتالوگ انیمیشن ورود غیرفعال باشد */
|
||||
disableEntranceAnimations?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -27,7 +32,20 @@ type BookPageProps = {
|
||||
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
|
||||
*/
|
||||
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl }, ref) => {
|
||||
({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, disableEntranceAnimations = false }, ref) => {
|
||||
const { attachRootRef, entranceSeq } = useBookPageEntranceSequence(disableEntranceAnimations);
|
||||
|
||||
const setHostRef = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
attachRootRef(node);
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
} else if (ref) {
|
||||
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
||||
}
|
||||
},
|
||||
[ref, attachRootRef],
|
||||
);
|
||||
// تابع برای تبدیل opacity به رنگ (مثل editor)
|
||||
// در editor، getColorWithOpacity انتظار opacity 0-100 دارد
|
||||
// در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
|
||||
@@ -57,6 +75,25 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
};
|
||||
|
||||
const flyLayoutPx = {
|
||||
width: pageWidth ?? 794 * scale,
|
||||
height: pageHeight ?? 1123 * scale,
|
||||
};
|
||||
|
||||
const withEntrance = (
|
||||
style: React.CSSProperties,
|
||||
obj: EditorObject,
|
||||
index: number,
|
||||
extra?: {
|
||||
transformOrigin?: React.CSSProperties['transformOrigin'];
|
||||
rotationDeg?: number;
|
||||
},
|
||||
) =>
|
||||
mergeEntranceAnimationStyle(style, obj, scale, index, entranceSeq, {
|
||||
...extra,
|
||||
flyLayoutPx,
|
||||
});
|
||||
|
||||
const renderObject = (obj: EditorObject, index: number) => {
|
||||
const actualStrokeWidth = obj.strokeWidth ?? 0;
|
||||
const hasStroke = actualStrokeWidth > 0;
|
||||
@@ -90,21 +127,25 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
...textBaseStyle,
|
||||
left: `${textLeft}px`,
|
||||
width: textWidth ? `${textWidth}px` : undefined,
|
||||
fontSize: `${(obj.fontSize || 16) * scale}px`,
|
||||
fontFamily: obj.fontFamily || 'irancell, sans-serif',
|
||||
fontWeight: obj.fontWeight || 'normal',
|
||||
lineHeight: obj.lineHeight ?? 1.2,
|
||||
textAlign: obj.textAlign ?? 'right',
|
||||
direction: 'rtl',
|
||||
color: getColorWithOpacity(obj.fill, obj.opacity),
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...textBaseStyle,
|
||||
left: `${textLeft}px`,
|
||||
width: textWidth ? `${textWidth}px` : undefined,
|
||||
fontSize: `${(obj.fontSize || 16) * scale}px`,
|
||||
fontFamily: obj.fontFamily || 'irancell, sans-serif',
|
||||
fontWeight: obj.fontWeight || 'normal',
|
||||
lineHeight: obj.lineHeight ?? 1.2,
|
||||
textAlign: obj.textAlign ?? 'right',
|
||||
direction: 'rtl',
|
||||
color: getColorWithOpacity(obj.fill, obj.opacity),
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
>
|
||||
{obj.text || ''}
|
||||
</div>
|
||||
@@ -117,13 +158,17 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
key={obj.id || index}
|
||||
src={obj.imageUrl || ''}
|
||||
alt=""
|
||||
style={{
|
||||
...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, // برای حفظ ترتیب رندر
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...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,
|
||||
index,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -133,14 +178,18 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
key={obj.id || index}
|
||||
src={obj.videoUrl || ''}
|
||||
controls
|
||||
style={{
|
||||
...baseStyle,
|
||||
width: obj.width ? `${obj.width * scale}px` : 'auto',
|
||||
height: obj.height ? `${obj.height * scale}px` : 'auto',
|
||||
objectFit: 'contain',
|
||||
zIndex: index,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...baseStyle,
|
||||
width: obj.width ? `${obj.width * scale}px` : 'auto',
|
||||
height: obj.height ? `${obj.height * scale}px` : 'auto',
|
||||
objectFit: 'contain',
|
||||
zIndex: index,
|
||||
pointerEvents: 'auto',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
@@ -175,15 +224,19 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
<button
|
||||
key={obj.id || index}
|
||||
type="button"
|
||||
style={{
|
||||
...linkStyle,
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'auto',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
padding: 0,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...linkStyle,
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'auto',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
padding: 0,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -209,11 +262,15 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
href={obj.linkUrl || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
...linkStyle,
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...linkStyle,
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'auto',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
@@ -238,23 +295,28 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${radius * 2}px`,
|
||||
height: `${radius * 2}px`,
|
||||
borderRadius: '50%',
|
||||
...objectFillStyle,
|
||||
border: hasStroke && obj.stroke
|
||||
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
||||
: 'none',
|
||||
boxSizing: 'border-box',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${radius * 2}px`,
|
||||
height: `${radius * 2}px`,
|
||||
borderRadius: '50%',
|
||||
...objectFillStyle,
|
||||
border: hasStroke && obj.stroke
|
||||
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
||||
: 'none',
|
||||
boxSizing: 'border-box',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
{ transformOrigin: 'center center' },
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -296,18 +358,23 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<svg
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
zIndex: index,
|
||||
overflow: 'visible',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
zIndex: index,
|
||||
overflow: 'visible',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
{ transformOrigin: 'center center' },
|
||||
)}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
>
|
||||
{obj.fillType === 'gradient' && obj.gradient ? (
|
||||
@@ -349,22 +416,27 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${radius * 2}px`,
|
||||
height: `${radius * 2}px`,
|
||||
clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
||||
...objectFillStyle,
|
||||
border: obj.stroke
|
||||
? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
|
||||
: 'none',
|
||||
boxSizing: 'border-box',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
position: 'absolute',
|
||||
left: `${centerX - radius}px`,
|
||||
top: `${centerY - radius}px`,
|
||||
width: `${radius * 2}px`,
|
||||
height: `${radius * 2}px`,
|
||||
clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
||||
...objectFillStyle,
|
||||
border: obj.stroke
|
||||
? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
|
||||
: 'none',
|
||||
boxSizing: 'border-box',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
{ transformOrigin: 'center center' },
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -383,7 +455,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
};
|
||||
|
||||
return <div key={obj.id || index} style={shapeStyle} />;
|
||||
return <div key={obj.id || index} style={withEntrance(shapeStyle, obj, index)} />;
|
||||
}
|
||||
|
||||
case 'line': {
|
||||
@@ -402,17 +474,22 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${startX}px`,
|
||||
top: `${startY}px`,
|
||||
width: `${lineLength}px`,
|
||||
height: `${(obj.strokeWidth || 2) * scale}px`,
|
||||
backgroundColor: obj.stroke || '#000000',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: `rotate(${lineAngle}deg)`,
|
||||
transformOrigin: 'left center',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
position: 'absolute',
|
||||
left: `${startX}px`,
|
||||
top: `${startY}px`,
|
||||
width: `${lineLength}px`,
|
||||
height: `${(obj.strokeWidth || 2) * scale}px`,
|
||||
backgroundColor: obj.stroke || '#000000',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: `rotate(${lineAngle}deg)`,
|
||||
transformOrigin: 'left center',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
{ rotationDeg: lineAngle, transformOrigin: 'left center' },
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -434,17 +511,22 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${startX}px`,
|
||||
top: `${startY}px`,
|
||||
width: `${arrowLength}px`,
|
||||
height: `${strokeWidth}px`,
|
||||
backgroundColor: obj.stroke || '#000000',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: `rotate(${arrowAngle}deg)`,
|
||||
transformOrigin: 'left center',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
position: 'absolute',
|
||||
left: `${startX}px`,
|
||||
top: `${startY}px`,
|
||||
width: `${arrowLength}px`,
|
||||
height: `${strokeWidth}px`,
|
||||
backgroundColor: obj.stroke || '#000000',
|
||||
opacity: (obj.opacity ?? 100) / 100,
|
||||
transform: `rotate(${arrowAngle}deg)`,
|
||||
transformOrigin: 'left center',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
{ rotationDeg: arrowAngle, transformOrigin: 'left center' },
|
||||
)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -472,13 +554,17 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
...baseStyle,
|
||||
display: 'grid',
|
||||
gridTemplateRows: `repeat(${rows}, ${cellHeight * scale}px)`,
|
||||
gridTemplateColumns: `repeat(${cols}, ${cellWidth * scale}px)`,
|
||||
border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : 'none',
|
||||
}}
|
||||
style={withEntrance(
|
||||
{
|
||||
...baseStyle,
|
||||
display: 'grid',
|
||||
gridTemplateRows: `repeat(${rows}, ${cellHeight * scale}px)`,
|
||||
gridTemplateColumns: `repeat(${cols}, ${cellWidth * scale}px)`,
|
||||
border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : 'none',
|
||||
},
|
||||
obj,
|
||||
index,
|
||||
)}
|
||||
>
|
||||
{Array.from({ length: rows }).map((_, rowIndex) =>
|
||||
Array.from({ length: cols }).map((_, colIndex) => {
|
||||
@@ -536,7 +622,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
ref={setHostRef}
|
||||
dir="rtl"
|
||||
className="page"
|
||||
data-density="soft"
|
||||
|
||||
Reference in New Issue
Block a user