import mitt from "mitt"; import { SingleHistory } from "../history"; import { installGlobalVar } from "./use-global-vars"; import { Ref, ref, watch } from "vue"; import { copy } from "@/utils/shared"; import { getEmptyStoreData, StoreData, useStoreRaw } from "../store/store"; type HistoryItem = { attachs: string; data: string }; export class DrawHistory { history = new SingleHistory(); hasUndo = this.history.hasUndo; hasRedo = this.history.hasRedo; get list() { return this.history.list() } get currentId() { return this.history.currentId } get current() { return this.list.find(item => item.id === this.currentId)?.data } private preventFlag = 0; private onceFlag = 0; private onceHistory: string | null = null; initData: string | null = null; clearData: string = '' renderer: (data: HistoryItem) => void; bus = mitt<{ attachs: Record; renderer: void; push: void; pushed: void; redo: void; undo: void; init: void; clear: void; currentChange: void }>(); private pushAttachs: Record = {}; constructor(renderer: (data: string) => void) { this.renderer = ({ data, attachs }: HistoryItem) => { renderer(data); this.bus.emit("renderer"); this.bus.emit("attachs", attachs ? JSON.parse(attachs) : {}); }; } setClearData(data:string = '') { this.clearData = data } clearHistoryAttach(id: string, name: string) { const history = this.history.history as any if (!history.$chunks[id]) return const data = JSON.parse(history.$chunks[id]).data as HistoryItem if (!data.attachs) return; const attachs = JSON.parse(data.attachs) if (attachs[name]) { delete attachs[name] data.attachs = JSON.stringify(attachs) history.$chunks[id] = JSON.stringify({ data }) } } setPushAttach(name: string, data: any) { this.pushAttachs[name] = data; } setInit(data: string) { this.initData = data; this.history.reset(); this.push(data); } private preventTrackCallback() { this.preventFlag-- } preventTrack(fn: () => any) { this.preventFlag++ const result = fn(); if (result instanceof Promise) { result.then(() => this.preventTrackCallback()) } else { this.preventTrackCallback() } } private saveKeyPrev = '__history__' private saveKeyId: string | null = null get saveKey() { if (!this.saveKeyId) { throw '未设置本地保存key' } return this.saveKeyPrev + this.saveKeyId } setLocalId(id: string) { this.saveKeyId = id } getLocalId() { return this.saveKeyId } saveLocal() { localStorage.setItem(this.saveKey, JSON.stringify(this.list.map(item => item.data))) } clearLocal() { localStorage.removeItem(this.saveKey) } hasLocal() { for (let i = 0, len = localStorage.length; i < len; i++) { if (localStorage.key(i) === this.saveKey) { return true } } return false } loadLocalStorage() { const list = JSON.parse(localStorage.getItem(this.saveKey)!) if (!list.length) { this.clear() return; } this.history.reset() this.setInit(list[0].data) for (let i = 1; i < list.length; i++) { this.push(list[i].data) } this.renderer(list[list.length - 1]) } push(data: string) { if (this.preventFlag) return; if (this.onceFlag) { this.onceHistory = data; } else if (data !== this.current?.data) { // console.log(data, this.current?.data) this.bus.emit("push"); this.history.push({ attachs: JSON.stringify(this.pushAttachs), data }); this.pushAttachs = {}; this.bus.emit("pushed"); } } redo() { const data = this.history.redo(); this.bus.emit("redo"); this.bus.emit('currentChange') this.renderer(data); return data; } undo() { const data = this.history.undo(); this.bus.emit("undo"); this.bus.emit('currentChange') this.renderer(data); return data; } private onceTrackCallback() { this.onceFlag-- if (this.onceHistory) { this.push(this.onceHistory); this.onceHistory = null; } } onceTrack(fn: () => any) { this.onceFlag++ const result = fn(); if (result instanceof Promise) { result.then(() => this.onceTrackCallback()) } else { this.onceTrackCallback() } } clearCurrent(): void { this.renderer({ data: this.clearData, attachs: "" }); this.push(this.clearData); } clear(): void { this.history.reset() this.clearCurrent() this.bus.emit('clear') this.bus.emit('currentChange') } init() { if (this.initData) { this.renderer({ data: this.initData, attachs: "" }); this.push(this.initData); this.bus.emit('init') this.bus.emit('currentChange') } } } export const useHistory = installGlobalVar(() => { const store = useStoreRaw(); const history = new DrawHistory((dataStr: string) => { const data: StoreData = dataStr ? JSON.parse(dataStr) : getEmptyStoreData() store.$patch((state) => { state.data = data; }); }); return history; }, Symbol("history")); export const useHistoryAttach = ( name: string, isRuning: Ref = ref(true), getInit: () => T, cleanup = true ) => { const history = useHistory(); const current = ref(copy(getInit()!)); const setIds = [] as string[] const addSetIds = () => setIds.push(history.currentId) const pushHandler = () => { history.setPushAttach(name, current.value); }; const attachsHandler = (attachs: any) => { current.value = attachs && attachs[name] ? attachs[name] : getInit(); }; const cleanupAttach = () => { setIds.forEach(id => history.clearHistoryAttach(id, name)) setIds.length = 0 } watch( isRuning, (isRun, _, onCleanup) => { console.log('isRun', isRun) if (!isRun) return; history.bus.on("push", pushHandler); history.bus.on("attachs", attachsHandler); if (cleanup) { history.bus.on('pushed', addSetIds) } onCleanup(() => { history.bus.off("push", pushHandler); history.bus.off("attachs", attachsHandler); current.value = void 0; if (cleanup) { history.bus.off('pushed', pushHandler); cleanupAttach() } }); }, { immediate: true, flush: "sync" } ); return current; };