import isString from 'tui-code-snippet/type/isString'; import CustomEvents from 'tui-code-snippet/customEvents/customEvents'; import commandFactory from '@/factory/command'; import { isFunction } from '@/util'; import { eventNames, rejectMessages } from '@/consts'; /** * Invoker * @class * @ignore */ class Invoker { constructor() { /** * Undo stack * @type {Array.} * @private */ this._undoStack = []; /** * Redo stack * @type {Array.} * @private */ this._redoStack = []; /** * Lock-flag for executing command * @type {boolean} * @private */ this._isLocked = false; this._isSilent = false; } /** * Invoke command execution * @param {Command} command - Command * @param {boolean} [isRedo=false] - check if command is redo * @returns {Promise} * @private */ _invokeExecution(command, isRedo = false) { this.lock(); let { args } = command; if (!args) { args = []; } return command .execute(...args) .then((value) => { if (!this._isSilent) { this.pushUndoStack(command); this.fire(isRedo ? eventNames.AFTER_REDO : eventNames.EXECUTE_COMMAND, command); } this.unlock(); if (isFunction(command.executeCallback)) { command.executeCallback(value); } return value; }) ['catch']((message) => { this.unlock(); return Promise.reject(message); }); } /** * Invoke command undo * @param {Command} command - Command * @returns {Promise} * @private */ _invokeUndo(command) { this.lock(); let { args } = command; if (!args) { args = []; } return command .undo(...args) .then((value) => { this.pushRedoStack(command); this.fire(eventNames.AFTER_UNDO, command); this.unlock(); if (isFunction(command.undoCallback)) { command.undoCallback(value); } return value; }) ['catch']((message) => { this.unlock(); return Promise.reject(message); }); } /** * fire REDO_STACK_CHANGED event * @private */ _fireRedoStackChanged() { this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length); } /** * fire UNDO_STACK_CHANGED event * @private */ _fireUndoStackChanged() { this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length); } /** * Lock this invoker */ lock() { this._isLocked = true; } /** * Unlock this invoker */ unlock() { this._isLocked = false; } executeSilent(...args) { this._isSilent = true; return this.execute(...args, this._isSilent).then(() => { this._isSilent = false; }); } /** * Invoke command * Store the command to the undoStack * Clear the redoStack * @param {String} commandName - Command name * @param {...*} args - Arguments for creating command * @returns {Promise} */ execute(...args) { if (this._isLocked) { return Promise.reject(rejectMessages.isLock); } let [command] = args; if (isString(command)) { command = commandFactory.create(...args); } return this._invokeExecution(command).then((value) => { this.clearRedoStack(); return value; }); } /** * Undo command * @returns {Promise} */ undo() { let command = this._undoStack.pop(); let promise; let message = ''; if (command && this._isLocked) { this.pushUndoStack(command, true); command = null; } if (command) { if (this.isEmptyUndoStack()) { this._fireUndoStackChanged(); } promise = this._invokeUndo(command); } else { message = rejectMessages.undo; if (this._isLocked) { message = `${message} Because ${rejectMessages.isLock}`; } promise = Promise.reject(message); } return promise; } /** * Redo command * @returns {Promise} */ redo() { let command = this._redoStack.pop(); let promise; let message = ''; if (command && this._isLocked) { this.pushRedoStack(command, true); command = null; } if (command) { if (this.isEmptyRedoStack()) { this._fireRedoStackChanged(); } promise = this._invokeExecution(command, true); } else { message = rejectMessages.redo; if (this._isLocked) { message = `${message} Because ${rejectMessages.isLock}`; } promise = Promise.reject(message); } return promise; } /** * Push undo stack * @param {Command} command - command * @param {boolean} [isSilent] - Fire event or not */ pushUndoStack(command, isSilent) { this._undoStack.push(command); if (!isSilent) { this._fireUndoStackChanged(); } } /** * Push redo stack * @param {Command} command - command * @param {boolean} [isSilent] - Fire event or not */ pushRedoStack(command, isSilent) { this._redoStack.push(command); if (!isSilent) { this._fireRedoStackChanged(); } } /** * Return whether the redoStack is empty * @returns {boolean} */ isEmptyRedoStack() { return this._redoStack.length === 0; } /** * Return whether the undoStack is empty * @returns {boolean} */ isEmptyUndoStack() { return this._undoStack.length === 0; } /** * Clear undoStack */ clearUndoStack() { if (!this.isEmptyUndoStack()) { this._undoStack = []; this._fireUndoStackChanged(); } } /** * Clear redoStack */ clearRedoStack() { if (!this.isEmptyRedoStack()) { this._redoStack = []; this._fireRedoStackChanged(); } } } CustomEvents.mixin(Invoker); export default Invoker;