UNPKG

@turbox3d/reactivity

Version:

Large-scale reactive state management library

230 lines (204 loc) 5.97 kB
import { fail, nextTick } from '@turbox3d/shared'; import { MapType, SetType } from './domain'; import { store, ActionType } from './store'; import { ctx } from '../const/config'; import { ECollectType, EMaterialType } from '../const/enums'; import { TURBOX_PREFIX } from '../const/symbol'; import { HistoryOperationType } from '../interfaces'; import { Action } from './action'; export interface DiffInfo { type: ECollectType; beforeUpdate: any; didUpdate: any; } export type KeyToDiffChangeMap = Map<any, DiffInfo>; export type History = Map<object, KeyToDiffChangeMap>; export interface HistoryNode { actionChain: ActionType[]; history: History; } export interface HistoryCollectorPayload { type: ECollectType; beforeUpdate?: any; didUpdate?: any; } /** * collect prop diff history record */ export class TimeTravel { static timeTravels: Record<string, TimeTravel> = {}; static currentTimeTravel?: TimeTravel; static processing = false; static switch = (name: string) => { TimeTravel.currentTimeTravel = TimeTravel.get(name); }; static create = (name: string) => { const tt = new TimeTravel(); TimeTravel.timeTravels[name] = tt; return tt; }; static get = (name: string): TimeTravel | undefined => { return TimeTravel.timeTravels[name]; } static pause = () => { ctx.timeTravel.isActive = false; }; static resume = () => { ctx.timeTravel.isActive = true; }; static undo = () => { TimeTravel.currentTimeTravel && TimeTravel.currentTimeTravel.undo(); }; static redo = () => { TimeTravel.currentTimeTravel && TimeTravel.currentTimeTravel.redo(); }; static clear = () => { TimeTravel.currentTimeTravel && TimeTravel.currentTimeTravel.clear(); }; static get undoable() { return TimeTravel.currentTimeTravel && TimeTravel.currentTimeTravel.undoable; } static get redoable() { return TimeTravel.currentTimeTravel && TimeTravel.currentTimeTravel.redoable; } static undoHandler(history: History) { history.forEach((keyToDiffObj, target) => { if (!keyToDiffObj) { return; } keyToDiffObj.forEach((value, key) => { if (!value) { return; } if (value.beforeUpdate === value.didUpdate) { return; } if (value.type === ECollectType.MAP_SET || value.type === ECollectType.MAP_DELETE) { if (value.beforeUpdate === void 0) { (target as MapType).delete(key); } else { (target as MapType).set(key, value.beforeUpdate); } } else if (value.type === ECollectType.SET_ADD) { (target as SetType).delete(key); } else if (value.type === ECollectType.SET_DELETE) { (target as SetType).add(key); } else if (Array.isArray(target) && value.beforeUpdate === void 0) { delete target[key]; } else { target[key] = value.beforeUpdate; } }); }); } static redoHandler(history: History) { history.forEach((keyToDiffObj, target) => { if (!keyToDiffObj) { return; } keyToDiffObj.forEach((value, key) => { if (!value) { return; } if (value.beforeUpdate === value.didUpdate) { return; } if (value.type === ECollectType.MAP_SET) { (target as MapType).set(key, value.didUpdate); } else if (value.type === ECollectType.SET_ADD) { (target as SetType).add(key); } else if (value.type === ECollectType.SET_DELETE) { (target as SetType).delete(key); } else if (value.type === ECollectType.MAP_DELETE) { (target as MapType).delete(key); } else { target[key] = value.didUpdate; } }); }); } get undoable() { return this.cursor >= 0; } get redoable() { return this.cursor < this.transactionHistories.length - 1; } currentHistory: History = new Map(); transactionHistories: HistoryNode[] = []; cursor = -1; onChange(undoable: boolean, redoable: boolean, type: HistoryOperationType, action?: Action) { // } /** * @todo support multiple steps * revert to the previous history status */ undo() { if (!this.undoable) { return; } const currentHistory = this.transactionHistories[this.cursor]; const original = () => { TimeTravel.undoHandler(currentHistory.history); }; if (!store) { fail('store is not ready, please init first.'); } const action = currentHistory.actionChain[0] || { name: '', displayName: '', }; store.dispatch({ name: `${TURBOX_PREFIX}UNDO_${action.name}`, displayName: `UNDO_${action.displayName}`, payload: [], original, type: EMaterialType.UNDO, isInner: true, }); this.cursor -= 1; nextTick(() => { this.onChange(this.undoable, this.redoable, 'undo'); }); } /** * @todo support multiple steps * apply the next history status */ redo() { if (!this.redoable) { return; } this.cursor += 1; const currentHistory = this.transactionHistories[this.cursor]; const original = () => { TimeTravel.redoHandler(currentHistory.history); }; if (!store) { fail('store is not ready, please init first.'); } const action = currentHistory.actionChain[0] || { name: '', displayName: '', }; store.dispatch({ name: `${TURBOX_PREFIX}REDO_${action.name}`, displayName: `REDO_${action.displayName}`, payload: [], original, type: EMaterialType.REDO, isInner: true, }); nextTick(() => { this.onChange(this.undoable, this.redoable, 'redo'); }); } clear() { this.currentHistory = new Map(); this.transactionHistories.length = 0; this.cursor = -1; nextTick(() => { this.onChange(this.undoable, this.redoable, 'clear'); }); } }