use-history.ts 6.3 KB

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