Home Reference Source

src/whiteboard/WhiteboardHistory.js

/**
 * @typedef {Object} WhiteboardHistoryItem
 * @property {string} id the identifier of the stored shape.
 * @property {string} previous previous shape state in stringified format.
 * @property {string} current new shape state in stringified format.
 */

const MAX_HISTORY_LENGTH = 20;

/**
 * Class that stores the changes made in the whiteboard and allows you to get the
 * shapes that undo the changes introduced in the whiteboard. As long as no new
 * changes are introduced this class also allows to obtain the shapes that reverts
 * the changes made (redo action).
 */
export class WhiteboardHistory {

	constructor() {

		/**
		 * History pointer.
		 * @private
		 * @type {number}
		 */
		this._index = 0;

		/**
		 * Changes introduced into history.
		 * @private
		 * @type {Array<WhiteboardHistoryItem>}
		 */
		this._history = [];
	}

	/**
	 * Reset the history.
	 */
	clear() {
		this._index = 0;
		this._history = [];
	}

	/**
	 * Get the previous state of a given shape.
	 * @private
	 * @param {string} id shape identifier.
	 * @return {Object} previous state of shape .
	 */
	_getPreviousShape(id) {
		const item = this._history.slice(0, this._index)
			.filter(x => x.id === id)
			.pop();
		return item ? item.current : JSON.stringify({id: id});
	}

	/**
	 * Add a new shape state to the history.
	 * @param {Object} shape the new state of the shape.
	 */
	add(shape) {
		const id = shape.id;
		const previous = this._getPreviousShape(id);
		shape = JSON.stringify(shape);
		if (previous === shape) {
			return;
		}
		this._history.length = this._index;
		this._history.push({
			id: id,
			previous: previous,
			current: shape,
		});
		if (this._index >= MAX_HISTORY_LENGTH) {
			this._history.shift();
		} else {
			this._index++;
		}
	}

	/**
	 * Check if there are items stored in the history can be used to undo an action.
	 * @return {boolean} true if there is at least one item stored in the history.
	 */
	canUndo() {
		return this._index > 0;
	}

	/**
	 * Check if there are items stored in the history that can be used to redo an action.
	 * @return {boolean} true if there is at least one item stored in the history.
	 */
	canRedo() {
		return this._index < this._history.length;
	}

	/**
	 * Get a shape that can be used to undo last added shape.
	 * @return {Object} shape that reverts last change.
	 */
	undo() {
		if (!this.canUndo()) {
			return null;
		}
		const shape = JSON.parse(this._history[this._index - 1].previous);
		this._index--;
		return shape;
	}

	/**
	 * Get a shape that can be used to revert last undo action.
	 * @return {Object} shape that reverts last undo action.
	 */
	redo() {
		if (!this.canRedo()) {
			return null;
		}
		const shape = JSON.parse(this._history[this._index].current);
		this._index++;
		return shape;
	}
}