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 ( +
+
خطا: {error}
+
+ ); + } + + 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 ( +