diff --git a/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx b/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx index 86c4152..c3e3f63 100644 --- a/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx @@ -2,7 +2,11 @@ import Select from "@/components/Select"; import Input from "@/components/Input"; import type { EditorObject } from "../../../store/editorStore"; import { ENTRANCE_ANIMATION_SELECT_ITEMS } from "../constants/entranceAnimationOptions"; -import { DEFAULT_ENTRANCE_DURATION_MS } from "@/shared/entranceAnimation"; +import { + DEFAULT_ENTRANCE_DURATION_MS, + DEFAULT_ENTRANCE_DELAY_MS, + MAX_ENTRANCE_DELAY_MS, +} from "@/shared/entranceAnimation"; type Props = { selectedObject: EditorObject; @@ -12,6 +16,7 @@ type Props = { const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => { const anim = selectedObject.entranceAnimation ?? "none"; const duration = selectedObject.entranceDurationMs ?? DEFAULT_ENTRANCE_DURATION_MS; + const delayMs = selectedObject.entranceDelayMs ?? DEFAULT_ENTRANCE_DELAY_MS; return (
@@ -23,12 +28,14 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => { const v = e.target.value as EditorObject["entranceAnimation"]; onUpdate(selectedObject.id, { entranceAnimation: v === "none" ? undefined : v, - ...(v === "none" || !v ? { entranceDurationMs: undefined } : {}), + ...(v === "none" || !v + ? { entranceDurationMs: undefined, entranceDelayMs: undefined } + : {}), }); }} /> {anim !== "none" && ( - { onUpdate(selectedObject.id, { entranceDurationMs: Math.min(4000, Math.max(100, n)), }); - }} - /> + }} /> { + const n = parseInt(e.target.value, 10); + if (Number.isNaN(n)) return; + const clamped = Math.min(MAX_ENTRANCE_DELAY_MS, Math.max(0, n)); + onUpdate(selectedObject.id, { + entranceDelayMs: clamped === 0 ? undefined : clamped, + }); + }} /> )}
); diff --git a/src/pages/editor/store/editorStore.types.ts b/src/pages/editor/store/editorStore.types.ts index 1c3e0a9..f3d6380 100644 --- a/src/pages/editor/store/editorStore.types.ts +++ b/src/pages/editor/store/editorStore.types.ts @@ -85,6 +85,8 @@ export type EditorObject = { entranceAnimation?: EntranceAnimationType; /** مدت انیمیشن ورود به میلی‌ثانیه (پیش‌فرض ۶۰۰) */ entranceDurationMs?: number; + /** تأخیر قبل از شروع انیمیشن ورود به میلی‌ثانیه (پیش‌فرض ۰) */ + entranceDelayMs?: number; }; export type PageGuide = { diff --git a/src/pages/viewer/utils/dataTransformer.ts b/src/pages/viewer/utils/dataTransformer.ts index e427757..dbf884e 100644 --- a/src/pages/viewer/utils/dataTransformer.ts +++ b/src/pages/viewer/utils/dataTransformer.ts @@ -45,6 +45,7 @@ type ViewerDataPage = { tableData?: TableData; entranceAnimation?: EditorObject["entranceAnimation"]; entranceDurationMs?: number; + entranceDelayMs?: number; }>; }; @@ -142,6 +143,9 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] { if (obj.entranceDurationMs !== undefined) { baseObject.entranceDurationMs = obj.entranceDurationMs; } + if (obj.entranceDelayMs !== undefined) { + baseObject.entranceDelayMs = obj.entranceDelayMs; + } return baseObject; }); diff --git a/src/pages/viewer/utils/entranceAnimationStyle.ts b/src/pages/viewer/utils/entranceAnimationStyle.ts index 15130f5..265b447 100644 --- a/src/pages/viewer/utils/entranceAnimationStyle.ts +++ b/src/pages/viewer/utils/entranceAnimationStyle.ts @@ -2,7 +2,9 @@ import type { CSSProperties } from "react"; import type { EditorObject } from "@/pages/editor/store/editorStore"; import { DEFAULT_ENTRANCE_DURATION_MS, + DEFAULT_ENTRANCE_DELAY_MS, ENTRANCE_ANIMATION_BASE_DELAY_MS, + MAX_ENTRANCE_DELAY_MS, } from "@/shared/entranceAnimation"; const ANIMATION_NAMES: Record< @@ -76,6 +78,11 @@ export function mergeEntranceAnimationStyle( 1000; const baseDelaySec = ENTRANCE_ANIMATION_BASE_DELAY_MS / 1000; const staggerSec = Math.min(elementIndex * 0.045, 1.2); + const userDelayMs = Math.min( + MAX_ENTRANCE_DELAY_MS, + Math.max(DEFAULT_ENTRANCE_DELAY_MS, obj.entranceDelayMs ?? DEFAULT_ENTRANCE_DELAY_MS), + ); + const userDelaySec = userDelayMs / 1000; const rot = options?.rotationDeg ?? obj.rotation ?? 0; const opacityEnd = (obj.opacity ?? 100) / 100; @@ -102,7 +109,7 @@ export function mergeEntranceAnimationStyle( willChange: "transform, opacity", animationName: name, animationDuration: `${durationSec}s`, - animationDelay: `${baseDelaySec + staggerSec}s`, + animationDelay: `${baseDelaySec + staggerSec + userDelaySec}s`, animationTimingFunction: "cubic-bezier(0.22, 1, 0.36, 1)", animationFillMode: "both", }; diff --git a/src/shared/entranceAnimation.ts b/src/shared/entranceAnimation.ts index ffa37a9..2c67bbc 100644 --- a/src/shared/entranceAnimation.ts +++ b/src/shared/entranceAnimation.ts @@ -1,5 +1,11 @@ /** مدت پیش‌فرض انیمیشن ورود در editor و viewer (میلی‌ثانیه) */ export const DEFAULT_ENTRANCE_DURATION_MS = 1000; +/** تأخیر اضافه‌شده توسط کاربر قبل از شروع انیمیشن (میلی‌ثانیه؛ پیش‌فرض ۰) */ +export const DEFAULT_ENTRANCE_DELAY_MS = 0; + +/** سقف تأخیر کاربر (میلی‌ثانیه) */ +export const MAX_ENTRANCE_DELAY_MS = 30_000; + /** تأخیر کوتاه قبل از شروع انیمیشن ورود تا با ورق خوردن صفحه هم‌هماهنگ شود (کمتر = حس فوری‌تر) */ export const ENTRANCE_ANIMATION_BASE_DELAY_MS = 75;