import { ACTION_MODE, App, vertexsMap } from '..'; import undoRedoService from '../../service/undoredo'; import Vertex from '../common/Vertex'; import { LogicItem } from '../logic/LogicItem'; export interface Operation { operationAction: string; operationDesc: string; operationBeforeImage: any; operationAfterImage: any; activeImage?: any } export interface OperationHandler { context: Vertex | { new(): LogicItem }, undoAction: string, undoActionParam: string, redoAction: string, redoActionParam: string, } /** * 后续考虑优化可逆操作 * @param operation * @returns */ function getOperationHandler(operation: Operation) { const map: { [action: string]: () => OperationHandler } = { /* Element */ 'Element.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.parentId), undoAction: 'Element.removeChild', undoActionParam: 'After', redoAction: 'Element.addChild', redoActionParam: 'After', }), 'Element.addChild': () => ({ context: vertexsMap.get(operation.operationAfterImage.parentId), undoAction: 'Element.removeChild', undoActionParam: 'After', redoAction: 'Element.addChild', redoActionParam: 'After', }), 'Element.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.parentId), undoAction: 'Element.addChild', undoActionParam: 'Before', redoAction: 'Element.removeChild', redoActionParam: 'Before', }), 'Element.removeChild': () => ({ context: vertexsMap.get(operation.operationBeforeImage.parentId), undoAction: 'Element.addChild', undoActionParam: 'Before', redoAction: 'Element.removeChild', redoActionParam: 'Before', }), 'Element.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Element.update', undoActionParam: 'Before', redoAction: 'Element.update', redoActionParam: 'After', }), 'Element.move': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Element.move', undoActionParam: 'Before', redoAction: 'Element.move', redoActionParam: 'After', }), /* Attr */ 'Attr.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteAttr', undoActionParam: 'After', redoAction: 'Element.addAttr', redoActionParam: 'After', }), 'Element.addAttr': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteAttr', undoActionParam: 'After', redoAction: 'Element.addAttr', redoActionParam: 'After', }), 'Attr.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addAttr', undoActionParam: 'Before', redoAction: 'Element.deleteAttr', redoActionParam: 'Before', }), 'Element.deleteAttr': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addAttr', undoActionParam: 'Before', redoAction: 'Element.deleteAttr', redoActionParam: 'Before', }), 'Attr.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Attr.update', undoActionParam: 'Before', redoAction: 'Attr.update', redoActionParam: 'After', }), /* Event */ 'Event.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteEvent', undoActionParam: 'After', redoAction: 'Element.addEvent', redoActionParam: 'After', }), 'Element.addEvent': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteEvent', undoActionParam: 'After', redoAction: 'Element.addEvent', redoActionParam: 'After', }), 'Event.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addEvent', undoActionParam: 'Before', redoAction: 'Element.deleteEvent', redoActionParam: 'Before', }), 'Element.deleteEvent': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addEvent', undoActionParam: 'Before', redoAction: 'Element.deleteEvent', redoActionParam: 'Before', }), 'Event.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Event.update', undoActionParam: 'Before', redoAction: 'Event.update', redoActionParam: 'After', }), /* Directive */ 'Directive.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteDirective', undoActionParam: 'After', redoAction: 'Element.addDirective', redoActionParam: 'After', }), 'Element.addDirective': () => ({ context: vertexsMap.get(operation.operationAfterImage.elementId), undoAction: 'Element.deleteDirective', undoActionParam: 'After', redoAction: 'Element.addDirective', redoActionParam: 'After', }), 'Directive.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addDirective', undoActionParam: 'Before', redoAction: 'Element.deleteDirective', redoActionParam: 'Before', }), 'Element.deleteDirective': () => ({ context: vertexsMap.get(operation.operationBeforeImage.elementId), undoAction: 'Element.addDirective', undoActionParam: 'Before', redoAction: 'Element.deleteDirective', redoActionParam: 'Before', }), 'Directive.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Directive.update', undoActionParam: 'Before', redoAction: 'Directive.update', redoActionParam: 'After', }), /* Lifecycle */ 'Lifecycle.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.viewId), undoAction: 'Element.removeLifecycle', undoActionParam: 'After', redoAction: 'Element.addLifecycle', redoActionParam: 'After', }), 'View.addLifecycle': () => ({ context: vertexsMap.get(operation.operationAfterImage.viewId), undoAction: 'View.removeLifecycle', undoActionParam: 'After', redoAction: 'View.addLifecycle', redoActionParam: 'After', }), 'Lifecycle.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.viewId), undoAction: 'View.addLifecycle', undoActionParam: 'Before', redoAction: 'View.removeLifecycle', redoActionParam: 'Before', }), 'View.removeLifecycle': () => ({ context: vertexsMap.get(operation.operationBeforeImage.viewId), undoAction: 'View.addLifecycle', undoActionParam: 'Before', redoAction: 'View.removeLifecycle', redoActionParam: 'Before', }), 'Lifecycle.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'Lifecycle.update', undoActionParam: 'Before', redoAction: 'Lifecycle.update', redoActionParam: 'After', }), /* LogicItem */ 'LogicItem.create': () => ({ context: vertexsMap.get(operation.operationAfterImage.logicId), undoAction: 'Logic.removeItem', undoActionParam: 'After', redoAction: 'Logic.addItem', redoActionParam: 'After', }), 'Logic.addItem': () => ({ context: vertexsMap.get(operation.operationAfterImage.logicId), undoAction: 'Logic.removeItem', undoActionParam: 'After', redoAction: 'Logic.addItem', redoActionParam: 'After', }), 'LogicItem.delete': () => ({ context: vertexsMap.get(operation.operationBeforeImage.logicId), undoAction: 'Logic.addItem', undoActionParam: 'Before', redoAction: 'Logic.removeItem', redoActionParam: 'Before', }), 'LogicItem.removeItem': () => ({ context: vertexsMap.get(operation.operationBeforeImage.logicId), undoAction: 'Logic.addItem', undoActionParam: 'Before', redoAction: 'Logic.removeItem', redoActionParam: 'Before', }), 'LogicItem.update': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'LogicItem.update', undoActionParam: 'Before', redoAction: 'LogicItem.update', redoActionParam: 'After', }), 'LogicItem.move': () => ({ context: vertexsMap.get(operation.operationBeforeImage.id), undoAction: 'LogicItem.move', undoActionParam: 'Before', redoAction: 'LogicItem.move', redoActionParam: 'After', }), 'LogicItem.paste': () => ({ context: LogicItem, undoAction: 'View.undoPaste', undoActionParam: 'After', redoAction: 'View.redoPaste', redoActionParam: 'After', }), 'View.mergeBlock': () => ({ context: vertexsMap.get(operation.operationAfterImage.id), undoAction: 'View.undoMergeBlock', undoActionParam: 'After', redoAction: 'View.redoMergeBlock', redoActionParam: 'After', }), }; return map[operation.operationAction](); } export class History { app: App = undefined; undoCount: number = 0; redoCount: number = 0; undoStack: Array = []; redoStack: Array = []; constructor(source: any) { Object.assign(this, source); } /** * 加载 undoRedo 栈 * @param operation 失败时用于前端模拟 */ async load(operation?: Operation) { try { const result = await undoRedoService.undoRedoInfo({ headers: { appId: this.app.id, }, }); this.undoCount = result.undoNumber; this.redoCount = result.redoNumber; } catch (e) { } } async undo() { this.app.emit('saving'); let operation: Operation; try { const result = await undoRedoService.undo({ headers: { appId: this.app.id, }, }); operation = result.operation; } catch (e) { return; } const oh = getOperationHandler(operation); let image = (operation as any)[`operation${oh.undoActionParam}Image`]; if (operation.operationAction === 'LogicItem.move') { // 移动 QueryJoinExpression 会同时修改 onExpressionList if (image.type === 'QueryJoinExpression') { const context = oh.context as LogicItem; context.assign(LogicItem.from(image, context.logic, context.parent)); } image = image.moveToLogicItem; } operation.activeImage = image; if (operation.operationAction === 'LogicItem.paste') operation.activeImage = operation.operationAfterImage.logicItems[0]; await (oh.context as any)[oh.undoAction.split('.').pop()](image, { actionMode: ACTION_MODE.undoRedo, }); return operation; } async redo() { this.app.emit('saving'); let operation: Operation; try { const result = await undoRedoService.redo({ headers: { appId: this.app.id, }, }); operation = result.operation; } catch (e) { return; } const oh = getOperationHandler(operation); let image = (operation as any)[`operation${oh.redoActionParam}Image`]; if (operation.operationAction === 'LogicItem.move') { // 移动 QueryJoinExpression 会同时修改 onExpressionList const { operationBeforeImage } = operation; if (operationBeforeImage.type === 'QueryJoinExpression') { const context = oh.context; context.onExpressionList = [LogicItem.from(operationBeforeImage.onExpressionList[0], context.logic, context)]; context.onExpressionList[0].left.propertyRef = ''; context.onExpressionList[0].right.propertyRef = ''; } image = image.moveToLogicItem; } operation.activeImage = image; if (operation.operationAction === 'LogicItem.paste') operation.activeImage = operation.operationAfterImage.logicItems[0]; await (oh.context as any)[oh.redoAction.split('.').pop()](image, { actionMode: ACTION_MODE.undoRedo, }); return operation; } clear() { this.undoStack = []; this.redoStack = []; this.undoCount = 0; this.redoCount = 0; } } export default History;