Skip to content

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's deepObserve.
    Since deepObserve 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:

    js
    EditorObjectManager.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 that HistoryStore 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 through updateSelectedWithFunc 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.)

    1. Call startTransaction before making changes.

    2. 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 TypeMethod/RequirementNotes
Data ChangesUse EditorObjectManager.updateSelectedWithFuncBatched via history transactions
Layout ChangesMust be wrapped in MobX actions (runInAction/store actions)Auto-tracked after editor initialization
Canvas InteractionsManually call startTransaction and endTransactionEnsure proper batching; check for auto layout nuances
Object Addition/RemovalShould be within MobX actionsSpecial cases (e.g., auto-layout) might vary
Combined ChangesUse startTransaction and endTransactionEnsure 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() and endTransaction().