scale viewer
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user