diff --git a/public/viewer-data.json b/public/viewer-data.json
new file mode 100644
index 0000000..85a40c5
--- /dev/null
+++ b/public/viewer-data.json
@@ -0,0 +1,539 @@
+{
+ "pages": [
+ {
+ "id": "page-1",
+ "name": "صفحه 1",
+ "objects": [
+ {
+ "id": "text-1",
+ "type": "text",
+ "x": 100,
+ "y": 100,
+ "text": "خوش آمدید به صفحه اول",
+ "fontSize": 36,
+ "fontFamily": "irancell",
+ "fontWeight": "bold",
+ "fill": "#1a1a1a",
+ "opacity": 1
+ },
+ {
+ "id": "image-1",
+ "type": "image",
+ "x": 100,
+ "y": 200,
+ "width": 300,
+ "height": 300,
+ "imageUrl": "https://picsum.photos/200/300",
+ "opacity": 1
+ },
+ {
+ "id": "rectangle-1",
+ "type": "rectangle",
+ "x": 450,
+ "y": 200,
+ "width": 200,
+ "height": 150,
+ "fill": "#3b82f6",
+ "stroke": "#1e40af",
+ "strokeWidth": 2,
+ "shapeType": "square",
+ "opacity": 1
+ },
+ {
+ "id": "circle-1",
+ "type": "rectangle",
+ "x": 500,
+ "y": 400,
+ "width": 150,
+ "height": 150,
+ "fill": "#ef4444",
+ "stroke": "#dc2626",
+ "strokeWidth": 2,
+ "shapeType": "circle",
+ "opacity": 1
+ },
+ {
+ "id": "video-1",
+ "type": "video",
+ "x": 100,
+ "y": 550,
+ "width": 400,
+ "height": 300,
+ "videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
+ "opacity": 1
+ }
+ ]
+ },
+ {
+ "id": "page-2",
+ "name": "صفحه 2",
+ "objects": [
+ {
+ "id": "text-2",
+ "type": "text",
+ "x": 100,
+ "y": 100,
+ "text": "فصل اول: مقدمه",
+ "fontSize": 32,
+ "fontFamily": "irancell",
+ "fontWeight": "bold",
+ "fill": "#000000",
+ "opacity": 1
+ },
+ {
+ "id": "line-1",
+ "type": "line",
+ "x": 100,
+ "y": 180,
+ "width": 500,
+ "height": 2,
+ "stroke": "#64748b",
+ "strokeWidth": 3,
+ "opacity": 1
+ },
+ {
+ "id": "arrow-1",
+ "type": "arrow",
+ "x": 200,
+ "y": 250,
+ "width": 300,
+ "height": 100,
+ "stroke": "#f59e0b",
+ "strokeWidth": 4,
+ "opacity": 1
+ },
+ {
+ "id": "triangle-1",
+ "type": "rectangle",
+ "x": 550,
+ "y": 250,
+ "width": 150,
+ "height": 150,
+ "fill": "#10b981",
+ "stroke": "#059669",
+ "strokeWidth": 2,
+ "shapeType": "triangle",
+ "opacity": 1
+ },
+ {
+ "id": "link-1",
+ "type": "link",
+ "x": 100,
+ "y": 400,
+ "width": 200,
+ "height": 50,
+ "text": "لینک به صفحه وب",
+ "fontSize": 18,
+ "linkUrl": "https://example.com",
+ "fill": "#3b82f6",
+ "opacity": 1
+ },
+ {
+ "id": "image-2",
+ "type": "image",
+ "x": 350,
+ "y": 400,
+ "width": 250,
+ "height": 200,
+ "imageUrl": "https://picsum.photos/200/300",
+ "opacity": 1
+ }
+ ]
+ },
+ {
+ "id": "page-3",
+ "name": "صفحه 3",
+ "objects": [
+ {
+ "id": "text-3",
+ "type": "text",
+ "x": 100,
+ "y": 100,
+ "text": "صفحه سوم\nاین صفحه شامل جدول است",
+ "fontSize": 28,
+ "fontFamily": "irancell",
+ "fontWeight": "normal",
+ "fill": "#1a1a1a",
+ "opacity": 1
+ },
+ {
+ "id": "grid-1",
+ "type": "grid",
+ "x": 100,
+ "y": 250,
+ "width": 500,
+ "height": 300,
+ "tableData": {
+ "id": "table-1",
+ "rows": 4,
+ "cols": 5,
+ "cellWidth": 100,
+ "cellHeight": 75,
+ "cells": {
+ "cell-0-0": {
+ "id": "cell-0-0",
+ "text": "ردیف 1",
+ "background": "#f3f4f6",
+ "width": 100,
+ "height": 75
+ },
+ "cell-0-1": {
+ "id": "cell-0-1",
+ "text": "ستون 1",
+ "background": "#e5e7eb",
+ "width": 100,
+ "height": 75
+ },
+ "cell-0-2": {
+ "id": "cell-0-2",
+ "text": "ستون 2",
+ "background": "#e5e7eb",
+ "width": 100,
+ "height": 75
+ },
+ "cell-0-3": {
+ "id": "cell-0-3",
+ "text": "ستون 3",
+ "background": "#e5e7eb",
+ "width": 100,
+ "height": 75
+ },
+ "cell-0-4": {
+ "id": "cell-0-4",
+ "text": "ستون 4",
+ "background": "#e5e7eb",
+ "width": 100,
+ "height": 75
+ },
+ "cell-1-0": {
+ "id": "cell-1-0",
+ "text": "ردیف 2",
+ "background": "#f3f4f6",
+ "width": 100,
+ "height": 75
+ },
+ "cell-1-1": {
+ "id": "cell-1-1",
+ "text": "داده 1",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-1-2": {
+ "id": "cell-1-2",
+ "text": "داده 2",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-1-3": {
+ "id": "cell-1-3",
+ "text": "داده 3",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-1-4": {
+ "id": "cell-1-4",
+ "text": "داده 4",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-2-0": {
+ "id": "cell-2-0",
+ "text": "ردیف 3",
+ "background": "#f3f4f6",
+ "width": 100,
+ "height": 75
+ },
+ "cell-2-1": {
+ "id": "cell-2-1",
+ "text": "داده 5",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-2-2": {
+ "id": "cell-2-2",
+ "text": "داده 6",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-2-3": {
+ "id": "cell-2-3",
+ "text": "داده 7",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-2-4": {
+ "id": "cell-2-4",
+ "text": "داده 8",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-3-0": {
+ "id": "cell-3-0",
+ "text": "ردیف 4",
+ "background": "#f3f4f6",
+ "width": 100,
+ "height": 75
+ },
+ "cell-3-1": {
+ "id": "cell-3-1",
+ "text": "داده 9",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-3-2": {
+ "id": "cell-3-2",
+ "text": "داده 10",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-3-3": {
+ "id": "cell-3-3",
+ "text": "داده 11",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ },
+ "cell-3-4": {
+ "id": "cell-3-4",
+ "text": "داده 12",
+ "background": "#ffffff",
+ "width": 100,
+ "height": 75
+ }
+ },
+ "stroke": "#808080",
+ "strokeWidth": 1
+ },
+ "opacity": 1
+ },
+ {
+ "id": "abstract-1",
+ "type": "rectangle",
+ "x": 650,
+ "y": 250,
+ "width": 100,
+ "height": 200,
+ "fill": "#8b5cf6",
+ "stroke": "#7c3aed",
+ "strokeWidth": 2,
+ "shapeType": "abstract",
+ "opacity": 1
+ }
+ ]
+ },
+ {
+ "id": "page-4",
+ "name": "صفحه 4",
+ "objects": [
+ {
+ "id": "text-4",
+ "type": "text",
+ "x": 100,
+ "y": 100,
+ "text": "صفحه چهارم\nشامل ویدیو و تصویر",
+ "fontSize": 30,
+ "fontFamily": "irancell",
+ "fontWeight": "bold",
+ "fill": "#1a1a1a",
+ "opacity": 1
+ },
+ {
+ "id": "video-2",
+ "type": "video",
+ "x": 100,
+ "y": 200,
+ "width": 350,
+ "height": 250,
+ "videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
+ "opacity": 1
+ },
+ {
+ "id": "image-3",
+ "type": "image",
+ "x": 500,
+ "y": 200,
+ "width": 250,
+ "height": 250,
+ "imageUrl": "https://picsum.photos/200/300",
+ "opacity": 1
+ },
+ {
+ "id": "line-2",
+ "type": "line",
+ "x": 100,
+ "y": 500,
+ "width": 600,
+ "height": 2,
+ "stroke": "#94a3b8",
+ "strokeWidth": 2,
+ "opacity": 1
+ },
+ {
+ "id": "arrow-2",
+ "type": "arrow",
+ "x": 200,
+ "y": 550,
+ "width": 400,
+ "height": 80,
+ "stroke": "#ec4899",
+ "strokeWidth": 5,
+ "opacity": 1
+ },
+ {
+ "id": "circle-2",
+ "type": "rectangle",
+ "x": 650,
+ "y": 550,
+ "width": 100,
+ "height": 100,
+ "fill": "#f59e0b",
+ "stroke": "#d97706",
+ "strokeWidth": 3,
+ "shapeType": "circle",
+ "opacity": 1
+ }
+ ]
+ },
+ {
+ "id": "page-5",
+ "name": "صفحه 5",
+ "objects": [
+ {
+ "id": "text-5",
+ "type": "text",
+ "x": 100,
+ "y": 100,
+ "text": "صفحه آخر\nنمایش همه انواع object ها",
+ "fontSize": 32,
+ "fontFamily": "irancell",
+ "fontWeight": "bold",
+ "fill": "#1a1a1a",
+ "opacity": 1
+ },
+ {
+ "id": "rectangle-2",
+ "type": "rectangle",
+ "x": 100,
+ "y": 200,
+ "width": 150,
+ "height": 120,
+ "fill": "#06b6d4",
+ "stroke": "#0891b2",
+ "strokeWidth": 2,
+ "shapeType": "square",
+ "rotation": 15,
+ "opacity": 1
+ },
+ {
+ "id": "triangle-2",
+ "type": "rectangle",
+ "x": 300,
+ "y": 200,
+ "width": 120,
+ "height": 120,
+ "fill": "#14b8a6",
+ "stroke": "#0d9488",
+ "strokeWidth": 2,
+ "shapeType": "triangle",
+ "rotation": 30,
+ "opacity": 1
+ },
+ {
+ "id": "abstract-2",
+ "type": "rectangle",
+ "x": 470,
+ "y": 200,
+ "width": 100,
+ "height": 150,
+ "fill": "#a855f7",
+ "stroke": "#9333ea",
+ "strokeWidth": 2,
+ "shapeType": "abstract",
+ "rotation": -20,
+ "opacity": 1
+ },
+ {
+ "id": "image-4",
+ "type": "image",
+ "x": 620,
+ "y": 200,
+ "width": 150,
+ "height": 150,
+ "imageUrl": "https://picsum.photos/200/300",
+ "rotation": 10,
+ "opacity": 1
+ },
+ {
+ "id": "video-3",
+ "type": "video",
+ "x": 100,
+ "y": 380,
+ "width": 300,
+ "height": 200,
+ "videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
+ "opacity": 1
+ },
+ {
+ "id": "link-2",
+ "type": "link",
+ "x": 450,
+ "y": 380,
+ "width": 250,
+ "height": 60,
+ "text": "لینک به صفحه دیگر",
+ "fontSize": 20,
+ "linkUrl": "https://example.com",
+ "fill": "#6366f1",
+ "opacity": 1
+ },
+ {
+ "id": "line-3",
+ "type": "line",
+ "x": 100,
+ "y": 620,
+ "width": 200,
+ "height": 3,
+ "stroke": "#f97316",
+ "strokeWidth": 4,
+ "rotation": 45,
+ "opacity": 1
+ },
+ {
+ "id": "arrow-3",
+ "type": "arrow",
+ "x": 350,
+ "y": 620,
+ "width": 250,
+ "height": 100,
+ "stroke": "#84cc16",
+ "strokeWidth": 6,
+ "rotation": -30,
+ "opacity": 1
+ },
+ {
+ "id": "text-6",
+ "type": "text",
+ "x": 100,
+ "y": 750,
+ "text": "این یک متن نمونه است\nبا چند خط\nبرای نمایش بهتر",
+ "fontSize": 22,
+ "fontFamily": "irancell",
+ "fontWeight": "normal",
+ "fill": "#475569",
+ "letterSpacing": 1,
+ "opacity": 1
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/pages/editor/components/EditorSidebar.tsx b/src/pages/editor/components/EditorSidebar.tsx
index 8a80c79..7ac7cac 100644
--- a/src/pages/editor/components/EditorSidebar.tsx
+++ b/src/pages/editor/components/EditorSidebar.tsx
@@ -1,13 +1,13 @@
import Logo from "@/assets/images/logo.svg";
import { clx } from "@/helpers/utils";
import { useSharedStore } from "@/shared/store/sharedStore";
-import { CloseSquare } from "iconsax-react";
+import { CloseSquare, DocumentUpload } from "iconsax-react";
import { useEditorStore, type ToolType } from "../store/editorStore";
import { ToolsBar, ObjectSettings, ToolInstructions } from "./sidebar";
const EditorSidebar = () => {
const { openSidebar, setOpenSidebar } = useSharedStore();
- const { tool, setTool, selectedObjectId, objects, updateObject, deleteObject } = useEditorStore();
+ const { tool, setTool, selectedObjectId, objects, updateObject, deleteObject, loadPages } = useEditorStore();
const selectedObject = objects.find((obj) => obj.id === selectedObjectId);
@@ -15,6 +15,19 @@ const EditorSidebar = () => {
setTool(selectedTool);
};
+ const handleLoadJSON = async () => {
+ try {
+ const response = await fetch('/viewer-data.json');
+ const data = await response.json();
+ if (data.pages && Array.isArray(data.pages)) {
+ loadPages(data.pages);
+ }
+ } catch (error) {
+ console.error('خطا در لود JSON:', error);
+ alert('خطا در لود فایل JSON');
+ }
+ };
+
return (
<>
@@ -29,12 +42,21 @@ const EditorSidebar = () => {
>

-
+
+
+
+
diff --git a/src/pages/editor/components/tools/VideoShape.tsx b/src/pages/editor/components/tools/VideoShape.tsx
index bd9d434..7302a7a 100644
--- a/src/pages/editor/components/tools/VideoShape.tsx
+++ b/src/pages/editor/components/tools/VideoShape.tsx
@@ -99,10 +99,6 @@ const VideoShape = ({
}
};
- if (!image) {
- return null;
- }
-
return (
<>
-
+ {image ? (
+
+ ) : (
+
+ )}
void;
groupObjects: (objectIds: string[]) => void;
ungroupObjects: (groupId: string) => void;
+ loadPages: (pages: Page[]) => void;
};
const createTableCellId = (row: number, col: number) => `cell-${row}-${col}`;
@@ -904,5 +905,16 @@ export const useEditorStore = create((set, get) => {
const state = get();
return state.objects.filter((obj) => obj.groupId === groupId);
},
+ loadPages: (pages) => {
+ if (pages.length === 0) return;
+ const firstPage = pages[0];
+ set({
+ pages,
+ currentPageId: firstPage.id,
+ objects: firstPage.objects,
+ selectedObjectId: null,
+ selectedObjectIds: [],
+ });
+ },
};
});
diff --git a/src/pages/viewer/Viewer.tsx b/src/pages/viewer/Viewer.tsx
index c6cf52b..1eff3d0 100644
--- a/src/pages/viewer/Viewer.tsx
+++ b/src/pages/viewer/Viewer.tsx
@@ -1,11 +1,63 @@
-import { type FC } from 'react';
+import { type FC, useEffect, useState } from 'react';
import BookViewer from './components/BookViewer';
-import { mockPages } from './components/mockData';
+import { transformViewerDataToPages } from './utils/dataTransformer';
+import type { PageData } from './types';
const Viewer: FC = () => {
+ const [pages, setPages] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadViewerData = async () => {
+ try {
+ setLoading(true);
+ const response = await fetch('/viewer-data.json');
+ if (!response.ok) {
+ throw new Error('خطا در بارگذاری دادهها');
+ }
+ const data = await response.json();
+ const transformedPages = transformViewerDataToPages(data);
+ setPages(transformedPages);
+ setError(null);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'خطای نامشخص');
+ console.error('خطا در بارگذاری viewer-data.json:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadViewerData();
+ }, []);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (pages.length === 0) {
+ return (
+
+ );
+ }
+
return (
-
+
);
};
diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx
index c1c0262..8545915 100644
--- a/src/pages/viewer/components/BookPage.tsx
+++ b/src/pages/viewer/components/BookPage.tsx
@@ -1,8 +1,10 @@
import { forwardRef } from 'react';
import { type PageData } from '../types';
+import type { EditorObject } from '@/pages/editor/store/editorStore';
type BookPageProps = {
page: PageData;
+ scale?: number;
};
/**
@@ -12,7 +14,323 @@ type BookPageProps = {
* این کامپوننت به عنوان child برای HTMLFlipBook استفاده میشود
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
*/
-const BookPage = forwardRef(({ page }, ref) => {
+const BookPage = forwardRef(({ page, scale = 1 }, ref) => {
+ const renderObject = (obj: EditorObject, index: number) => {
+ const baseStyle: React.CSSProperties = {
+ position: 'absolute',
+ left: `${(obj.x || 0) * scale}px`,
+ top: `${(obj.y || 0) * scale}px`,
+ opacity: obj.opacity ?? 1,
+ transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
+ transformOrigin: 'center center',
+ };
+
+ if (obj.visible === false) {
+ return null;
+ }
+
+ switch (obj.type) {
+ case 'text':
+ return (
+
+ {obj.text || ''}
+
+ );
+
+ case 'image':
+ return (
+
+ );
+
+ case 'video':
+ return (
+
+ );
+
+ case 'link':
+ return (
+
+ {obj.text || obj.linkUrl || ''}
+
+ );
+
+ case 'rectangle':
+ if (obj.shapeType === 'circle') {
+ // در editor، circle با مرکز در x, y رندر میشود (radius = width/2)
+ // اما در viewer-data.json ممکن است x, y به عنوان گوشه بالا-چپ bounding box باشد
+ // برای سازگاری با editor، فرض میکنیم x, y مرکز است
+ const radius = ((obj.width || 100) / 2) * scale;
+ const centerX = (obj.x || 0) * scale;
+ const centerY = (obj.y || 0) * scale;
+
+ return (
+
+ );
+ }
+
+ if (obj.shapeType === 'triangle') {
+ // در editor، triangle با مرکز در x + width/2, y + height/2 رندر میشود
+ const width = (obj.width || 100) * scale;
+ const height = (obj.height || 100) * scale;
+ const radius = Math.min(width, height) / 2;
+ const centerX = (obj.x || 0) * scale + width / 2;
+ const centerY = (obj.y || 0) * scale + height / 2;
+
+ return (
+
+ );
+ }
+
+ if (obj.shapeType === 'abstract') {
+ // در editor، abstract (star) با مرکز در x + width/2, y + height/2 رندر میشود
+ const width = (obj.width || 100) * scale;
+ const height = (obj.height || 100) * scale;
+ const baseRadius = Math.min(width, height) / 2;
+ const abstractScale = 0.85;
+ const radius = baseRadius * abstractScale;
+ const centerX = (obj.x || 0) * scale + width / 2;
+ const centerY = (obj.y || 0) * scale + height / 2;
+
+ return (
+
+ );
+ }
+
+ // Rectangle عادی
+ const shapeStyle: React.CSSProperties = {
+ ...baseStyle,
+ width: `${(obj.width || 100) * scale}px`,
+ height: `${(obj.height || 100) * scale}px`,
+ backgroundColor: obj.fill || 'transparent',
+ border: obj.stroke
+ ? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
+ : 'none',
+ };
+
+ return ;
+
+ case 'line': {
+ // در EditorObject، width و height به عنوان مختصات انتهایی استفاده میشوند
+ // در Konva Line، points به صورت [0, 0, endX - startX, endY - startY] است
+ const startX = (obj.x || 0) * scale;
+ const startY = (obj.y || 0) * scale;
+ const endX = (obj.width ?? obj.x ?? 0) * scale;
+ const endY = (obj.height ?? obj.y ?? 0) * scale;
+ const dx = endX - startX;
+ const dy = endY - startY;
+ const lineLength = Math.sqrt(dx * dx + dy * dy);
+ const lineAngle = Math.atan2(dy, dx) * (180 / Math.PI);
+
+ return (
+
+ );
+ }
+
+ case 'arrow': {
+ // در EditorObject، width و height به عنوان مختصات انتهایی استفاده میشوند
+ // در Konva Arrow، points به صورت [0, 0, endX - startX, endY - startY] است
+ const startX = (obj.x || 0) * scale;
+ const startY = (obj.y || 0) * scale;
+ const endX = (obj.width ?? obj.x ?? 0) * scale;
+ const endY = (obj.height ?? obj.y ?? 0) * scale;
+ const dx = endX - startX;
+ const dy = endY - startY;
+ const arrowLength = Math.sqrt(dx * dx + dy * dy);
+ const arrowAngle = Math.atan2(dy, dx) * (180 / Math.PI);
+ const strokeWidth = (obj.strokeWidth || 4) * scale;
+
+ return (
+
+ );
+ }
+
+ case 'grid':
+ if (!obj.tableData) return null;
+
+ const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } =
+ obj.tableData;
+
+ return (
+
+ {Array.from({ length: rows }).map((_, rowIndex) =>
+ Array.from({ length: cols }).map((_, colIndex) => {
+ const cellId = `cell-${rowIndex}-${colIndex}`;
+ const cell = cells[cellId];
+
+ return (
+
+ {cell?.text || ''}
+
+ );
+ })
+ )}
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
return (
(({ page }, ref) => {
position: 'relative',
backgroundColor: '#ffffff',
overflow: 'hidden',
- padding: '40px',
boxSizing: 'border-box',
- width: '100%',
- height: '100%',
+ width: `${794 * scale}px`,
+ height: `${1123 * scale}px`,
}}
>
- {page.elements.map((element, index) => {
- if (element.type === 'text') {
- return (
-
- {element.text}
-
- );
- }
-
- if (element.type === 'image') {
- return (
-

- );
- }
-
- return null;
- })}
+ {page.elements.map((element, index) => renderObject(element, index))}
);
});
diff --git a/src/pages/viewer/components/BookViewer.tsx b/src/pages/viewer/components/BookViewer.tsx
index 5555917..d37c44f 100644
--- a/src/pages/viewer/components/BookViewer.tsx
+++ b/src/pages/viewer/components/BookViewer.tsx
@@ -56,18 +56,18 @@ const BookViewer: FC = ({ pages }) => {
/>
= ({ pages }) => {
}}
onFlip={handlePageFlip}
>
- {pages.map((page) => (
-
- ))}
+ {pages.map((page) => (
+
+ ))}
diff --git a/src/pages/viewer/types/index.ts b/src/pages/viewer/types/index.ts
index dd0e942..99b6a69 100644
--- a/src/pages/viewer/types/index.ts
+++ b/src/pages/viewer/types/index.ts
@@ -1,11 +1,9 @@
-export type PageElement =
- | { type: "text"; x: number; y: number; text: string; fontSize: number; color: string }
- | { type: "image"; x: number; y: number; src: string; width: number; height: number };
+import type { EditorObject } from '@/pages/editor/store/editorStore';
export type PageData = {
id: number;
width: number;
height: number;
- elements: PageElement[];
+ elements: EditorObject[];
};
diff --git a/src/pages/viewer/utils/dataTransformer.ts b/src/pages/viewer/utils/dataTransformer.ts
new file mode 100644
index 0000000..2ada0fc
--- /dev/null
+++ b/src/pages/viewer/utils/dataTransformer.ts
@@ -0,0 +1,118 @@
+import type { EditorObject } from '@/pages/editor/store/editorStore';
+import type { PageData } from '../types';
+
+type ViewerDataPage = {
+ id: string;
+ name: string;
+ objects: Array<{
+ id: string;
+ type: string;
+ x: number;
+ y: number;
+ width?: number;
+ height?: number;
+ text?: string;
+ fontSize?: number;
+ fontFamily?: string;
+ fontWeight?: string;
+ fill?: string;
+ stroke?: string;
+ strokeWidth?: number;
+ shapeType?: string;
+ imageUrl?: string;
+ videoUrl?: string;
+ linkUrl?: string;
+ rotation?: number;
+ opacity?: number;
+ letterSpacing?: number;
+ tableData?: any;
+ }>;
+};
+
+type ViewerData = {
+ pages: ViewerDataPage[];
+};
+
+/**
+ * تبدیل دادههای viewer-data.json به فرمت PageData
+ */
+export function transformViewerDataToPages(data: ViewerData): PageData[] {
+ return data.pages.map((page, index) => {
+ const objects: EditorObject[] = page.objects.map((obj) => {
+ const baseObject: EditorObject = {
+ id: obj.id,
+ type: obj.type as EditorObject['type'],
+ x: obj.x,
+ y: obj.y,
+ opacity: obj.opacity ?? 1,
+ visible: true,
+ };
+
+ // برای line و arrow، width و height در viewer-data.json به عنوان اندازه هستند
+ // اما در EditorObject به عنوان مختصات انتهایی استفاده میشوند
+ if (obj.type === 'line' || obj.type === 'arrow') {
+ if (obj.width !== undefined) {
+ baseObject.width = obj.x + obj.width;
+ }
+ if (obj.height !== undefined) {
+ // برای line، height ممکن است ضخامت باشد، اما برای arrow طول عمودی است
+ baseObject.height = obj.y + obj.height;
+ }
+ } else {
+ if (obj.width !== undefined) baseObject.width = obj.width;
+ if (obj.height !== undefined) baseObject.height = obj.height;
+ }
+
+ // برای circle در viewer-data.json، x, y به عنوان گوشه بالا-چپ bounding box است
+ // اما در editor، circle با مرکز در x, y ذخیره میشود
+ // پس باید تبدیل کنیم
+ if (obj.type === 'rectangle' && obj.shapeType === 'circle') {
+ // تبدیل از گوشه بالا-چپ به مرکز
+ baseObject.x = obj.x + (obj.width || 100) / 2;
+ baseObject.y = obj.y + (obj.height || 100) / 2;
+ }
+ if (obj.fill !== undefined) baseObject.fill = obj.fill;
+ if (obj.stroke !== undefined) baseObject.stroke = obj.stroke;
+ if (obj.strokeWidth !== undefined) baseObject.strokeWidth = obj.strokeWidth;
+ if (obj.rotation !== undefined) baseObject.rotation = obj.rotation;
+
+ // ویژگیهای متنی
+ if (obj.text !== undefined) baseObject.text = obj.text;
+ if (obj.fontSize !== undefined) baseObject.fontSize = obj.fontSize;
+ if (obj.fontFamily !== undefined) baseObject.fontFamily = obj.fontFamily;
+ if (obj.fontWeight !== undefined) baseObject.fontWeight = obj.fontWeight;
+ if (obj.letterSpacing !== undefined) baseObject.letterSpacing = obj.letterSpacing;
+
+ // انواع خاص objectها
+ if (obj.type === 'rectangle' && obj.shapeType) {
+ baseObject.shapeType = obj.shapeType as EditorObject['shapeType'];
+ }
+
+ if (obj.type === 'image' && obj.imageUrl) {
+ baseObject.imageUrl = obj.imageUrl;
+ }
+
+ if (obj.type === 'video' && obj.videoUrl) {
+ baseObject.videoUrl = obj.videoUrl;
+ }
+
+ if (obj.type === 'link' && obj.linkUrl) {
+ baseObject.linkUrl = obj.linkUrl;
+ }
+
+ if (obj.type === 'grid' && obj.tableData) {
+ baseObject.tableData = obj.tableData;
+ }
+
+ return baseObject;
+ });
+
+ return {
+ id: index + 1,
+ width: 794,
+ height: 1123,
+ elements: objects,
+ };
+ });
+}
+