tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

undo.js (4236B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /**
      8 * A simple undo stack manager.
      9 *
     10 * Actions are added along with the necessary code to
     11 * reverse the action.
     12 */
     13 class UndoStack {
     14  /**
     15   * @param {integer} maxUndo Maximum number of undo steps.
     16   *   defaults to 50.
     17   */
     18  constructor(maxUndo) {
     19    this.maxUndo = maxUndo || 50;
     20    this._stack = [];
     21  }
     22 
     23  // Current index into the undo stack.  Is positioned after the last
     24  // currently-applied change.
     25  _index = 0;
     26 
     27  // The current batch depth (see startBatch() for details)
     28  _batchDepth = 0;
     29 
     30  destroy() {
     31    this.uninstallController();
     32    delete this._stack;
     33  }
     34 
     35  /**
     36   * Start a collection of related changes.  Changes will be batched
     37   * together into one undo/redo item until endBatch() is called.
     38   *
     39   * Batches can be nested, in which case the outer batch will contain
     40   * all items from the inner batches.  This allows larger user
     41   * actions made up of a collection of smaller actions to be
     42   * undone as a single action.
     43   */
     44  startBatch() {
     45    if (this._batchDepth++ === 0) {
     46      this._batch = [];
     47    }
     48  }
     49 
     50  /**
     51   * End a batch of related changes, performing its action and adding
     52   * it to the undo stack.
     53   */
     54  endBatch() {
     55    if (--this._batchDepth > 0) {
     56      return;
     57    }
     58 
     59    // Cut off the end of the undo stack at the current index,
     60    // and the beginning to prevent a stack larger than maxUndo.
     61    const start = Math.max(this._index + 1 - this.maxUndo, 0);
     62    this._stack = this._stack.slice(start, this._index);
     63 
     64    const batch = this._batch;
     65    delete this._batch;
     66    const entry = {
     67      do() {
     68        for (const item of batch) {
     69          item.do();
     70        }
     71      },
     72      undo() {
     73        for (let i = batch.length - 1; i >= 0; i--) {
     74          batch[i].undo();
     75        }
     76      },
     77    };
     78    this._stack.push(entry);
     79    this._index = this._stack.length;
     80    entry.do();
     81  }
     82 
     83  /**
     84   * Perform an action, adding it to the undo stack.
     85   *
     86   * @param function toDo Called to perform the action.
     87   * @param function undo Called to reverse the action.
     88   */
     89  do(toDo, undo) {
     90    this.startBatch();
     91    this._batch.push({ do: toDo, undo });
     92    this.endBatch();
     93  }
     94 
     95  /*
     96   * Returns true if undo() will do anything.
     97   */
     98  canUndo() {
     99    return this._index > 0;
    100  }
    101 
    102  /**
    103   * Undo the top of the undo stack.
    104   *
    105   * @return true if an action was undone.
    106   */
    107  undo() {
    108    if (!this.canUndo()) {
    109      return false;
    110    }
    111    this._stack[--this._index].undo();
    112    return true;
    113  }
    114 
    115  /**
    116   * Returns true if redo() will do anything.
    117   */
    118  canRedo() {
    119    return this._stack.length > this._index;
    120  }
    121 
    122  /**
    123   * Redo the most recently undone action.
    124   *
    125   * @return true if an action was redone.
    126   */
    127  redo() {
    128    if (!this.canRedo()) {
    129      return false;
    130    }
    131    this._stack[this._index++].do();
    132    return true;
    133  }
    134 
    135  /**
    136   * ViewController implementation for undo/redo.
    137   */
    138 
    139  /**
    140   * Install this object as a command controller.
    141   */
    142  installController(controllerWindow) {
    143    const controllers = controllerWindow.controllers;
    144    // Only available when running in a Firefox panel.
    145    if (!controllers || !controllers.appendController) {
    146      return;
    147    }
    148 
    149    this._controllerWindow = controllerWindow;
    150    controllers.appendController(this);
    151  }
    152 
    153  /**
    154   * Uninstall this object from the command controller.
    155   */
    156  uninstallController() {
    157    if (!this._controllerWindow) {
    158      return;
    159    }
    160    this._controllerWindow.controllers.removeController(this);
    161  }
    162 
    163  supportsCommand(command) {
    164    return command == "cmd_undo" || command == "cmd_redo";
    165  }
    166 
    167  isCommandEnabled(command) {
    168    switch (command) {
    169      case "cmd_undo":
    170        return this.canUndo();
    171      case "cmd_redo":
    172        return this.canRedo();
    173    }
    174    return false;
    175  }
    176 
    177  doCommand(command) {
    178    switch (command) {
    179      case "cmd_undo":
    180        return this.undo();
    181      case "cmd_redo":
    182        return this.redo();
    183      default:
    184        return null;
    185    }
    186  }
    187 
    188  onEvent() {}
    189 }
    190 
    191 exports.UndoStack = UndoStack;