@itwin/core-markup
Version:
iTwin.js markup package
177 lines • 6.76 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module MarkupTools
*/
import { assert } from "@itwin/core-bentley";
import { MarkupApp } from "./Markup";
/* @internal */
class UndoAction {
cmdName;
cmdId = 0;
constructor(cmdName) {
this.cmdName = cmdName;
}
}
/** created when a new element is added to the markup
* @internal
*/
class AddAction extends UndoAction {
_elem;
_parent;
_index;
constructor(cmdName, _elem) {
super(cmdName);
this._elem = _elem;
this._parent = _elem.parent();
assert(this._parent !== undefined);
this._index = _elem.position();
}
reinstate() { this._parent.add(this._elem, this._index); }
reverse() {
MarkupApp.markup.selected.drop(this._elem);
this._elem.remove();
}
}
/** created when an existing element is deleted from the markup
* @internal
*/
class DeleteAction extends UndoAction {
_elem;
_parent;
_index;
constructor(cmdName, _elem) {
super(cmdName);
this._elem = _elem;
this._parent = _elem.parent();
assert(this._parent !== undefined);
this._index = _elem.position();
}
reverse() { this._parent.add(this._elem, this._index); }
reinstate() {
MarkupApp.markup.selected.drop(this._elem);
this._elem.remove();
}
}
/** created when an existing element's position is moved in the display order. This can also include re-parenting
* @internal
*/
class RepositionAction extends UndoAction {
_elem;
_oldIndex;
_oldParent;
_newParent;
_newIndex;
constructor(cmdName, _elem, _oldIndex, _oldParent) {
super(cmdName);
this._elem = _elem;
this._oldIndex = _oldIndex;
this._oldParent = _oldParent;
this._newParent = _elem.parent();
assert(this._newParent !== undefined);
this._newIndex = _elem.position();
}
reinstate() {
this._newParent.add(this._elem, this._newIndex);
}
reverse() {
this._oldParent.add(this._elem, this._oldIndex);
if (this._elem.inSelection)
MarkupApp.markup.selected.drop(this._elem);
}
}
/** created when an existing element's properties are modified.
* @internal
*/
class ModifyAction extends UndoAction {
_newElem;
_oldElement;
constructor(cmdName, _newElem, _oldElement) {
super(cmdName);
this._newElem = _newElem;
this._oldElement = _oldElement;
assert(_newElem !== undefined && _oldElement !== undefined);
MarkupApp.markup.selected.replace(_oldElement, _newElem);
}
reinstate() {
this._oldElement.replace(this._newElem);
MarkupApp.markup.selected.replace(this._oldElement, this._newElem);
}
reverse() {
this._newElem.replace(this._oldElement);
MarkupApp.markup.selected.replace(this._newElem, this._oldElement);
}
}
/** Stores the sequence of operations performed on a Markup. Facilitates undo/redo of the operations.
* @public
*/
export class UndoManager {
_currentCmd = 0;
_grouped = 0;
_stack = [];
_currentPos = 0;
_cmdName = "";
addAction(action) {
this._stack.length = this._currentPos;
action.cmdId = this._currentCmd;
this._stack.push(action);
this._currentPos = this.size;
}
/** @internal */
get size() { return this._stack.length; }
startCommand() {
if (0 === this._grouped)
++this._currentCmd;
}
startGroup() {
this.startCommand();
++this._grouped;
}
endGroup() { --this._grouped; }
/** Perform a series of changes to markup elements that should all be reversed as a single operation.
* @param fn the function that performs the changes to the elements. It must call the onXXX methods of this class to store
* the operations in the undo buffer.
* @note all of the onXXX methods of this class should *only* be called from within the callback function of this method.
*/
performOperation(cmdName, fn) {
this._cmdName = cmdName;
this.startGroup();
fn();
this.endGroup();
}
/** call this from within a [[performOperation]] function *after* an element has been added to a markup */
onAdded(elem) { this.addAction(new AddAction(this._cmdName, elem)); }
/** call this from within a [[performOperation]] function *before* an element is about to be deleted from a markup */
onDelete(elem) { this.addAction(new DeleteAction(this._cmdName, elem)); }
/** call this from within a [[performOperation]] function *after* an element has been moved in display order in a markup */
onRepositioned(elem, oldIndex, oldParent) { this.addAction(new RepositionAction(this._cmdName, elem, oldIndex, oldParent)); }
/** call this from within a [[performOperation]] function *after* an element has been modified in a markup */
onModified(newElem, oldElem) { this.addAction(new ModifyAction(this._cmdName, newElem, oldElem)); }
/** determine whether there are any un-reversed operations */
get undoPossible() { return this._currentPos > 0; }
/** determine whether there are any reversed operations */
get redoPossible() { return this._currentPos < this.size; }
/** the name of the operation that can be undone (or undefined) */
get undoString() { return this.undoPossible ? this._stack[this._currentPos - 1].cmdName : undefined; }
/** the name of the operation that can be redone (or undefined) */
get redoString() { return this.redoPossible ? this._stack[this._currentPos].cmdName : undefined; }
/** reverse the most recent operation, if any */
doUndo() {
if (this._currentPos === 0)
return; // no operations have been performed
const cmdId = this._stack[this._currentPos - 1].cmdId;
while (this._currentPos > 0 && cmdId === this._stack[this._currentPos - 1].cmdId)
this._stack[--this._currentPos].reverse();
}
/** reinstate the most recently reversed operation, if any */
doRedo() {
if (this._currentPos === this.size)
return; // no operations have been reversed.
const cmdId = this._stack[this._currentPos].cmdId;
while (this._currentPos < this.size && cmdId === this._stack[this._currentPos].cmdId)
this._stack[this._currentPos++].reinstate();
}
}
//# sourceMappingURL=Undo.js.map