d8ccc426c9dfeb0b4e0a69f36509e8dde337f3da31c9d5ed179cd4ec001f1430d4cd8573ef20812a7b1c6383eb141c3e7b71617b326e31ea070074e5494a9c 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. Simple JavaScript undo and redo.
  3. https://github.com/ArthurClemens/JavaScript-Undo-Manager
  4. */
  5. (function () {
  6. 'use strict';
  7. function removeFromTo(array, from, to) {
  8. array.splice(
  9. from,
  10. !to ||
  11. 1 +
  12. to -
  13. from +
  14. (!((to < 0) ^ (from >= 0)) && (to < 0 || -1) * array.length),
  15. );
  16. return array.length;
  17. }
  18. let UndoManager = function () {
  19. let commands = [],
  20. index = -1,
  21. limit = 0,
  22. isExecuting = false,
  23. callback;
  24. /**
  25. * Executes a single command.
  26. * @property {object} command - Command
  27. * @property {function} command.undo - Undo function
  28. * @property {function} command.redo - Redo function
  29. * @property {string} action - "undo" or "redo"
  30. */
  31. function execute(command, action) {
  32. if (!command || typeof command[action] !== 'function') {
  33. return this;
  34. }
  35. isExecuting = true;
  36. command[action]();
  37. isExecuting = false;
  38. return this;
  39. }
  40. return {
  41. /**
  42. * Adds a command to the queue.
  43. * @property {object} command - Command
  44. * @property {function} command.undo - Undo function
  45. * @property {function} command.redo - Redo function
  46. * @property {string} [command.groupId] - Optional group id
  47. */
  48. add: function (command) {
  49. if (isExecuting) {
  50. return this;
  51. }
  52. // if we are here after having called undo,
  53. // invalidate items higher on the stack
  54. commands.splice(index + 1, commands.length - index);
  55. commands.push(command);
  56. // if limit is set, remove items from the start
  57. if (limit && commands.length > limit) {
  58. removeFromTo(commands, 0, -(limit + 1));
  59. }
  60. // set the current index to the end
  61. index = commands.length - 1;
  62. if (callback) {
  63. callback();
  64. }
  65. return this;
  66. },
  67. /**
  68. * Pass a function to be called on undo and redo actions.
  69. * @property {function} callbackFunc - Callback function
  70. */
  71. setCallback: function (callbackFunc) {
  72. callback = callbackFunc;
  73. },
  74. /**
  75. * Performs undo: call the undo function at the current index and decrease the index by 1.
  76. */
  77. undo: function () {
  78. let command = commands[index];
  79. if (!command) {
  80. return this;
  81. }
  82. const groupId = command.groupId;
  83. while (command.groupId === groupId) {
  84. execute(command, 'undo');
  85. index -= 1;
  86. command = commands[index];
  87. if (!command || !command.groupId) break;
  88. }
  89. if (callback) {
  90. callback();
  91. }
  92. return this;
  93. },
  94. /**
  95. * Performs redo: call the redo function at the next index and increase the index by 1.
  96. */
  97. redo: function () {
  98. let command = commands[index + 1];
  99. if (!command) {
  100. return this;
  101. }
  102. const groupId = command.groupId;
  103. while (command.groupId === groupId) {
  104. execute(command, 'redo');
  105. index += 1;
  106. command = commands[index + 1];
  107. if (!command || !command.groupId) break;
  108. }
  109. if (callback) {
  110. callback();
  111. }
  112. return this;
  113. },
  114. /**
  115. * Clears the memory, losing all stored states. Resets the index.
  116. */
  117. clear: function () {
  118. let prev_size = commands.length;
  119. commands = [];
  120. index = -1;
  121. if (callback && prev_size > 0) {
  122. callback();
  123. }
  124. },
  125. /**
  126. * Tests if any undo actions exist.
  127. * @returns {boolean}
  128. */
  129. hasUndo: function () {
  130. return index !== -1;
  131. },
  132. /**
  133. * Tests if any redo actions exist.
  134. * @returns {boolean}
  135. */
  136. hasRedo: function () {
  137. return index < commands.length - 1;
  138. },
  139. /**
  140. * Returns the list of queued commands.
  141. * @param {string} [groupId] - Optionally filter commands by group ID
  142. * @returns {array}
  143. */
  144. getCommands: function (groupId) {
  145. return groupId ? commands.filter(c => c.groupId === groupId) : commands;
  146. },
  147. /**
  148. * Returns the index of the actions list.
  149. * @returns {number}
  150. */
  151. getIndex: function () {
  152. return index;
  153. },
  154. /**
  155. * Sets the maximum number of undo steps. Default: 0 (unlimited).
  156. * @property {number} max - Maximum number of undo steps
  157. */
  158. setLimit: function (max) {
  159. limit = max;
  160. },
  161. };
  162. };
  163. if (
  164. typeof define === 'function' &&
  165. typeof define.amd === 'object' &&
  166. define.amd
  167. ) {
  168. // AMD. Register as an anonymous module.
  169. define(function () {
  170. return UndoManager;
  171. });
  172. } else if (typeof module !== 'undefined' && module.exports) {
  173. module.exports = UndoManager;
  174. } else {
  175. window.UndoManager = UndoManager;
  176. }
  177. })();