From df7f46f6dda9963809aca17f23ed34fdc5ee6b87 Mon Sep 17 00:00:00 2001 From: bryzZz Date: Sun, 1 Oct 2023 17:22:34 +0800 Subject: [PATCH] add evens undoRedo --- src/renderer/src/components/DiagramEditor.tsx | 12 +- .../src/hooks/useDiagramContextMenu.ts | 5 +- src/renderer/src/lib/data/EditorManager.ts | 112 ++++++++++++------ src/renderer/src/lib/data/StateMachine.ts | 108 ++++++++++++++--- src/renderer/src/lib/data/UndoRedo.ts | 49 ++++++-- src/renderer/src/lib/drawable/States.ts | 13 +- src/renderer/src/types/EditorManager.ts | 2 +- 7 files changed, 221 insertions(+), 80 deletions(-) diff --git a/src/renderer/src/components/DiagramEditor.tsx b/src/renderer/src/components/DiagramEditor.tsx index 9160e6e40..d92cdb3e6 100644 --- a/src/renderer/src/components/DiagramEditor.tsx +++ b/src/renderer/src/components/DiagramEditor.tsx @@ -74,7 +74,7 @@ export const DiagramEditor: React.FC = ({ manager, editor, s // manager.triggerDataUpdate(); }); - editor.container.states.onStateEventCreate((state, event, click) => { + editor.container.states.onStateEventChange((state, event, click) => { ClearUseState(); setIdEvents({ state, event, click }); openEventsModal(); @@ -113,12 +113,8 @@ export const DiagramEditor: React.FC = ({ manager, editor, s const handleCreateEventsModal = (data: EventsModalResult) => { setEvents([...events, data.action]); - if (!isModalOpen) { - editor?.container.machine.createOrChangeEvent( - data.id?.state.id, - data.id?.event, - data.trigger - ); + if (!isModalOpen && data.id?.event) { + editor?.container.machine.changeEvent(data.id?.state.id, data.id.event, data.trigger); } closeEventsModal(); }; @@ -129,7 +125,7 @@ export const DiagramEditor: React.FC = ({ manager, editor, s id: data.id, triggerComponent: data.trigger.component, triggerMethod: data.trigger.method, - events, + actions: events, }); } else if (transition && data.key === 3) { editor?.container.machine.changeTransition({ diff --git a/src/renderer/src/hooks/useDiagramContextMenu.ts b/src/renderer/src/hooks/useDiagramContextMenu.ts index 83e2e435e..2d865830b 100644 --- a/src/renderer/src/hooks/useDiagramContextMenu.ts +++ b/src/renderer/src/hooks/useDiagramContextMenu.ts @@ -1,11 +1,10 @@ import { useEffect, useState } from 'react'; -import { useTabs } from '@renderer/store/useTabs'; - import { CanvasEditor } from '@renderer/lib/CanvasEditor'; import { EditorManager } from '@renderer/lib/data/EditorManager'; import { Condition } from '@renderer/lib/drawable/Condition'; import { State } from '@renderer/lib/drawable/State'; +import { useTabs } from '@renderer/store/useTabs'; import { Point } from '@renderer/types/graphics'; type DiagramContextMenuItem = { label: string; action: () => void }; @@ -133,7 +132,7 @@ export const useDiagramContextMenu = (editor: CanvasEditor | null, manager: Edit { label: 'Удалить', action: () => { - editor?.container.machine.deleteEvent(state.id as string, event); + editor?.container.machine.deleteEvent(state.id, event); }, }, ]); diff --git a/src/renderer/src/lib/data/EditorManager.ts b/src/renderer/src/lib/data/EditorManager.ts index 304f59fd9..13c083ca5 100644 --- a/src/renderer/src/lib/data/EditorManager.ts +++ b/src/renderer/src/lib/data/EditorManager.ts @@ -12,6 +12,7 @@ import { Transition, Component, Elements, + EventData, } from '@renderer/types/diagram'; import { emptyEditorData, @@ -32,6 +33,7 @@ import { Point, Rectangle } from '@renderer/types/graphics'; import { isPlatformAvailable } from './PlatformLoader'; import ElementsJSONCodec from '../codecs/ElementsJSONCodec'; +import { EventSelection } from '../drawable/Events'; import { stateStyle } from '../styles'; export type FileError = { @@ -343,31 +345,36 @@ export class EditorManager { return newId; } - changeStateEvents({ id, triggerComponent, triggerMethod, events }: ChangeStateEventsParams) { + changeStateEvents({ id, triggerComponent, triggerMethod, actions }: ChangeStateEventsParams) { const state = this.data.elements.states[id]; if (!state) return false; - const trueTab = state.events.find( + const eventIndex = state.events.findIndex( (value) => triggerComponent === value.trigger.component && triggerMethod === value.trigger.method && undefined === value.trigger.args // FIXME: сравнение по args может не работать ); + const event = state.events[eventIndex]; - if (trueTab === undefined) { + if (event === undefined) { state.events = [ ...state.events, { - do: events, + do: actions, trigger: { component: triggerComponent, method: triggerMethod, - //args: {}, + // args: {}, }, }, ]; } else { - trueTab.do = [...events]; + if (actions.length) { + event.do = [...actions]; + } else { + state.events.splice(eventIndex, 1); + } } return true; @@ -447,46 +454,81 @@ export class EditorManager { return true; } - changeEvent(stateId: string, event: any, newValue: Event | Action) { + createEvent(stateId: string, eventData: EventData) { const state = this.data.elements.states[stateId]; if (!state) return false; - //Проверяем по условию, что мы редактируем, либо главное событие, либо действие - if (event.actionIdx === null) { - const trueTab = state.events.find( - (value, id) => - event.eventIdx !== id && - newValue.component === value.trigger.component && - newValue.method === value.trigger.method && - undefined === value.trigger.args // FIXME: сравнение по args может не работать - ); + state.events.push(eventData); - if (trueTab === undefined) { - state.events[event.eventIdx].trigger = newValue; - } else { - trueTab.do = [...trueTab.do, ...state.events[event.eventIdx].do]; - state.events.splice(event.eventIdx, 1); - } - } else { - state.events[event.eventIdx].do[event.actionIdx] = newValue; - } + return true; + } + + createEventAction(stateId: string, event: EventSelection, value: Action) { + const state = this.data.elements.states[stateId]; + if (!state) return false; + + const { eventIdx, actionIdx } = event; + + state.events[eventIdx].do.splice(actionIdx ?? state.events[eventIdx].do.length - 1, 0, value); return true; } - deleteEvent(stateId: string, eventIdx: number, actionIdx: number | null) { + changeEvent(stateId: string, eventIdx: number, newValue: Event) { const state = this.data.elements.states[stateId]; if (!state) return false; - if (actionIdx !== null) { - state.events[eventIdx].do.splice(actionIdx!, 1); - // Проверяем, есть ли действия в событие, если нет, то удалять его - if (state.events[eventIdx].do.length === 0) { - state.events.splice(eventIdx, 1); - } - } else { - state.events.splice(eventIdx, 1); - } + // const event = state.events.find( + // (value, id) => + // eventIdx !== id && + // newValue.component === value.trigger.component && + // newValue.method === value.trigger.method && + // undefined === value.trigger.args // FIXME: сравнение по args может не работать + // ); + + const event = state.events[eventIdx]; + + if (!event) return false; + + event.trigger = newValue; + + // if (trueTab === undefined) { + // state.events[eventIdx].trigger = newValue; + // } else { + // event.do = [...event.do, ...state.events[eventIdx].do]; + // state.events.splice(eventIdx, 1); + // } + + return true; + } + + changeEventAction(stateId: string, event: EventSelection, newValue: Action) { + const state = this.data.elements.states[stateId]; + if (!state) return false; + + const { eventIdx, actionIdx } = event; + + state.events[eventIdx].do[actionIdx as number] = newValue; + + return true; + } + + deleteEvent(stateId: string, eventIdx: number) { + const state = this.data.elements.states[stateId]; + if (!state) return false; + + state.events.splice(eventIdx, 1); + + return true; + } + + deleteEventAction(stateId: string, event: EventSelection) { + const state = this.data.elements.states[stateId]; + if (!state) return false; + + const { eventIdx, actionIdx } = event; + + state.events[eventIdx].do.splice(actionIdx as number, 1); return true; } diff --git a/src/renderer/src/lib/data/StateMachine.ts b/src/renderer/src/lib/data/StateMachine.ts index 1545f18b5..618494491 100644 --- a/src/renderer/src/lib/data/StateMachine.ts +++ b/src/renderer/src/lib/data/StateMachine.ts @@ -6,6 +6,7 @@ import { Variable, State as StateType, Transition as TransitionType, + EventData, } from '@renderer/types/diagram'; import { AddComponentParams, @@ -173,9 +174,18 @@ export class StateMachine extends EventEmitter { if (!state) return; if (canUndo) { + const prevEvent = state.data.events.find( + (value) => + args.triggerComponent === value.trigger.component && + args.triggerMethod === value.trigger.method && + undefined === value.trigger.args // FIXME: сравнение по args может не работать + ); + + const prevActions = structuredClone(prevEvent?.do ?? []); + this.undoRedo.do({ type: 'changeStateEvents', - args: { state, args }, + args: { args, prevActions }, }); } @@ -186,7 +196,7 @@ export class StateMachine extends EventEmitter { this.container.isDirty = true; } - setStateEvents(args: SetStateEventsParams) { + setStateEvents(args: SetStateEventsParams, canUndo = true) { const { id } = args; const state = this.states.get(id); @@ -490,12 +500,12 @@ export class StateMachine extends EventEmitter { this.states.forEach((state) => { if (state.isSelected) { if (state.eventBox.selection) { - this.deleteEvent(state.id!, state.eventBox.selection); + this.deleteEvent(state.id, state.eventBox.selection); state.eventBox.selection = undefined; removed = true; return; } else { - killList.push(state.id!); + killList.push(state.id); } } }); @@ -570,19 +580,58 @@ export class StateMachine extends EventEmitter { this.container.isDirty = true; } + createEvent(stateId: string, eventData: EventData, canUndo = true) { + const state = this.states.get(stateId); + if (!state) return; + + this.container.app.manager.createEvent(stateId, eventData); + + state.eventBox.recalculate(); + + this.container.isDirty = true; + } + + createEventAction(stateId: string, event: EventSelection, value: Action, canUndo = true) { + const state = this.states.get(stateId); + if (!state) return; + + this.container.app.manager.createEventAction(stateId, event, value); + + state.eventBox.recalculate(); + + this.container.isDirty = true; + } + // Редактирование события в состояниях - createOrChangeEvent(stateId: string, event: any, newValue: Event | Action, canUndo = true) { + changeEvent(stateId: string, event: EventSelection, newValue: Event | Action, canUndo = true) { const state = this.states.get(stateId); if (!state) return; - if (canUndo) { - this.undoRedo.do({ - type: 'changeEvent', - args: { stateId, event, newValue }, - }); - } + const { eventIdx, actionIdx } = event; + + if (actionIdx !== null) { + const prevValue = state.data.events[eventIdx].do[actionIdx]; - this.container.app.manager.changeEvent(stateId, event, newValue); + this.container.app.manager.changeEventAction(stateId, event, newValue); + + if (canUndo) { + this.undoRedo.do({ + type: 'changeEvent', + args: { stateId, event, newValue, prevValue }, + }); + } + } else { + const prevValue = state.data.events[eventIdx].trigger; + + this.container.app.manager.changeEvent(stateId, eventIdx, newValue); + + if (canUndo) { + this.undoRedo.do({ + type: 'changeEvent', + args: { stateId, event, newValue, prevValue }, + }); + } + } state.eventBox.recalculate(); @@ -591,11 +640,40 @@ export class StateMachine extends EventEmitter { // Удаление события в состояниях //TODO показывать предупреждение при удалении события в состоянии(модалка) - deleteEvent(id: string, eventId: EventSelection) { - const state = this.states.get(id); + deleteEvent(stateId: string, event: EventSelection, canUndo = true) { + const state = this.states.get(stateId); if (!state) return; - this.container.app.manager.deleteEvent(id, eventId.eventIdx, eventId.actionIdx); + const { eventIdx, actionIdx } = event; + + if (actionIdx !== null) { + // Проверяем если действие в событие последнее то надо удалить всё событие + if (state.data.events[eventIdx].do.length === 1) { + return this.deleteEvent(stateId, { eventIdx, actionIdx: null }); + } + + const prevValue = state.data.events[eventIdx].do[actionIdx]; + + this.container.app.manager.deleteEventAction(stateId, event); + + if (canUndo) { + this.undoRedo.do({ + type: 'deleteEventAction', + args: { stateId, event, prevValue }, + }); + } + } else { + const prevValue = state.data.events[eventIdx]; + + this.container.app.manager.deleteEvent(stateId, eventIdx); + + if (canUndo) { + this.undoRedo.do({ + type: 'deleteEvent', + args: { stateId, eventIdx, prevValue }, + }); + } + } this.container.isDirty = true; } diff --git a/src/renderer/src/lib/data/UndoRedo.ts b/src/renderer/src/lib/data/UndoRedo.ts index 95cc7fc23..6792bc2d6 100644 --- a/src/renderer/src/lib/data/UndoRedo.ts +++ b/src/renderer/src/lib/data/UndoRedo.ts @@ -3,6 +3,7 @@ import { Event, Component, Transition as TransitionData, + EventData, } from '@renderer/types/diagram'; import { AddComponentParams, @@ -19,6 +20,7 @@ import { import { StateMachine } from './StateMachine'; +import { EventSelection } from '../drawable/Events'; import { State } from '../drawable/State'; import { Transition } from '../drawable/Transition'; @@ -26,7 +28,7 @@ type PossibleActions = { stateCreate: CreateStateParameters & { newStateId: string }; deleteState: { id: string; state: State }; changeStateName: { id: string; name: string; state: State }; - changeStateEvents: { state: State; args: ChangeStateEventsParams }; + changeStateEvents: { args: ChangeStateEventsParams; prevActions: EventAction[] }; linkState: { parentId: string; childId: string }; unlinkState: { parentId: string; childId: string }; createTransition: { id: string; params: CreateTransitionParameters }; @@ -39,7 +41,15 @@ type PossibleActions = { changeInitialState: { id: string; prevInitial: string }; changeStatePosition: { id: string; startPosition: Point; endPosition: Point }; changeTransitionPosition: { id: string; startPosition: Point; endPosition: Point }; - changeEvent: { stateId: string; event: any; newValue: Event | EventAction }; + changeEvent: { stateId: string; event: EventSelection; newValue: Event; prevValue: Event }; + changeEventAction: { + stateId: string; + event: EventSelection; + newValue: EventAction; + prevValue: EventAction; + }; + deleteEvent: { stateId: string; eventIdx: number; prevValue: EventData }; + deleteEventAction: { stateId: string; event: EventSelection; prevValue: EventAction }; addComponent: { args: AddComponentParams }; removeComponent: { args: RemoveComponentParams; prevComponent: Component }; editComponent: { args: EditComponentParams; prevComponent: Component }; @@ -84,12 +94,16 @@ export const actionFunctions: ActionFunctions = { redo: sM.changeStateName.bind(sM, id, name, false), undo: sM.changeStateName.bind(sM, id, state.data.name, false), }), - changeStateEvents: (sM, { state, args }) => ({ + changeStateEvents: (sM, { args, prevActions }) => ({ redo: sM.changeStateEvents.bind(sM, args, false), - undo: sM.setStateEvents.bind(sM, { - id: state.id, - events: state.data.events, - }), + undo: sM.changeStateEvents.bind( + sM, + { + ...args, + actions: prevActions, + }, + false + ), }), linkState: (sM, { parentId, childId }) => ({ redo: sM.linkState.bind(sM, parentId, childId, false), @@ -145,9 +159,21 @@ export const actionFunctions: ActionFunctions = { redo: sM.changeTransitionPosition.bind(sM, id, startPosition, endPosition, false), undo: sM.changeTransitionPosition.bind(sM, id, endPosition, startPosition, false), }), - changeEvent: (sM, { stateId, event, newValue }) => ({ - redo: sM.createOrChangeEvent.bind(sM, stateId, event, newValue, false), - undo: sM.createOrChangeEvent.bind(sM, stateId, newValue, event, false), + changeEvent: (sM, { stateId, event, newValue, prevValue }) => ({ + redo: sM.changeEvent.bind(sM, stateId, event, newValue, false), + undo: sM.changeEvent.bind(sM, stateId, event, prevValue, false), + }), + changeEventAction: (sM, { stateId, event, newValue, prevValue }) => ({ + redo: sM.changeEvent.bind(sM, stateId, event, newValue, false), + undo: sM.changeEvent.bind(sM, stateId, event, prevValue, false), + }), + deleteEvent: (sM, { stateId, eventIdx, prevValue }) => ({ + redo: sM.deleteEvent.bind(sM, stateId, { eventIdx, actionIdx: null }, false), + undo: sM.createEvent.bind(sM, stateId, prevValue, false), + }), + deleteEventAction: (sM, { stateId, event, prevValue }) => ({ + redo: sM.deleteEvent.bind(sM, stateId, event, false), + undo: sM.createEventAction.bind(sM, stateId, event, prevValue, false), }), addComponent: (sM, { args }) => ({ redo: sM.addComponent.bind(sM, args, false), @@ -181,8 +207,7 @@ export class UndoRedo { do(action: Action) { this.redoStack.length = 0; - this.redoStack.push(action); - this.redo(); + this.undoStack.push(action); } undo = () => { diff --git a/src/renderer/src/lib/drawable/States.ts b/src/renderer/src/lib/drawable/States.ts index 030712274..2436c8f4e 100644 --- a/src/renderer/src/lib/drawable/States.ts +++ b/src/renderer/src/lib/drawable/States.ts @@ -1,10 +1,11 @@ +import { Point } from '@renderer/types/graphics'; + +import { EventSelection } from './Events'; import { State } from './State'; import { Container } from '../basic/Container'; import { EventEmitter } from '../common/EventEmitter'; import { MyMouseEvent } from '../common/MouseEventEmitter'; -import { Point } from '@renderer/types/graphics'; -import { EventSelection } from './Events'; type CreateNameCallback = (state: State) => void; type CreateCallback = (state: State) => void; @@ -27,7 +28,7 @@ export class States extends EventEmitter { createCallback!: CreateCallback; createNameCallback!: CreateNameCallback; - createEventCallback!: CreateEventCallback; + changeEventCallback!: CreateEventCallback; menuEventCallback!: MenuEventCallback; menuCallback!: MenuCallback; @@ -39,8 +40,8 @@ export class States extends EventEmitter { this.createNameCallback = nameCallback; }; - onStateEventCreate = (eventCallback: CreateEventCallback) => { - this.createEventCallback = eventCallback; + onStateEventChange = (eventCallback: CreateEventCallback) => { + this.changeEventCallback = eventCallback; }; onStateContextMenu = (menuCallback: MenuCallback) => { @@ -96,7 +97,7 @@ export class States extends EventEmitter { if (!eventIdx) { this.createCallback?.(e.target); } else { - this.createEventCallback?.(e.target, eventIdx, true); + this.changeEventCallback?.(e.target, eventIdx, true); } } }; diff --git a/src/renderer/src/types/EditorManager.ts b/src/renderer/src/types/EditorManager.ts index eb49b7c08..35bfc3f5c 100644 --- a/src/renderer/src/types/EditorManager.ts +++ b/src/renderer/src/types/EditorManager.ts @@ -60,7 +60,7 @@ export interface ChangeTransitionParameters { export interface ChangeStateEventsParams { id: string; - events: Action[]; + actions: Action[]; triggerComponent: string; triggerMethod: string; }