use-history.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import mitt from "mitt";
  2. import { SingleHistory } from "../history";
  3. import { installGlobalVar } from "./use-global-vars";
  4. import { Ref, ref, watch } from "vue";
  5. import { copy, trackFlag } from "@/utils/shared";
  6. type HistoryItem = { attachs: string; data: string };
  7. export class DrawHistory {
  8. history = new SingleHistory<HistoryItem>();
  9. hasUndo = this.history.hasUndo;
  10. hasRedo = this.history.hasRedo;
  11. get list() {
  12. return this.history.list()
  13. }
  14. get currentId() {
  15. return this.history.currentId
  16. }
  17. get current() {
  18. return this.list.find(item => item.id === this.currentId)?.data
  19. }
  20. private once = trackFlag((flag) => {
  21. if (flag === 0 && this.onceHistory) {
  22. this.push(this.onceHistory);
  23. this.onceHistory = null;
  24. }
  25. })
  26. private prevent = trackFlag()
  27. private enforce = trackFlag()
  28. private onceHistory: string | null = null;
  29. private rendererRaw?: (data: string) => void;
  30. initData: string | null = null;
  31. clearData: string = ''
  32. renderer: (data: HistoryItem) => void;
  33. bus = mitt<{
  34. attachs: Record<string, any>;
  35. renderer: void;
  36. push: string;
  37. pushed: void;
  38. redo: void;
  39. undo: void;
  40. init: void;
  41. clear: void;
  42. }>();
  43. private pushAttachs: Record<string, any> = {};
  44. constructor(renderer?: (data: string) => void) {
  45. this.rendererRaw = renderer
  46. this.renderer = ({ data, attachs }: HistoryItem) => {
  47. this.rendererRaw && this.rendererRaw(data);
  48. this.bus.emit("renderer");
  49. this.bus.emit("attachs", attachs ? JSON.parse(attachs) : {});
  50. };
  51. }
  52. onceTrack<T>(fn: () => T): T {
  53. return this.once.track(fn)
  54. }
  55. preventTrack(fn: () => any) {
  56. return this.prevent.track(fn)
  57. }
  58. enforceTrack(fn: () => any) {
  59. return this.enforce.track(fn)
  60. }
  61. setRenderer(renderer: (data: string) => void) {
  62. this.rendererRaw = renderer
  63. }
  64. setClearData(data:string = '') {
  65. this.clearData = data
  66. }
  67. clearHistoryAttach(id: string, name: string) {
  68. const history = this.history.history as any
  69. if (!history.$chunks[id]) return
  70. const data = JSON.parse(history.$chunks[id]).data as HistoryItem
  71. if (!data.attachs) return;
  72. const attachs = JSON.parse(data.attachs)
  73. if (attachs[name]) {
  74. delete attachs[name]
  75. data.attachs = JSON.stringify(attachs)
  76. history.$chunks[id] = JSON.stringify({ data })
  77. }
  78. }
  79. setPushAttach(name: string, data: any) {
  80. this.pushAttachs[name] = data;
  81. }
  82. setInit(data: string) {
  83. this.initData = data;
  84. this.history.reset();
  85. this.push(data);
  86. }
  87. private saveKeyPrev = '__history__'
  88. private saveKeyId: string | null = null
  89. get saveKey() {
  90. if (!this.saveKeyId) {
  91. throw '未设置本地保存key'
  92. }
  93. return this.saveKeyPrev + this.saveKeyId
  94. }
  95. setLocalId(id: string) {
  96. this.saveKeyId = id
  97. }
  98. getLocalId() {
  99. return this.saveKeyId
  100. }
  101. saveLocal() {
  102. localStorage.setItem(this.saveKey, JSON.stringify(this.list.map(item => item.data)))
  103. }
  104. clearLocal() {
  105. localStorage.removeItem(this.saveKey)
  106. }
  107. hasLocal() {
  108. for (let i = 0, len = localStorage.length; i < len; i++) {
  109. if (localStorage.key(i) === this.saveKey) {
  110. return true
  111. }
  112. }
  113. return false
  114. }
  115. loadLocalStorage() {
  116. const list = JSON.parse(localStorage.getItem(this.saveKey)!)
  117. if (!list.length) {
  118. this.clear()
  119. return;
  120. }
  121. this.history.reset()
  122. this.setInit(list[0].data)
  123. for (let i = 1; i < list.length; i++) {
  124. this.push(list[i].data)
  125. }
  126. this.renderer(list[list.length - 1])
  127. }
  128. push(data: string) {
  129. if (this.prevent.flag) return;
  130. if (this.once.flag) {
  131. this.onceHistory = data;
  132. } else if (data !== this.current?.data || this.enforce.flag) {
  133. console.log('push history')
  134. this.bus.emit("push", data);
  135. this.history.push({ attachs: JSON.stringify(this.pushAttachs), data });
  136. this.pushAttachs = {};
  137. this.bus.emit("pushed");
  138. }
  139. }
  140. redo() {
  141. const data = this.history.redo();
  142. this.bus.emit("redo");
  143. this.renderer(data);
  144. return data;
  145. }
  146. undo() {
  147. const data = this.history.undo();
  148. this.bus.emit("undo");
  149. this.renderer(data);
  150. return data;
  151. }
  152. clearCurrent(): void {
  153. this.renderer({ data: this.clearData, attachs: "" });
  154. this.push(this.clearData);
  155. }
  156. clear(): void {
  157. this.history.reset()
  158. this.clearCurrent()
  159. this.bus.emit('clear')
  160. }
  161. init() {
  162. if (this.initData) {
  163. this.renderer({ data: this.initData, attachs: "" });
  164. this.push(this.initData);
  165. this.bus.emit('init')
  166. }
  167. }
  168. }
  169. export const useHistory = installGlobalVar(() => new DrawHistory(), Symbol("history"));
  170. export const useHistoryAttach = <T>(
  171. name: string,
  172. isRuning: Ref<boolean> = ref(true),
  173. getInit: () => T,
  174. cleanup = true
  175. ) => {
  176. const history = useHistory();
  177. const current = ref<T>(copy(getInit()!));
  178. const setIds = [] as string[]
  179. const addSetIds = () => setIds.push(history.currentId)
  180. const pushHandler = () => {
  181. history.setPushAttach(name, current.value);
  182. };
  183. const attachsHandler = (attachs: any) => {
  184. current.value = attachs && attachs[name] ? attachs[name] : getInit();
  185. };
  186. const cleanupAttach = () => {
  187. setIds.forEach(id => history.clearHistoryAttach(id, name))
  188. setIds.length = 0
  189. }
  190. watch(
  191. isRuning,
  192. (isRun, _, onCleanup) => {
  193. if (!isRun) return;
  194. history.bus.on("push", pushHandler);
  195. history.bus.on("attachs", attachsHandler);
  196. if (cleanup) {
  197. history.bus.on('pushed', addSetIds)
  198. }
  199. onCleanup(() => {
  200. history.bus.off("push", pushHandler);
  201. history.bus.off("attachs", attachsHandler);
  202. current.value = void 0;
  203. if (cleanup) {
  204. history.bus.off('pushed', pushHandler);
  205. cleanupAttach()
  206. }
  207. });
  208. },
  209. { immediate: true, flush: "sync" }
  210. );
  211. return current;
  212. };