scale viewer

This commit is contained in:
hamid zarghami
2026-03-14 11:42:10 +03:30
parent b5a454a3fe
commit 27f57084e9
7 changed files with 82 additions and 31 deletions
+15
View File
@@ -0,0 +1,15 @@
/**
* ابعاد استاندارد کاغذ (ISO A) به پیکسل - همان مقیاس ادیتور (A4 = 794×1123)
*/
export const PAPER_DIMENSIONS = {
a3: { width: 1123, height: 1587 },
a4: { width: 794, height: 1123 },
a5: { width: 561, height: 794 },
} as const;
export type PaperSizeKey = keyof typeof PAPER_DIMENSIONS;
export function getPaperDimensions(size: string): { width: number; height: number } {
const key = size?.toLowerCase() as PaperSizeKey;
return PAPER_DIMENSIONS[key] ?? PAPER_DIMENSIONS.a4;
}
+1 -1
View File
@@ -45,7 +45,7 @@ const Editor: FC = () => {
isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]" isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]"
)}> )}>
<LayersPanel isOpen={isLayersPanelOpen} setIsOpen={setIsLayersPanelOpen} /> <LayersPanel isOpen={isLayersPanelOpen} setIsOpen={setIsLayersPanelOpen} />
<EditorCanvas /> <EditorCanvas catalogSize={data?.data?.size} />
<EditorSidebar /> <EditorSidebar />
</div> </div>
) )
+6 -2
View File
@@ -13,7 +13,11 @@ import ObjectsLayer from "./canvas/ObjectsLayer";
import CellEditor from "@/components/CellEditor"; import CellEditor from "@/components/CellEditor";
import ZoomControls from "./ZoomControls"; import ZoomControls from "./ZoomControls";
const EditorCanvas = () => { type EditorCanvasProps = {
catalogSize?: string;
};
const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
const stageRef = useRef<Konva.Stage>(null); const stageRef = useRef<Konva.Stage>(null);
const layerRef = useRef<Konva.Layer>(null); const layerRef = useRef<Konva.Layer>(null);
@@ -30,7 +34,7 @@ const EditorCanvas = () => {
} = useEditorStore(); } = useEditorStore();
const { transformerRef, handleStageMouseDown, handleStageMouseMove, handleStageMouseUp } = useDrawingHandlers(); const { transformerRef, handleStageMouseDown, handleStageMouseMove, handleStageMouseUp } = useDrawingHandlers();
const stageSize = useStageSize(); const stageSize = useStageSize(catalogSize);
useKeyboardMovement(); useKeyboardMovement();
const { handleSelect, handleCellClick, handleCellDblClick } = useSelectionHandlers(); const { handleSelect, handleCellClick, handleCellDblClick } = useSelectionHandlers();
@@ -1,35 +1,33 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { getPaperDimensions } from "@/config/paperSizes";
export const A4_WIDTH = 794;
export const A4_HEIGHT = 1123;
const SCALE = 0.8; const SCALE = 0.8;
export const useStageSize = () => { export const useStageSize = (catalogSize?: string) => {
const { width: baseWidth, height: baseHeight } = getPaperDimensions(catalogSize ?? "a4");
const [stageSize, setStageSize] = useState({ const [stageSize, setStageSize] = useState({
width: A4_WIDTH, width: baseWidth,
height: A4_HEIGHT, height: baseHeight,
scale: SCALE, scale: SCALE,
}); });
useEffect(() => { useEffect(() => {
const { width, height } = getPaperDimensions(catalogSize ?? "a4");
const handleResize = () => { const handleResize = () => {
const maxWidth = window.innerWidth - 400; const maxWidth = window.innerWidth - 400;
const maxHeight = window.innerHeight - 100; const maxHeight = window.innerHeight - 100;
const scaleX = maxWidth / A4_WIDTH; const scaleX = maxWidth / width;
const scaleY = maxHeight / A4_HEIGHT; const scaleY = maxHeight / height;
const newScale = Math.min(scaleX, scaleY, SCALE); const newScale = Math.min(scaleX, scaleY, SCALE);
setStageSize({ width, height, scale: newScale });
setStageSize({
width: A4_WIDTH,
height: A4_HEIGHT,
scale: newScale,
});
}; };
handleResize(); handleResize();
window.addEventListener("resize", handleResize); window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize);
}, []); }, [catalogSize]);
return stageSize; return stageSize;
}; };
+1 -1
View File
@@ -58,7 +58,7 @@ const Viewer: FC = () => {
return ( return (
<div className="w-full h-full"> <div className="w-full h-full">
<BookViewer pages={pages} /> <BookViewer pages={pages} catalogSize={data?.data?.size} />
</div> </div>
); );
}; };
+9 -3
View File
@@ -5,6 +5,8 @@ import type { EditorObject } from '@/pages/editor/store/editorStore';
type BookPageProps = { type BookPageProps = {
page: PageData; page: PageData;
scale?: number; scale?: number;
pageWidth?: number;
pageHeight?: number;
onLinkClick?: (linkUrl: string) => void; onLinkClick?: (linkUrl: string) => void;
}; };
@@ -15,7 +17,8 @@ type BookPageProps = {
* این کامپوننت به عنوان child برای HTMLFlipBook استفاده می‌شود * این کامپوننت به عنوان child برای HTMLFlipBook استفاده می‌شود
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند * نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
*/ */
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1, onLinkClick }, ref) => { const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
({ page, scale = 1, pageWidth, pageHeight, onLinkClick }, ref) => {
// تابع برای تبدیل opacity به رنگ (مثل editor) // تابع برای تبدیل opacity به رنگ (مثل editor)
// در editor، getColorWithOpacity انتظار opacity 0-100 دارد // در editor، getColorWithOpacity انتظار opacity 0-100 دارد
// در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است // در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
@@ -462,6 +465,9 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1, o
} }
}; };
const width = pageWidth ?? 794 * scale;
const height = pageHeight ?? 1123 * scale;
return ( return (
<div <div
ref={ref} ref={ref}
@@ -472,8 +478,8 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1, o
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
overflow: 'hidden', overflow: 'hidden',
boxSizing: 'border-box', boxSizing: 'border-box',
width: `${794 * scale}px`, width: `${width}px`,
height: `${1123 * scale}px`, height: `${height}px`,
isolation: 'isolate', isolation: 'isolate',
zIndex: 1, zIndex: 1,
}} }}
+38 -10
View File
@@ -1,11 +1,18 @@
import { type FC, useRef, useCallback, useState, useEffect } from 'react'; import { type FC, useRef, useCallback, useState, useEffect, useMemo } from 'react';
import HTMLFlipBook from 'react-pageflip'; import HTMLFlipBook from 'react-pageflip';
import { type PageData } from '../types'; import { type PageData } from '../types';
import BookPage from './BookPage'; import BookPage from './BookPage';
import { ArrowLeft2, ArrowRight2 } from 'iconsax-react'; import { ArrowLeft2, ArrowRight2 } from 'iconsax-react';
import { getPaperDimensions } from '@/config/paperSizes';
type BookViewerProps = { const VIEWER_SCALE = 0.5;
// سایز کتاب همیشه A4 است (ثابت)
const BOOK_PAPER_SIZE = 'a4';
export type BookViewerProps = {
pages: PageData[]; pages: PageData[];
catalogSize?: string;
}; };
interface PageFlipAPI { interface PageFlipAPI {
@@ -27,10 +34,24 @@ interface FlipEvent {
* این کامپوننت از HTMLFlipBook استفاده می‌کند که یک wrapper برای PageFlip است * این کامپوننت از HTMLFlipBook استفاده می‌کند که یک wrapper برای PageFlip است
* از HTML برای رندر صفحات استفاده می‌شود (نه Canvas) برای سازگاری با Konva در آینده * از HTML برای رندر صفحات استفاده می‌شود (نه Canvas) برای سازگاری با Konva در آینده
*/ */
const BookViewer: FC<BookViewerProps> = ({ pages }) => { const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
const bookRef = useRef<FlipBookInstance | null>(null); const bookRef = useRef<FlipBookInstance | null>(null);
const [currentPage, setCurrentPage] = useState(0); const [currentPage, setCurrentPage] = useState(0);
// سایز کتاب ثابت (همیشه A4)
const { width: bookWidth, height: bookHeight } = useMemo(
() => getPaperDimensions(BOOK_PAPER_SIZE),
[]
);
const displayWidth = Math.round(bookWidth * VIEWER_SCALE);
const displayHeight = Math.round(bookHeight * VIEWER_SCALE);
// نسبت محتوا بر اساس catalogSize (A3/A4/A5)
const contentScale = useMemo(() => {
const { width: contentWidth } = getPaperDimensions(catalogSize ?? 'a4');
return VIEWER_SCALE * (bookWidth / contentWidth);
}, [catalogSize]);
// اطمینان از اینکه currentPage همیشه در محدوده معتبر است // اطمینان از اینکه currentPage همیشه در محدوده معتبر است
useEffect(() => { useEffect(() => {
if (currentPage < 0) { if (currentPage < 0) {
@@ -156,13 +177,13 @@ const BookViewer: FC<BookViewerProps> = ({ pages }) => {
<div style={{ overflow: 'hidden', isolation: 'isolate' }}> <div style={{ overflow: 'hidden', isolation: 'isolate' }}>
<HTMLFlipBook <HTMLFlipBook
ref={bookRef} ref={bookRef}
width={397} width={displayWidth}
height={561} height={displayHeight}
size="fixed" size="fixed"
minWidth={397} minWidth={displayWidth}
minHeight={561} minHeight={displayHeight}
maxWidth={397} maxWidth={displayWidth}
maxHeight={561} maxHeight={displayHeight}
drawShadow={true} drawShadow={true}
flippingTime={800} flippingTime={800}
usePortrait={false} usePortrait={false}
@@ -184,7 +205,14 @@ const BookViewer: FC<BookViewerProps> = ({ pages }) => {
onFlip={handlePageFlip} onFlip={handlePageFlip}
> >
{pages.map((page) => ( {pages.map((page) => (
<BookPage key={page.id} page={page} scale={0.5} onLinkClick={handleLinkClick} /> <BookPage
key={page.id}
page={page}
scale={contentScale}
pageWidth={displayWidth}
pageHeight={displayHeight}
onLinkClick={handleLinkClick}
/>
))} ))}
</HTMLFlipBook> </HTMLFlipBook>
</div> </div>