diff --git a/.env b/.env index e11c74f..2aa0616 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -VITE_API_URL = 'http://188.121.103.8:4141' -# VITE_API_URL = 'http://188.121.103.8:4141' +VITE_API_URL = 'http://89.32.249.26:4010' +# VITE_API_URL = 'http://192.168.99.131:4000' VITE_TOKEN_NAME = 'dpage-editor-t' VITE_REFRESH_TOKEN_NAME = 'dpage-editor-refresh-t' \ No newline at end of file diff --git a/src/pages/editor/components/EditorCanvas.tsx b/src/pages/editor/components/EditorCanvas.tsx index 89d9f0a..d5a4f07 100644 --- a/src/pages/editor/components/EditorCanvas.tsx +++ b/src/pages/editor/components/EditorCanvas.tsx @@ -23,6 +23,11 @@ import ZoomControls from "./ZoomControls"; import EditorCanvasToolbar from "./EditorCanvasToolbar"; import { createScopedId } from "../store/editorStore.helpers"; import { getKonvaGradientProps } from "../utils/gradient"; +import { STICKER_DRAG_MIME } from "../types/IconTypes"; +import { + createStickerObject, + scaleStickerDimensions, +} from "../utils/stickerUtils"; type EditorCanvasProps = { catalogSize?: string; @@ -50,6 +55,9 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => { setLayerRef, setGuides, zoom, + addObject, + setSelectedObjectId, + setTool, } = useEditorStore(); const isSmartGuideEnabled = documentSettings.smartGuide; const currentPage = pages.find((page) => page.id === currentPageId); @@ -360,6 +368,39 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => { const scaledW = stageSize.width * finalScale; const scaledH = stageSize.height * finalScale; + + const handleStickerDragOver = (e: React.DragEvent) => { + if (!e.dataTransfer.types.includes(STICKER_DRAG_MIME)) return; + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + }; + + const placeStickerAt = (imageUrl: string, stageX: number, stageY: number, width: number, height: number) => { + const sticker = createStickerObject(imageUrl, stageX, stageY, width, height); + addObject(sticker); + setSelectedObjectId(sticker.id); + setTool("select"); + }; + + const handleStickerDrop = (e: React.DragEvent) => { + const imageUrl = e.dataTransfer.getData(STICKER_DRAG_MIME); + if (!imageUrl || !stageWrapRef.current) return; + e.preventDefault(); + + const rect = stageWrapRef.current.getBoundingClientRect(); + const stageX = (e.clientX - rect.left) / finalScale; + const stageY = (e.clientY - rect.top) / finalScale; + + const img = new Image(); + img.onload = () => { + const { width, height } = scaleStickerDimensions(img.naturalWidth, img.naturalHeight); + placeStickerAt(imageUrl, stageX, stageY, width, height); + }; + img.onerror = () => { + placeStickerAt(imageUrl, stageX, stageY, 100, 100); + }; + img.src = imageUrl; + }; const backgroundGradientProps = getKonvaGradientProps( currentPage?.backgroundType === "gradient" ? "gradient" : "solid", bgGradient, @@ -410,7 +451,12 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => { title="Drag to create horizontal guide" />
-
+
; break; + case "sticker": + shapeElement = ; + break; case "grid": shapeElement = ( ( -
-

برای افزودن استیکر، روی کانوس کلیک کنید

+import { useMemo } from "react"; +import { useGetIconGroups } from "@/pages/editor/hooks/useIconData"; +import { STICKER_DRAG_MIME } from "@/pages/editor/types/IconTypes"; + +const StickerInstruction = () => { + const { data, isLoading, isError } = useGetIconGroups(); + + const stickers = useMemo(() => { + const groups = data?.data ?? []; + return groups.flatMap((group) => group.icons); + }, [data]); + + const handleDragStart = (e: React.DragEvent, url: string) => { + e.dataTransfer.setData(STICKER_DRAG_MIME, url); + e.dataTransfer.effectAllowed = "copy"; + }; + + return ( +
+
استیکر
+

+ استیکر را بکشید و روی صفحه رها کنید +

+ + {isLoading && ( +

در حال بارگذاری استیکرها...

+ )} + + {isError && ( +

خطا در بارگذاری استیکرها

+ )} + + {!isLoading && !isError && stickers.length === 0 && ( +

استیکری یافت نشد

+ )} + + {stickers.length > 0 && ( +
+ {stickers.map((icon) => ( +
+ handleDragStart(e, icon.url)} + className="max-h-full max-w-full object-contain cursor-grab active:cursor-grabbing" + /> +
+ ))} +
+ )}
-); + ); +}; export default StickerInstruction; - - diff --git a/src/pages/editor/hooks/useIconData.ts b/src/pages/editor/hooks/useIconData.ts new file mode 100644 index 0000000..5a4a827 --- /dev/null +++ b/src/pages/editor/hooks/useIconData.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/IconService"; + +export const useGetIconGroups = () => { + return useQuery({ + queryKey: ["icon-groups"], + queryFn: api.getIconGroups, + staleTime: 5 * 60 * 1000, + }); +}; diff --git a/src/pages/editor/service/IconService.ts b/src/pages/editor/service/IconService.ts new file mode 100644 index 0000000..3cb27fa --- /dev/null +++ b/src/pages/editor/service/IconService.ts @@ -0,0 +1,7 @@ +import axios from "@/config/axios"; +import type { IconGroupsResponse } from "../types/IconTypes"; + +export const getIconGroups = async () => { + const { data } = await axios.get("/admin/groups/icons"); + return data; +}; diff --git a/src/pages/editor/types/IconTypes.ts b/src/pages/editor/types/IconTypes.ts new file mode 100644 index 0000000..649d29f --- /dev/null +++ b/src/pages/editor/types/IconTypes.ts @@ -0,0 +1,23 @@ +import type { BaseResponse } from "@/shared/types/SharedTypes"; + +export type IconItem = { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + url: string; + group: string; +}; + +export type IconGroup = { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + name: string; + icons: IconItem[]; +}; + +export type IconGroupsResponse = BaseResponse; + +export const STICKER_DRAG_MIME = "application/x-dpage-sticker"; diff --git a/src/pages/editor/utils/exportUtils.ts b/src/pages/editor/utils/exportUtils.ts index 7360418..d0750ad 100644 --- a/src/pages/editor/utils/exportUtils.ts +++ b/src/pages/editor/utils/exportUtils.ts @@ -117,7 +117,7 @@ const drawObject = async ( ctx.scale(object.scaleX || 1, object.scaleY || 1); // برای image objects - if (object.type === "image" || object.type === "document") { + if (object.type === "image" || object.type === "document" || object.type === "sticker") { if (object.imageUrl) { const img = new Image(); img.crossOrigin = "anonymous"; diff --git a/src/pages/editor/utils/stickerUtils.ts b/src/pages/editor/utils/stickerUtils.ts new file mode 100644 index 0000000..03da14a --- /dev/null +++ b/src/pages/editor/utils/stickerUtils.ts @@ -0,0 +1,36 @@ +import type { ToolType } from "../store/editorStore.types"; + +const STICKER_MAX_SIZE = 120; + +export const scaleStickerDimensions = ( + naturalWidth: number, + naturalHeight: number, + maxSize = STICKER_MAX_SIZE, +) => { + let width = naturalWidth; + let height = naturalHeight; + + if (width > maxSize || height > maxSize) { + const ratio = Math.min(maxSize / width, maxSize / height); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + } + + return { width, height }; +}; + +export const createStickerObject = ( + imageUrl: string, + x: number, + y: number, + width: number, + height: number, +) => ({ + id: `sticker-${Date.now()}`, + type: "sticker" as ToolType, + x: Math.round(x - width / 2), + y: Math.round(y - height / 2), + width, + height, + imageUrl, +}); diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index d93a6f3..cc89b5d 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -172,6 +172,7 @@ const BookPage = forwardRef( } case 'image': + case 'sticker': return (