History (Undo & Redo)
The History feature adds support for undo/redo functionality in the editor.
It automatically tracks changes made to object data and layout, ensuring that future features integrate seamlessly with this system—if implemented correctly.
Tip: When implementing new features, always ask yourself: “Do I need to manually manage transactions for undo/redo?” 🚀
Overview
History Stack:
Every change gets recorded as an entry in the history stack.
Each entry contains:dataChanges
: Patches to object data (only the modified parts are stored).layoutChanges
: Additions or removals of objects.
Undo/Redo:
When undoing or redoing, patches are applied to the objects' data and layout.
For undo, patches are applied in reverse order.
How It Works
Data Changes
Tracking Mechanism:
Data changes are tracked using mobx-utils'sdeepObserve
.
SincedeepObserve
does not respect MobX transactions, we use our own transaction system to batch these changes.Batched Updates:
To ensure all data changes are batched into a single history entry, any modification (other than some special cases) should go through:jsEditorObjectManager.updateSelectedWithFunc<T extends EditorObjectBase>((iObject: T) => { // Make changes to iObject });
Transactions:
updateSelectedWithFunc
wraps your function with a transaction.
In some cases you might need to do that manually, and for thatHistoryStore
exposes these 2 functions:startTransaction()
endTransaction()
Any changes made between these calls will be grouped together.
Layout Changes
- Automatic Tracking:
Layout changes (like adding or removing objects) are automatically tracked after the editor is initialized using a MobX reaction. This means MobX transactions (i.e.runInAction
or store actions) are respected.
❗ Important
When adding or removing objects, always wrap your changes in MobX actions (using runInAction
or similar) to ensure that they are batched correctly.
Combined Changes
Both data and layout changes that occur inside a history transaction, will be group into a single history entry.
Ways to Make Changes
1. Changes via Side Panel / Context Menu
Typical Use:
These are mostly data changes that go throughupdateSelectedWithFunc
and work out of the box.Special Cases:
Auto Layout: Implemented separately.
Rotation: Has its own handling for single objects, multiple objects, and groups.
2. Changes via Canvas
Examples: Moving objects, resizing, etc.
New canvas behaviors must follow this pattern to work correctly with undo/redo.
(Note: Some cases, like auto layout, might require additional handling.)Call
startTransaction
before making changes.Call
endTransaction
after changes are complete.
3. Object Addition / Removal
Automatic Tracking:
Since layout changes are always tracked, operations like copy-paste or cloning work automatically—as long as they occur within MobX actions.Special Cases:
Features like auto layout may require custom implementations.
Caveats
There are some under-the-hood details that you should be aware of:
Adding New Objects
When new objects are added, they are recorded under added
within the layoutChanges
entry.
If these additions occur during a history transaction—where changes are not immediately squashed into a single entry (for instance, when applying an Auto-Layout, where a transaction ensures that all objects are added simultaneously)—the object data is not cloned until the transaction is finalized.
Consequently, any modifications made to the objects during the transaction will be reflected in the final history entry.
Tip
While adding new objects - if it requires using a manual transaction (like in Auto-Layout \ Notes),
there's no need to track data changes on the newly added objects.
Even if you do - any data change corresponding to the new objects will be ignored.
TL;DR
Use startTransaction
and endTransaction
to ensure that all changes (whether data or layout) are batched into a single history entry.
Change Type | Method/Requirement | Notes |
---|---|---|
Data Changes | Use EditorObjectManager.updateSelectedWithFunc | Batched via history transactions |
Layout Changes | Must be wrapped in MobX actions (runInAction /store actions) | Auto-tracked after editor initialization |
Canvas Interactions | Manually call startTransaction and endTransaction | Ensure proper batching; check for auto layout nuances |
Object Addition/Removal | Should be within MobX actions | Special cases (e.g., auto-layout) might vary |
Combined Changes | Use startTransaction and endTransaction | Ensure all changes are batched into a single history entry |
Quick Reference
For any new feature, ask first:
Does this only modify object data? → Use
updateSelectedWithFunc()
.Does this affect layout (add/remove objects)? → Wrap in MobX actions.
Are there combined changes (data + layout)? → Use
startTransaction()
andendTransaction()
.