fix animation
This commit is contained in:
@@ -23,7 +23,7 @@ const Editor: FC = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
||||||
const [isInitialContentReady, setIsInitialContentReady] = 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 { data, isFetched } = useGetCatalogById(id!)
|
||||||
const { scheduleUpdate, isSaving } = useUpdateCatalog()
|
const { scheduleUpdate, isSaving } = useUpdateCatalog()
|
||||||
|
|
||||||
@@ -51,13 +51,19 @@ const Editor: FC = () => {
|
|||||||
if (!isInitialContentReady) return
|
if (!isInitialContentReady) return
|
||||||
if (!pages || pages.length === 0) return
|
if (!pages || pages.length === 0) return
|
||||||
|
|
||||||
|
const pagesToSave = currentPageId
|
||||||
|
? pages.map((p) =>
|
||||||
|
p.id === currentPageId ? { ...p, objects } : p,
|
||||||
|
)
|
||||||
|
: pages
|
||||||
|
|
||||||
scheduleUpdate({
|
scheduleUpdate({
|
||||||
id,
|
id,
|
||||||
content: JSON.stringify({ pages, documentSettings }),
|
content: JSON.stringify({ pages: pagesToSave, documentSettings }),
|
||||||
})
|
})
|
||||||
// عمداً scheduleUpdate را در وابستگیها نیاوردیم تا از لوپ ذخیرهسازی جلوگیری شود
|
// عمداً scheduleUpdate را در وابستگیها نیاوردیم تا از لوپ ذخیرهسازی جلوگیری شود
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [id, isInitialContentReady, pages, documentSettings])
|
}, [id, isInitialContentReady, pages, documentSettings, objects, currentPageId])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Select from "@/components/Select";
|
import Select from "@/components/Select";
|
||||||
import Input from "@/components/Input";
|
import Input from "@/components/Input";
|
||||||
import type { EditorObject } from "../../../store/editorStore";
|
import type { EditorObject } from "../../../store/editorStore";
|
||||||
|
import { useEditorStore } from "../../../store/editorStore";
|
||||||
import { ENTRANCE_ANIMATION_SELECT_ITEMS } from "../constants/entranceAnimationOptions";
|
import { ENTRANCE_ANIMATION_SELECT_ITEMS } from "../constants/entranceAnimationOptions";
|
||||||
import {
|
import {
|
||||||
DEFAULT_ENTRANCE_DURATION_MS,
|
DEFAULT_ENTRANCE_DURATION_MS,
|
||||||
@@ -13,10 +14,34 @@ type Props = {
|
|||||||
onUpdate: (id: string, updates: Partial<EditorObject>) => void;
|
onUpdate: (id: string, updates: Partial<EditorObject>) => 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 EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => {
|
||||||
const anim = selectedObject.entranceAnimation ?? "none";
|
const objects = useEditorStore((s) => s.objects);
|
||||||
const duration = selectedObject.entranceDurationMs ?? DEFAULT_ENTRANCE_DURATION_MS;
|
const targets = getAnimationTargets(selectedObject, objects);
|
||||||
const delayMs = selectedObject.entranceDelayMs ?? DEFAULT_ENTRANCE_DELAY_MS;
|
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<EditorObject>) => {
|
||||||
|
const ids =
|
||||||
|
targets.length > 0 ? targets.map((t) => t.id) : [selectedObject.id];
|
||||||
|
ids.forEach((id) => onUpdate(id, updates));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-4 flex flex-col gap-4 border-t border-border">
|
<div className="pt-4 flex flex-col gap-4 border-t border-border">
|
||||||
@@ -26,7 +51,7 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => {
|
|||||||
value={anim}
|
value={anim}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = e.target.value as EditorObject["entranceAnimation"];
|
const v = e.target.value as EditorObject["entranceAnimation"];
|
||||||
onUpdate(selectedObject.id, {
|
applyToTargets({
|
||||||
entranceAnimation: v === "none" ? undefined : v,
|
entranceAnimation: v === "none" ? undefined : v,
|
||||||
...(v === "none" || !v
|
...(v === "none" || !v
|
||||||
? { entranceDurationMs: undefined, entranceDelayMs: undefined }
|
? { entranceDurationMs: undefined, entranceDelayMs: undefined }
|
||||||
@@ -35,19 +60,22 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{anim !== "none" && (
|
{anim !== "none" && (
|
||||||
<><Input
|
<>
|
||||||
label="مدت انیمیشن (میلیثانیه)"
|
<Input
|
||||||
type="number"
|
label="مدت انیمیشن (میلیثانیه)"
|
||||||
min={100}
|
type="number"
|
||||||
max={4000}
|
min={100}
|
||||||
value={duration}
|
max={4000}
|
||||||
onChange={(e) => {
|
value={duration}
|
||||||
const n = parseInt(e.target.value, 10);
|
onChange={(e) => {
|
||||||
if (Number.isNaN(n)) return;
|
const n = parseInt(e.target.value, 10);
|
||||||
onUpdate(selectedObject.id, {
|
if (Number.isNaN(n)) return;
|
||||||
entranceDurationMs: Math.min(4000, Math.max(100, n)),
|
applyToTargets({
|
||||||
});
|
entranceDurationMs: Math.min(4000, Math.max(100, n)),
|
||||||
}} /><Input
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
label="تأخیر قبل از شروع (میلیثانیه)"
|
label="تأخیر قبل از شروع (میلیثانیه)"
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
@@ -57,13 +85,15 @@ const EntranceAnimationSettings = ({ selectedObject, onUpdate }: Props) => {
|
|||||||
const n = parseInt(e.target.value, 10);
|
const n = parseInt(e.target.value, 10);
|
||||||
if (Number.isNaN(n)) return;
|
if (Number.isNaN(n)) return;
|
||||||
const clamped = Math.min(MAX_ENTRANCE_DELAY_MS, Math.max(0, n));
|
const clamped = Math.min(MAX_ENTRANCE_DELAY_MS, Math.max(0, n));
|
||||||
onUpdate(selectedObject.id, {
|
applyToTargets({
|
||||||
entranceDelayMs: clamped === 0 ? undefined : clamped,
|
entranceDelayMs: clamped === 0 ? undefined : clamped,
|
||||||
});
|
});
|
||||||
}} /></>
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EntranceAnimationSettings;
|
export default EntranceAnimationSettings;
|
||||||
@@ -30,6 +30,22 @@ const cloneObjectList = (objects: EditorObject[]): EditorObject[] =>
|
|||||||
const objectListsEqual = (a: EditorObject[], b: EditorObject[]) =>
|
const objectListsEqual = (a: EditorObject[], b: EditorObject[]) =>
|
||||||
JSON.stringify(a) === JSON.stringify(b);
|
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;
|
let objectGestureSnapshot: EditorObject[] | null = null;
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
@@ -248,7 +264,6 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
beginObjectGesture: () => {
|
beginObjectGesture: () => {
|
||||||
if (objectGestureSnapshot !== null) return;
|
|
||||||
objectGestureSnapshot = cloneObjectList(get().objects);
|
objectGestureSnapshot = cloneObjectList(get().objects);
|
||||||
},
|
},
|
||||||
endObjectGesture: () => {
|
endObjectGesture: () => {
|
||||||
@@ -336,12 +351,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
if (i === -1 || i >= objs.length - 1) return;
|
if (i === -1 || i >= objs.length - 1) return;
|
||||||
[objs[i], objs[i + 1]] = [objs[i + 1]!, objs[i]!];
|
[objs[i], objs[i + 1]] = [objs[i + 1]!, objs[i]!];
|
||||||
if (state.currentPageId) {
|
if (state.currentPageId) {
|
||||||
set({
|
set(withSyncedCurrentPageObjects(state, objs));
|
||||||
objects: objs,
|
|
||||||
pages: state.pages.map((p) =>
|
|
||||||
p.id === state.currentPageId ? { ...p, objects: objs } : p,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
set({ objects: objs });
|
set({ objects: objs });
|
||||||
@@ -354,12 +364,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
if (i <= 0) return;
|
if (i <= 0) return;
|
||||||
[objs[i], objs[i - 1]] = [objs[i - 1]!, objs[i]!];
|
[objs[i], objs[i - 1]] = [objs[i - 1]!, objs[i]!];
|
||||||
if (state.currentPageId) {
|
if (state.currentPageId) {
|
||||||
set({
|
set(withSyncedCurrentPageObjects(state, objs));
|
||||||
objects: objs,
|
|
||||||
pages: state.pages.map((p) =>
|
|
||||||
p.id === state.currentPageId ? { ...p, objects: objs } : p,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
set({ objects: objs });
|
set({ objects: objs });
|
||||||
@@ -398,30 +403,12 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
set({ guides });
|
set({ guides });
|
||||||
},
|
},
|
||||||
updateObject: (id, updates) => {
|
updateObject: (id, updates) => {
|
||||||
const state = get();
|
set((state) => {
|
||||||
if (state.currentPageId) {
|
const nextObjects = state.objects.map((obj) =>
|
||||||
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) =>
|
|
||||||
obj.id === id ? { ...obj, ...updates } : obj,
|
obj.id === id ? { ...obj, ...updates } : obj,
|
||||||
),
|
);
|
||||||
}));
|
return withSyncedCurrentPageObjects(state, nextObjects);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
deleteObject: (id, options) => {
|
deleteObject: (id, options) => {
|
||||||
if (!options?.skipHistory) {
|
if (!options?.skipHistory) {
|
||||||
@@ -535,17 +522,12 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
get().commitObjectHistoryBeforeChange();
|
get().commitObjectHistoryBeforeChange();
|
||||||
const state = get();
|
const state = get();
|
||||||
if (state.currentPageId) {
|
if (state.currentPageId) {
|
||||||
set((state) => ({
|
set((state) =>
|
||||||
objects: reorderByIds(state.objects, draggedId, targetId),
|
withSyncedCurrentPageObjects(
|
||||||
pages: state.pages.map((page) =>
|
state,
|
||||||
page.id === state.currentPageId
|
reorderByIds(state.objects, draggedId, targetId),
|
||||||
? {
|
|
||||||
...page,
|
|
||||||
objects: reorderByIds(page.objects, draggedId, targetId),
|
|
||||||
}
|
|
||||||
: page,
|
|
||||||
),
|
),
|
||||||
}));
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,24 +944,8 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
if (state.currentPageId) {
|
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) => ({
|
set((state) => ({
|
||||||
objects: result.nextObjects,
|
...withSyncedCurrentPageObjects(state, result.nextObjects),
|
||||||
pages: state.pages.map((p) =>
|
|
||||||
p.id === state.currentPageId
|
|
||||||
? { ...p, objects: pageResult.nextObjects }
|
|
||||||
: p,
|
|
||||||
),
|
|
||||||
selectedObjectId: result.groupId,
|
selectedObjectId: result.groupId,
|
||||||
selectedObjectIds: [result.groupId],
|
selectedObjectIds: [result.groupId],
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user