import mitt from "mitt"; import { SingleHistory } from "../history"; import { installGlobalVar } from "./use-global-vars"; import { Ref, ref, watch } from "vue"; import { copy, trackFlag } from "@/utils/shared"; 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 once = trackFlag((flag) => { if (flag === 0 && this.onceHistory) { this.push(this.onceHistory); this.onceHistory = null; } }) private prevent = trackFlag() private enforce = trackFlag() private onceHistory: string | null = null; private rendererRaw?: (data: string) => void; initData: string | null = null; clearData: string = '' renderer: (data: HistoryItem) => void; bus = mitt<{ attachs: Record; renderer: void; push: string; pushed: void; redo: void; undo: void; init: void; clear: void; }>(); private pushAttachs: Record = {}; constructor(renderer?: (data: string) => void) { this.rendererRaw = renderer this.renderer = ({ data, attachs }: HistoryItem) => { this.rendererRaw && this.rendererRaw(data); this.bus.emit("renderer"); this.bus.emit("attachs", attachs ? JSON.parse(attachs) : {}); }; } onceTrack(fn: () => T): T { return this.once.track(fn) } preventTrack(fn: () => any) { return this.prevent.track(fn) } enforceTrack(fn: () => any) { return this.enforce.track(fn) } setRenderer(renderer: (data: string) => void) { this.rendererRaw = renderer } 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 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.prevent.flag) return; if (this.once.flag) { this.onceHistory = data; } else if (data !== this.current?.data || this.enforce.flag) { console.log('push history') this.bus.emit("push", data); 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.renderer(data); return data; } undo() { const data = this.history.undo(); this.bus.emit("undo"); this.renderer(data); return data; } clearCurrent(): void { this.renderer({ data: this.clearData, attachs: "" }); this.push(this.clearData); } clear(): void { this.history.reset() this.clearCurrent() this.bus.emit('clear') } init() { if (this.initData) { this.renderer({ data: this.initData, attachs: "" }); this.push(this.initData); this.bus.emit('init') } } } export const useHistory = installGlobalVar(() => new DrawHistory(), 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) => { 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; };