mask in viewer

This commit is contained in:
hamid zarghami
2026-06-13 10:24:16 +03:30
parent ef4ddeb3fa
commit 5174d0371d
3 changed files with 331 additions and 15 deletions
+81 -15
View File
@@ -10,8 +10,13 @@ import {
} from '@/pages/editor/utils/textStyle';
import '@/pages/viewer/styles/entranceAnimations.css';
import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle';
import { getMaskImageStyle, getMaskedLayout } from '@/pages/viewer/utils/maskStyle';
import type { EntrancePhase } from '@/pages/viewer/hooks/useBookEntranceController';
type RenderObjectOptions = {
skipEntrance?: boolean;
};
type BookPageProps = {
page: PageData;
scale?: number;
@@ -94,7 +99,24 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
flyLayoutPx,
});
const renderObject = (obj: EditorObject, index: number) => {
const renderObjectContent = (
obj: EditorObject,
index: number,
options: RenderObjectOptions = {},
) => {
const applyStyle = (
style: React.CSSProperties,
styleObj: EditorObject,
styleIndex: number,
extra?: {
transformOrigin?: React.CSSProperties['transformOrigin'];
rotationDeg?: number;
},
) =>
options.skipEntrance
? style
: withEntrance(style, styleObj, styleIndex, extra);
const actualStrokeWidth = obj.strokeWidth ?? 0;
const hasStroke = actualStrokeWidth > 0;
const baseStyle: React.CSSProperties = {
@@ -146,7 +168,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
...textBaseStyle,
left: `${textLayout.leftPx}px`,
@@ -182,7 +204,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
key={obj.id || index}
src={obj.imageUrl || ''}
alt=""
style={withEntrance(
style={applyStyle(
{
...baseStyle,
width: obj.width ? `${obj.width * scale}px` : 'auto',
@@ -202,7 +224,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
key={obj.id || index}
src={obj.videoUrl || ''}
controls
style={withEntrance(
style={applyStyle(
{
...baseStyle,
width: obj.width ? `${obj.width * scale}px` : 'auto',
@@ -233,7 +255,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
src={obj.audioUrl || ''}
controls
preload="metadata"
style={withEntrance(
style={applyStyle(
{
...baseStyle,
width: obj.width ? `${obj.width * scale}px` : `${320 * scale}px`,
@@ -279,7 +301,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
<button
key={obj.id || index}
type="button"
style={withEntrance(
style={applyStyle(
{
...linkStyle,
zIndex: 9999,
@@ -317,7 +339,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
href={obj.linkUrl || '#'}
target="_blank"
rel="noopener noreferrer"
style={withEntrance(
style={applyStyle(
{
...linkStyle,
zIndex: 9999,
@@ -350,7 +372,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
position: 'absolute',
left: `${centerX - radius}px`,
@@ -413,7 +435,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<svg
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
position: 'absolute',
left: `${centerX - radius}px`,
@@ -471,7 +493,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
position: 'absolute',
left: `${centerX - radius}px`,
@@ -510,7 +532,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
zIndex: index, // برای حفظ ترتیب رندر
};
return <div key={obj.id || index} style={withEntrance(shapeStyle, obj, index)} />;
return <div key={obj.id || index} style={applyStyle(shapeStyle, obj, index)} />;
}
case 'line': {
@@ -529,7 +551,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
position: 'absolute',
left: `${startX}px`,
@@ -566,7 +588,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
position: 'absolute',
left: `${startX}px`,
@@ -609,7 +631,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
return (
<div
key={obj.id || index}
style={withEntrance(
style={applyStyle(
{
...baseStyle,
display: 'grid',
@@ -653,6 +675,50 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
}
};
const renderObject = (obj: EditorObject, index: number, allElements: EditorObject[]) => {
if (obj.isMask) return null;
const maskShape = obj.maskId
? allElements.find((m) => m.id === obj.maskId)
: null;
if (!maskShape) {
return renderObjectContent(obj, index);
}
const layout = getMaskedLayout(obj, maskShape, scale);
const unionLeft = layout.left / scale;
const unionTop = layout.top / scale;
const content = renderObjectContent(
{ ...obj, x: (obj.x || 0) - unionLeft, y: (obj.y || 0) - unionTop },
index,
{ skipEntrance: true },
);
if (!content) return null;
const wrapperStyle: React.CSSProperties = {
position: 'absolute',
left: `${layout.left}px`,
top: `${layout.top}px`,
width: `${layout.width}px`,
height: `${layout.height}px`,
overflow: 'visible',
zIndex: index,
...getMaskImageStyle(obj, maskShape, scale, layout),
};
return (
<div
key={`masked-${obj.id || index}`}
style={withEntrance(wrapperStyle, obj, index)}
>
{content}
</div>
);
};
const width = pageWidth ?? 794 * scale;
const height = pageHeight ?? 1123 * scale;
@@ -706,7 +772,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
/>
)}
<div style={{ position: 'absolute', inset: 0, zIndex: 1 }}>
{page.elements.map((element, index) => renderObject(element, index))}
{page.elements.map((element, index) => renderObject(element, index, page.elements))}
</div>
{useHardPage && showPaperShadow && <div aria-hidden className="page-paper-shadow" />}
</div>