Undo / Redo
The SDK ships with an UndoManager that records state changes across the Engine, Timeline, Layers, Clips, Library, and Subtitles. It is disabled by default. Enable it when initializing the Engine:
await Engine.getInstance().init({
enableUndoRedo: true,
...
});Basic Usage
const undoManager = Engine.getInstance().getUndoManager();
undoManager.undo();
undoManager.redo();
undoManager.canUndo();
undoManager.canRedo();
undoManager.clear();Grouping Changes
By default, each recorded action is its own undo step. To group multiple changes into a single undoable operation, wrap them in a group:
undoManager.startGroup("Resize and rotate");
clip.style.setScale(0.8, 0.8);
clip.style.setRotation(Math.PI / 6);
undoManager.endGroup();Call cancelGroup() to discard a group that is still open.
Pausing Recording
If you need to apply changes that should not be undoable (for example, temporary previews while a user drags), pause the recorder:
undoManager.setPause(true);
// ... non-undoable changes ...
undoManager.setPause(false);For nested pausing (recommended when the same flow can be entered from multiple places), use pushPause() and popPause().
undoManager.pushPause();
// ... work ...
undoManager.popPause();You can also inspect the pause stack and clear it when needed:
const stack = undoManager.getPauseStack();
undoManager.clearPauseStack();Enabling / Disabling at Runtime
undoManager.setEnabled(false);Disabling the manager also clears its internal stacks.
Serialization
The undo/redo history can be serialized and restored alongside the project. This allows users to continue editing after closing the tab:
const undoData = undoManager.serialize();
await undoManager.deserialize(undoData);Events
Every undo/redo mutation emits an event. Use these events to update your UI state (for example, to enable/disable the Undo and Redo buttons):
Engine.getInstance().events.on("undo:changed", ({ canUndo, canRedo }) => {
undoButton.disabled = !canUndo;
redoButton.disabled = !canRedo;
});
Engine.getInstance().events.on("undo:performed", ({ group }) => {
console.log("Undid:", group.name);
});
Engine.getInstance().events.on("redo:performed", ({ group }) => {
console.log("Redid:", group.name);
});Custom Undo Actions
If you are building on top of the SDK (for example with a Custom Clip), you can record your own undo entries through recordCustomUndo. The action name must not collide with any value in UndoActionEnum (those are reserved for the SDK itself).
undoManager.recordCustomUndo("my-app:rename", {
prev: previousName,
next: nextName,
});The undo:process:custom event fires when the custom record is undone or redone, giving you a chance to apply the state change yourself:
Engine.getInstance().events.on(
"undo:process:custom",
({ undoRecord, redoGroup }) => {
if (undoRecord.action === "my-app:rename") {
// Add a redo on undo
redoGroup.records.push(
new UndoRecord("my-app:rename", {
prev: undoRecord.data.next,
next: undoRecord.data.prev,
}),
);
applyName(undoRecord.data.prev);
}
},
);Inspecting History
Surface the pending undo/redo groups in your UI (for example, to show "Undo: Move clip" in a menu):
undoManager.canUndo(); // boolean
undoManager.canRedo(); // boolean
undoManager.getUndoHistory(); // string[] of group names, newest last
undoManager.getRedoHistory(); // string[]
undoManager.getPerforming(); // true while an undo/redo is in flightundo:redo:changed fires on every mutation, so you can drive the UI reactively without polling.
History Size
The default history holds ~100 groups. Adjust it with setMaxUndoHistory:
undoManager.setMaxUndoHistory(500);
const cap = undoManager.getMaxUndoHistory();Older entries are dropped once the cap is exceeded.
Enable / Disable at Runtime
undoManager.setEnabled(false);
const isOn = undoManager.getEnabled();Disabling clears both stacks. Useful when importing a project, you don't want the import operations to show up in the user's undo history.