Fix issues related to history (#701)

* Separate UI from Canvas

* Explicitly define history recording

* ActionManager: Set syncActionState during construction instead of in every call

* Add commit to history flag to necessary actions

* Disable undoing during multiElement

* Write custom equality function for UI component to render it only when specific props and elements change

* Remove stale comments about history skipping

* Stop undo/redoing when in resizing element mode

* wip

* correctly reset resizingElement & add undo check

* Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection

* Remove selectionElement from local storage

* Remove unnecessary readonly type casting in actionFinalize

* Fix undo / redo for multi points

* Fix an issue that did not update history when elements were locked

* Disable committing to history for noops

- deleteSelected without deleting anything
- Basic selection

* Use generateEntry only inside history and pass elements and appstate to history

* Update component after every history resume

* Remove last item from the history only if in multi mode

* Resume recording when element type is not selection

* ensure we prevent hotkeys only on writable elements

* Remove selection clearing from history

* Remove one point arrows as they are invisibly small

* Remove shape of elements from local storage

* Fix removing invisible element from the array

* add missing history resuming cases & simplify slice

* fix lint

* don't regenerate elements if no elements deselected

* regenerate elements array on selection

* reset state.selectionElement unconditionally

* Use getter instead of passing appState and scene data through functions to actions

* fix import

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Gasim Gasimzada 2020-02-05 22:47:10 +04:00 committed by GitHub
parent 972d69da6c
commit 33016bf6bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 450 additions and 413 deletions

View file

@ -12,29 +12,37 @@ import { t } from "../i18n";
export class ActionManager implements ActionsManagerInterface {
actions: { [keyProp: string]: Action } = {};
updater:
| ((elements: ExcalidrawElement[], appState: AppState) => void)
| null = null;
updater: UpdaterFn;
setUpdater(
updater: (elements: ExcalidrawElement[], appState: AppState) => void,
resumeHistoryRecording: () => void;
getAppState: () => AppState;
getElements: () => readonly ExcalidrawElement[];
constructor(
updater: UpdaterFn,
resumeHistoryRecording: () => void,
getAppState: () => AppState,
getElements: () => readonly ExcalidrawElement[],
) {
this.updater = updater;
this.resumeHistoryRecording = resumeHistoryRecording;
this.getAppState = getAppState;
this.getElements = getElements;
}
registerAction(action: Action) {
this.actions[action.name] = action;
}
handleKeyDown(
event: KeyboardEvent,
elements: readonly ExcalidrawElement[],
appState: AppState,
) {
handleKeyDown(event: KeyboardEvent) {
const data = Object.values(this.actions)
.sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
.filter(
action => action.keyTest && action.keyTest(event, appState, elements),
action =>
action.keyTest &&
action.keyTest(event, this.getAppState(), this.getElements()),
);
if (data.length === 0) {
@ -42,15 +50,16 @@ export class ActionManager implements ActionsManagerInterface {
}
event.preventDefault();
return data[0].perform(elements, appState, null);
if (
data[0].commitToHistory &&
data[0].commitToHistory(this.getAppState(), this.getElements())
) {
this.resumeHistoryRecording();
}
return data[0].perform(this.getElements(), this.getAppState(), null);
}
getContextMenuItems(
elements: readonly ExcalidrawElement[],
appState: AppState,
updater: UpdaterFn,
actionFilter: ActionFilterFn = action => action,
) {
getContextMenuItems(actionFilter: ActionFilterFn = action => action) {
return Object.values(this.actions)
.filter(actionFilter)
.filter(action => "contextItemLabel" in action)
@ -62,28 +71,40 @@ export class ActionManager implements ActionsManagerInterface {
.map(action => ({
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
action: () => {
updater(action.perform(elements, appState, null));
if (
action.commitToHistory &&
action.commitToHistory(this.getAppState(), this.getElements())
) {
this.resumeHistoryRecording();
}
this.updater(
action.perform(this.getElements(), this.getAppState(), null),
);
},
}));
}
renderAction(
name: string,
elements: readonly ExcalidrawElement[],
appState: AppState,
updater: UpdaterFn,
) {
renderAction(name: string) {
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
const action = this.actions[name];
const PanelComponent = action.PanelComponent!;
const updateData = (formState: any) => {
updater(action.perform(elements, appState, formState));
if (
action.commitToHistory &&
action.commitToHistory(this.getAppState(), this.getElements()) ===
true
) {
this.resumeHistoryRecording();
}
this.updater(
action.perform(this.getElements(), this.getAppState(), formState),
);
};
return (
<PanelComponent
elements={elements}
appState={appState}
elements={this.getElements()}
appState={this.getAppState()}
updateData={updateData}
/>
);