From 0628340a5dc7159caf7cf866b821cf5256d670be Mon Sep 17 00:00:00 2001 From: Hamid Date: Sat, 16 May 2026 04:50:17 -0700 Subject: [PATCH] fix animation --- src/pages/editor/Editor.tsx | 12 ++- .../settings/EntranceAnimationSettings.tsx | 70 ++++++++++---- src/pages/editor/store/editorStore.ts | 92 ++++++------------- 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/src/pages/editor/Editor.tsx b/src/pages/editor/Editor.tsx index 1bf19aa..368ce82 100644 --- a/src/pages/editor/Editor.tsx +++ b/src/pages/editor/Editor.tsx @@ -23,7 +23,7 @@ const Editor: FC = () => { }, []) const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false) const [isInitialContentReady, setIsInitialContentReady] = useState(false) - const { loadPages, pages, documentSettings } = useEditorStore() + const { loadPages, pages, documentSettings, objects, currentPageId } = useEditorStore() const { data, isFetched } = useGetCatalogById(id!) const { scheduleUpdate, isSaving } = useUpdateCatalog() @@ -51,13 +51,19 @@ const Editor: FC = () => { if (!isInitialContentReady) return if (!pages || pages.length === 0) return + const pagesToSave = currentPageId + ? pages.map((p) => + p.id === currentPageId ? { ...p, objects } : p, + ) + : pages + scheduleUpdate({ id, - content: JSON.stringify({ pages, documentSettings }), + content: JSON.stringify({ pages: pagesToSave, documentSettings }), }) // عمداً scheduleUpdate را در وابستگی‌ها نیاوردیم تا از لوپ ذخیره‌سازی جلوگیری شود // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, isInitialContentReady, pages, documentSettings]) + }, [id, isInitialContentReady, pages, documentSettings, objects, currentPageId]) return ( diff --git a/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx b/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx index c3e3f63..a008d91 100644 --- a/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/EntranceAnimationSettings.tsx @@ -1,6 +1,7 @@ import Select from "@/components/Select"; import Input from "@/components/Input"; import type { EditorObject } from "../../../store/editorStore"; +import { useEditorStore } from "../../../store/editorStore"; import { ENTRANCE_ANIMATION_SELECT_ITEMS } from "../constants/entranceAnimationOptions"; import { DEFAULT_ENTRANCE_DURATION_MS, @@ -13,10 +14,34 @@ type Props = { onUpdate: (id: string, updates: Partial) => void; }; +/** اعضای گروه (نه خود باکس group) برای تنظیم انیمیشن ورود */ +function getAnimationTargets( + selectedObject: EditorObject, + objects: EditorObject[], +): EditorObject[] { + if (selectedObject.type === "group") { + return objects.filter( + (o) => o.groupId === selectedObject.id && o.type !== "group", + ); + } + return [selectedObject]; +} + 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; + const objects = useEditorStore((s) => s.objects); + const targets = getAnimationTargets(selectedObject, objects); + const primary = targets[0] ?? selectedObject; + + const anim = primary.entranceAnimation ?? "none"; + + const duration = primary.entranceDurationMs ?? DEFAULT_ENTRANCE_DURATION_MS; + const delayMs = primary.entranceDelayMs ?? DEFAULT_ENTRANCE_DELAY_MS; + + const applyToTargets = (updates: Partial) => { + const ids = + targets.length > 0 ? targets.map((t) => t.id) : [selectedObject.id]; + ids.forEach((id) => onUpdate(id, updates)); + }; return (
@@ -26,7 +51,7 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => { value={anim} onChange={(e) => { const v = e.target.value as EditorObject["entranceAnimation"]; - onUpdate(selectedObject.id, { + applyToTargets({ entranceAnimation: v === "none" ? undefined : v, ...(v === "none" || !v ? { entranceDurationMs: undefined, entranceDelayMs: undefined } @@ -35,19 +60,22 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => { }} /> {anim !== "none" && ( - <> { - const n = parseInt(e.target.value, 10); - if (Number.isNaN(n)) return; - onUpdate(selectedObject.id, { - entranceDurationMs: Math.min(4000, Math.max(100, n)), - }); - }} /> + { + const n = parseInt(e.target.value, 10); + if (Number.isNaN(n)) return; + applyToTargets({ + 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, { + applyToTargets({ entranceDelayMs: clamped === 0 ? undefined : clamped, }); - }} /> + }} + /> + )}
); }; -export default EntranceAnimationSettings; +export default EntranceAnimationSettings; \ No newline at end of file diff --git a/src/pages/editor/store/editorStore.ts b/src/pages/editor/store/editorStore.ts index 3006922..8aecf61 100644 --- a/src/pages/editor/store/editorStore.ts +++ b/src/pages/editor/store/editorStore.ts @@ -30,6 +30,22 @@ const cloneObjectList = (objects: EditorObject[]): EditorObject[] => const objectListsEqual = (a: EditorObject[], b: EditorObject[]) => JSON.stringify(a) === JSON.stringify(b); +/** یک آرایهٔ objects برای state و صفحهٔ جاری در pages (جلوگیری از از دست رفتن فیلدهایی مثل entranceAnimation) */ +const withSyncedCurrentPageObjects = ( + state: { objects: EditorObject[]; pages: Page[]; currentPageId: string | null }, + nextObjects: EditorObject[], +) => { + if (!state.currentPageId) { + return { objects: nextObjects }; + } + return { + objects: nextObjects, + pages: state.pages.map((p) => + p.id === state.currentPageId ? { ...p, objects: nextObjects } : p, + ), + }; +}; + let objectGestureSnapshot: EditorObject[] | null = null; export type { @@ -248,7 +264,6 @@ export const useEditorStore = create((set, get) => { }); }, beginObjectGesture: () => { - if (objectGestureSnapshot !== null) return; objectGestureSnapshot = cloneObjectList(get().objects); }, endObjectGesture: () => { @@ -336,12 +351,7 @@ export const useEditorStore = create((set, get) => { if (i === -1 || i >= objs.length - 1) return; [objs[i], objs[i + 1]] = [objs[i + 1]!, objs[i]!]; if (state.currentPageId) { - set({ - objects: objs, - pages: state.pages.map((p) => - p.id === state.currentPageId ? { ...p, objects: objs } : p, - ), - }); + set(withSyncedCurrentPageObjects(state, objs)); return; } set({ objects: objs }); @@ -354,12 +364,7 @@ export const useEditorStore = create((set, get) => { if (i <= 0) return; [objs[i], objs[i - 1]] = [objs[i - 1]!, objs[i]!]; if (state.currentPageId) { - set({ - objects: objs, - pages: state.pages.map((p) => - p.id === state.currentPageId ? { ...p, objects: objs } : p, - ), - }); + set(withSyncedCurrentPageObjects(state, objs)); return; } set({ objects: objs }); @@ -398,30 +403,12 @@ export const useEditorStore = create((set, get) => { set({ guides }); }, updateObject: (id, updates) => { - const state = get(); - if (state.currentPageId) { - set((state) => ({ - objects: state.objects.map((obj) => - obj.id === id ? { ...obj, ...updates } : obj, - ), - pages: state.pages.map((p) => - p.id === state.currentPageId - ? { - ...p, - objects: p.objects.map((obj) => - obj.id === id ? { ...obj, ...updates } : obj, - ), - } - : p, - ), - })); - return; - } - set((state) => ({ - objects: state.objects.map((obj) => + set((state) => { + const nextObjects = state.objects.map((obj) => obj.id === id ? { ...obj, ...updates } : obj, - ), - })); + ); + return withSyncedCurrentPageObjects(state, nextObjects); + }); }, deleteObject: (id, options) => { if (!options?.skipHistory) { @@ -535,17 +522,12 @@ export const useEditorStore = create((set, get) => { get().commitObjectHistoryBeforeChange(); const state = get(); if (state.currentPageId) { - set((state) => ({ - objects: reorderByIds(state.objects, draggedId, targetId), - pages: state.pages.map((page) => - page.id === state.currentPageId - ? { - ...page, - objects: reorderByIds(page.objects, draggedId, targetId), - } - : page, + set((state) => + withSyncedCurrentPageObjects( + state, + reorderByIds(state.objects, draggedId, targetId), ), - })); + ); return; } @@ -962,24 +944,8 @@ export const useEditorStore = create((set, get) => { if (!result) return; if (state.currentPageId) { - const pageToGroup = state.pages.find( - (p) => p.id === state.currentPageId, - ); - if (!pageToGroup) return; - const pageResult = buildGroupResult( - pageToGroup.objects, - objectIds, - result.groupId, - ); - if (!pageResult) return; - set((state) => ({ - objects: result.nextObjects, - pages: state.pages.map((p) => - p.id === state.currentPageId - ? { ...p, objects: pageResult.nextObjects } - : p, - ), + ...withSyncedCurrentPageObjects(state, result.nextObjects), selectedObjectId: result.groupId, selectedObjectIds: [result.groupId], }));