How IoT Studio Implements Undo/Redo with Command and Snapshot Patterns

This article explains the design and implementation of undo and redo functionality in the IoT Studio visual builder, comparing snapshot and command (operation) approaches, detailing transaction handling, a doubly‑linked‑list history manager, and exploring hybrid solutions such as immutable.js and Git‑style diff storage.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
How IoT Studio Implements Undo/Redo with Command and Snapshot Patterns

Background

In the field of frontend visual building, “redo” and “undo” are essential features that act as a “regret pill” for user mistakes. This article introduces the design and thinking behind the edit‑history functionality of the IoT Studio visual building platform.

Implementation Ideas

1. Page DSL Maintenance

IoT Studio maintains page state using an abstract syntax tree (AST). Both page information and component information are stored in corresponding nodes.

PageNode: {
  componentName: 'page1',
  id: 'page1',
  props: {},
  children: [
    ComponentNode: {
      componentName: 'component1',
      id: 'component1',
      props: { width: 800, height: 1000, color: '#ffffff' },
      children: []
    },
    ComponentNode: {
      componentName: 'component2',
      id: 'component2',
      props: {},
      children: []
    },
    ComponentNode: {
      componentName: 'component3',
      id: 'component3',
      props: {},
      children: []
    }
  ]
}

When a page is saved, its configuration is uploaded to OSS as a JSON file.

2. Redo and Undo

Snapshot Method

Each edit creates a deep‑copy snapshot stored in the history. Undo/redo operations replace the current page state with the corresponding snapshot, using a pointer to indicate the current version.

Advantages:

Simple implementation; full deep copy of page data.

Flexible switching between history records.

High storage consumption when page data is large.

Command (Operation) Method

IoT Studio adopts this method. For each user action, two methods— execute and undo —are defined, and the action is abstracted as an Operation object.

export abstract class Operation<T = void> {
  /**
   * Undo operation
   */
  protected abstract undo(): T;

  /**
   * Execute operation
   */
  protected abstract execute(): T;
}

Each edit creates an Operation instance and calls its execute. To undo, the undo method is invoked.

Advantages:

Compared with snapshots, it saves storage when page configurations are complex.

Different operations may have different execute/undo logic, increasing development cost.

Redo/undo across multiple history records requires sequential execution of all involved operations, which is less convenient than the snapshot approach.

3. Implementation Details

Transaction

When a single user action affects multiple components, several Operation instances are grouped into a Transaction. The transaction maintains a list of operations and iterates over them to perform execute or undo as a batch.

Doubly‑Linked List

The edit history is managed by a doubly‑linked list where each node holds a Transaction. The latest node’s execution result represents the current state.

class Manager {
  backwardCurrent(): boolean {
    if (this._current?.prev) {
      this._current.value.operation.undo();
      this._current = this._current.prev;
      this._validLength -= 1;
      return true;
    }
    return false;
  }

  forwardCurrent(): boolean {
    if (this._current?.next) {
      this._current.next.value.operation.execute();
      this._current = this._current.next;
      this._validLength += 1;
      return true;
    }
    return false;
  }

  addAfterCurrent(item: OperationResult<any>) {
    // Insert a new node after the current one
    this._current.next = { value: item, prev: this._current };
    this._current = this._current.next;
  }
}

When a new edit occurs, addAfterCurrent inserts a new node into the list.

Summary

The Operation class provides the minimal command unit for implementing redo and undo; different subclasses implement specific execute and undo logic. A Transaction groups multiple operations, and a doubly‑linked list manages the sequence of transactions, enabling a robust edit‑history feature for visual builders.

Exploration

To combine the advantages of both snapshot and command methods, two ideas are suggested:

immutable.js + Snapshot

Immutable.js uses persistent data structures with structural sharing, avoiding full deep copies while preserving previous versions. Combining it with the snapshot approach can reduce memory usage, as used by some internal visual engines.

Git‑style Diff Storage

Git stores changes as diffs after the initial full snapshot, balancing time and space. Applying a similar strategy to edit history can keep storage low while allowing fast access to the latest version.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

snapshotVisual EditorCommand PatternUNDORedo
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.