From d218c0998b333c9bf6e905e999ce8b0bf02a72f7 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 20 Feb 2019 20:18:21 -0500 Subject: Added undo/redo --- src/client/util/UndoManager.ts | 121 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/client/util/UndoManager.ts (limited to 'src/client/util/UndoManager.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts new file mode 100644 index 000000000..34910bac3 --- /dev/null +++ b/src/client/util/UndoManager.ts @@ -0,0 +1,121 @@ +import { observable, action } from "mobx"; +import { Opt } from "../../fields/Field"; + +export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any { + let fn: (...args: any[]) => any; + let patchedFn: Opt<(...args: any[]) => any>; + + if (descriptor) { + fn = descriptor.value; + } + + return { + configurable: true, + enumerable: false, + get() { + if (!patchedFn) { + patchedFn = (...args: any[]) => { + try { + UndoManager.StartBatch() + return fn.call(this, ...args) + } finally { + UndoManager.EndBatch() + } + }; + } + return patchedFn; + }, + set(newFn: any) { + patchedFn = undefined; + fn = newFn; + } + } +} +export namespace UndoManager { + export interface UndoEvent { + undo: () => void; + redo: () => void; + } + type UndoBatch = UndoEvent[]; + + let undoStack: UndoBatch[] = observable([]); + let redoStack: UndoBatch[] = observable([]); + let currentBatch: UndoBatch | undefined; + let batchCounter = 0; + let undoing = false; + + export function AddEvent(event: UndoEvent): void { + if (currentBatch && batchCounter && !undoing) { + currentBatch.push(event); + } + } + + export function CanUndo(): boolean { + return undoStack.length > 0; + } + + export function CanRedo(): boolean { + return redoStack.length > 0; + } + + export function StartBatch(): void { + batchCounter++; + if (batchCounter > 0) { + currentBatch = []; + } + } + + export const EndBatch = action(() => { + batchCounter--; + if (batchCounter === 0 && currentBatch && currentBatch.length) { + undoStack.push(currentBatch); + redoStack.length = 0; + currentBatch = undefined; + } + }) + + export function RunInBatch(fn: () => void) { + StartBatch(); + fn(); + EndBatch(); + } + + export const Undo = action(() => { + if (undoStack.length === 0) { + return; + } + + let commands = undoStack.pop(); + if (!commands) { + return; + } + + undoing = true; + for (let i = commands.length - 1; i >= 0; i--) { + commands[i].undo(); + } + undoing = false; + + redoStack.push(commands); + }) + + export const Redo = action(() => { + if (redoStack.length === 0) { + return; + } + + let commands = redoStack.pop(); + if (!commands) { + return; + } + + undoing = true; + for (let i = 0; i < commands.length; i++) { + commands[i].redo(); + } + undoing = false; + + undoStack.push(commands); + }) + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 7339b4b1f8e9f92160b753dfd3d81faf2887c02f Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 20 Feb 2019 22:02:56 -0500 Subject: Kind of fixed undo decorator, more work needed to get it to work with arrow functions --- src/client/util/UndoManager.ts | 57 +++++++++++++--------- .../views/collections/CollectionFreeFormView.tsx | 12 +++-- 2 files changed, 44 insertions(+), 25 deletions(-) (limited to 'src/client/util/UndoManager.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 34910bac3..8e9e11a11 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,36 +1,49 @@ import { observable, action } from "mobx"; import { Opt } from "../../fields/Field"; -export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any { - let fn: (...args: any[]) => any; - let patchedFn: Opt<(...args: any[]) => any>; - - if (descriptor) { - fn = descriptor.value; - } - - return { +function propertyDecorator(target: any, key: string | symbol) { + Object.defineProperty(target, key, { configurable: true, enumerable: false, - get() { - if (!patchedFn) { - patchedFn = (...args: any[]) => { + get: function () { + return 5; + }, + set: function (value: any) { + Object.defineProperty(this, key, { + enumerable: false, + writable: true, + configurable: true, + value: function (...args: any[]) { try { - UndoManager.StartBatch() - return fn.call(this, ...args) + UndoManager.StartBatch(); + return value.apply(this, args); } finally { - UndoManager.EndBatch() + UndoManager.EndBatch(); } - }; - } - return patchedFn; - }, - set(newFn: any) { - patchedFn = undefined; - fn = newFn; + } + }) + } + }) +} +export function undoBatch(target: any, key: string | symbol, descriptor: TypedPropertyDescriptor): any { + if (!descriptor) { + propertyDecorator(target, key); + return; + } + const oldFunction = descriptor.value; + + descriptor.value = function (...args: any[]) { + try { + UndoManager.StartBatch() + return oldFunction.apply(this, args) + } finally { + UndoManager.EndBatch() } } + + return descriptor; } + export namespace UndoManager { export interface UndoEvent { undo: () => void; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 3a66ebb75..07e9c0899 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -4,7 +4,7 @@ import { action, computed } from "mobx"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DragManager } from "../../util/DragManager"; import "./CollectionFreeFormView.scss"; -import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; +import { CollectionViewBase, COLLECTION_BORDER_WIDTH, CollectionViewProps } from "./CollectionViewBase"; import { KeyStore } from "../../../fields/KeyStore"; import { Document } from "../../../fields/Document"; import { ListField } from "../../../fields/ListField"; @@ -38,9 +38,15 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get resizeScaling() { return this.isAnnotationOverlay ? this.props.Document.GetNumber(KeyStore.Width, 0) / this.nativeWidth : 1; } - @undoBatch + constructor(props: CollectionViewProps) { + super(props); + + this.drop = this.drop.bind(this); + } + @action - drop = (e: Event, de: DragManager.DropEvent) => { + @undoBatch + drop(e: Event, de: DragManager.DropEvent) { const doc: DocumentView = de.data["document"]; if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { doc.props.ContainingCollectionView.removeDocument(doc.props.Document); -- cgit v1.2.3-70-g09d2 From 8e56ee4c6f1f16402edd8abee9daa55678117466 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 21 Feb 2019 13:44:58 -0500 Subject: everything works. --- src/client/util/UndoManager.ts | 2 +- src/client/views/Main.tsx | 2 +- src/client/views/collections/CollectionDockingView.tsx | 2 ++ src/client/views/collections/CollectionFreeFormView.tsx | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/client/util/UndoManager.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 8e9e11a11..edb75b55f 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -25,7 +25,7 @@ function propertyDecorator(target: any, key: string | symbol) { } }) } -export function undoBatch(target: any, key: string | symbol, descriptor: TypedPropertyDescriptor): any { +export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any { if (!descriptor) { propertyDecorator(target, key); return; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index f35362fd4..c7a6a44e8 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -76,7 +76,7 @@ Documents.initProtos(() => { var docs = [mainfreeform].map(doc => CollectionDockingView.makeDocumentConfig(doc)); mainContainer.SetText(KeyStore.Data, JSON.stringify({ content: [{ type: 'row', content: docs }] })); mainContainer.Set(KeyStore.ActiveFrame, mainfreeform); - }, 25); + }, 0); } let addImageNode = action(() => { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e8a1eaf92..35b130a9a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -16,6 +16,7 @@ import { Server } from "../../Server"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { Opt } from "../../../fields/Field"; +import { undoBatch } from "../../util/UndoManager"; @observer export class CollectionDockingView extends CollectionViewBase { @@ -201,6 +202,7 @@ export class CollectionDockingView extends CollectionViewBase { } } + @undoBatch stateChanged = () => { var json = JSON.stringify(this._goldenLayout.toConfig()); this.props.Document.SetText(KeyStore.Data, json) diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 07e9c0899..986bcdcee 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -40,13 +40,11 @@ export class CollectionFreeFormView extends CollectionViewBase { constructor(props: CollectionViewProps) { super(props); - - this.drop = this.drop.bind(this); } - @action @undoBatch - drop(e: Event, de: DragManager.DropEvent) { + @action + drop = (e: Event, de: DragManager.DropEvent) => { const doc: DocumentView = de.data["document"]; if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { doc.props.ContainingCollectionView.removeDocument(doc.props.Document); -- cgit v1.2.3-70-g09d2 From 3f9e4363e6601eac175ff71192d414fd6051d921 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 21 Feb 2019 14:13:44 -0500 Subject: clean up --- src/client/util/UndoManager.ts | 1 - .../views/collections/CollectionDockingView.tsx | 21 ++++++++++----------- .../views/collections/CollectionSchemaView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 12 +++--------- src/fields/Document.ts | 5 ----- src/fields/ListField.ts | 9 ++++----- 6 files changed, 18 insertions(+), 32 deletions(-) (limited to 'src/client/util/UndoManager.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index edb75b55f..46ad558f3 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,5 +1,4 @@ import { observable, action } from "mobx"; -import { Opt } from "../../fields/Field"; function propertyDecorator(target: any, key: string | symbol) { Object.defineProperty(target, key, { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 35b130a9a..60dc24b5f 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,22 +1,21 @@ import * as GoldenLayout from "golden-layout"; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, computed, observable, reaction, trace, untracked } from "mobx"; -import { DragManager } from "../../util/DragManager"; -import { DocumentView } from "../nodes/DocumentView"; -import { Document } from "../../../fields/Document"; -import "./CollectionDockingView.scss"; -import { CollectionViewBase, COLLECTION_BORDER_WIDTH, CollectionViewProps } from "./CollectionViewBase"; -import React = require("react"); +import { action, computed, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; +import { Document } from "../../../fields/Document"; +import { FieldId, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; import { Utils } from "../../../Utils"; -import { FieldId } from "../../../fields/Field"; import { Server } from "../../Server"; -import { observer } from "mobx-react"; -import { KeyStore } from "../../../fields/KeyStore"; -import { Opt } from "../../../fields/Field"; +import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; +import { DocumentView } from "../nodes/DocumentView"; +import "./CollectionDockingView.scss"; +import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; +import React = require("react"); @observer export class CollectionDockingView extends CollectionViewBase { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index f3217d55d..5ec288b13 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1,5 +1,5 @@ import React = require("react") -import { action, computed, observable } from "mobx"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7cad6ffc1..5568935fa 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,17 +1,11 @@ -import { action, computed } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { NumberField } from "../../../fields/NumberField"; -import { DragManager } from "../../util/DragManager"; -import { SelectionManager } from "../../util/SelectionManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; -import { ContextMenu } from "../ContextMenu"; +import { Transform } from "../../util/Transform"; +import { DocumentView, DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { DocumentView, DocumentViewProps } from "./DocumentView"; -import { Utils } from "../../../Utils"; -import { Transform } from "../../util/Transform"; @observer diff --git a/src/fields/Document.ts b/src/fields/Document.ts index d8522fb5b..6667485b6 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -152,16 +152,13 @@ export class Document extends Field { SetData(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) { let field = this.Get(key, true); - //if (field != WAITING) { // do we want to wait for the field to come back from the server to set it, or do we overwrite? if (field instanceof ctor) { field.Data = value; - // Server.SetFieldValue(field, value); } else if (!field || replaceWrongType) { let newField = new ctor(); newField.Data = value; this.Set(key, newField); } - //} } @action @@ -213,14 +210,12 @@ export class Document extends Field { } ToJson(): { type: Types, data: [string, string][], _id: string } { - // console.log(this.fields) let fields: [string, string][] = [] this._proxies.forEach((field, key) => { if (field) { fields.push([key, field as string]) } }); - // console.log(fields) return { type: Types.Document, diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index ad5374dc9..75c2eb343 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -1,10 +1,9 @@ -import { Field, FieldId, FieldValue, Opt } from "./Field"; -import { BasicField } from "./BasicField"; -import { Types } from "../server/Message"; -import { observe, action, IArrayChange, IArraySplice, IObservableArray } from "mobx"; +import { action, IArrayChange, IArraySplice, IObservableArray, observe } from "mobx"; import { Server } from "../client/Server"; -import { ServerUtils } from "../server/ServerUtil"; import { UndoManager } from "../client/util/UndoManager"; +import { Types } from "../server/Message"; +import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; export class ListField extends BasicField { private _proxies: string[] = [] -- cgit v1.2.3-70-g09d2