diff options
Diffstat (limited to 'src/fields')
-rw-r--r-- | src/fields/BasicField.ts | 29 | ||||
-rw-r--r-- | src/fields/Document.ts | 129 | ||||
-rw-r--r-- | src/fields/DocumentReference.ts | 15 | ||||
-rw-r--r-- | src/fields/Field.ts | 11 | ||||
-rw-r--r-- | src/fields/HtmlField.ts | 25 | ||||
-rw-r--r-- | src/fields/ImageField.ts | 15 | ||||
-rw-r--r-- | src/fields/Key.ts | 46 | ||||
-rw-r--r-- | src/fields/KeyStore.ts | 29 | ||||
-rw-r--r-- | src/fields/ListField.ts | 93 | ||||
-rw-r--r-- | src/fields/NumberField.ts | 14 | ||||
-rw-r--r-- | src/fields/RichTextField.ts | 14 | ||||
-rw-r--r-- | src/fields/TextField.ts | 16 |
12 files changed, 367 insertions, 69 deletions
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts index fb5cc773e..a92c4a236 100644 --- a/src/fields/BasicField.ts +++ b/src/fields/BasicField.ts @@ -1,15 +1,26 @@ -import { Field } from "./Field" +import { Field, FieldId } from "./Field" import { observable, computed, action } from "mobx"; +import { Server } from "../client/Server"; +import { UndoManager } from "../client/util/UndoManager"; export abstract class BasicField<T> extends Field { - constructor(data: T) { - super(); + constructor(data: T, save: boolean, id?: FieldId) { + super(id); this.data = data; + if (save) { + Server.UpdateField(this) + } + } + + UpdateFromServer(data: any) { + if (this.data !== data) { + this.data = data; + } } @observable - private data: T; + protected data: T; @computed get Data(): T { @@ -20,6 +31,16 @@ export abstract class BasicField<T> extends Field { if (this.data === value) { return; } + let oldValue = this.data; + this.setData(value); + UndoManager.AddEvent({ + undo: () => this.Data = oldValue, + redo: () => this.Data = value + }) + Server.UpdateField(this); + } + + protected setData(value: T) { this.data = value; } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index c682d8e94..6193ea56c 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,14 +1,36 @@ -import { Field, Cast, Opt, FieldWaiting, FieldId, FieldValue } from "./Field" -import { Key, KeyStore } from "./Key" +import { Key } from "./Key" +import { KeyStore } from "./KeyStore"; +import { Field, Cast, FieldWaiting, FieldValue, FieldId } from "./Field" import { NumberField } from "./NumberField"; -import { ObservableMap, computed, action, observable } from "mobx"; +import { ObservableMap, computed, action } from "mobx"; import { TextField } from "./TextField"; import { ListField } from "./ListField"; import { Server } from "../client/Server"; +import { Types } from "../server/Message"; +import { UndoManager } from "../client/util/UndoManager"; export class Document extends Field { - public fields: ObservableMap<Key, Field> = new ObservableMap(); - public _proxies: ObservableMap<Key, FieldId> = new ObservableMap(); + public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap(); + public _proxies: ObservableMap<string, FieldId> = new ObservableMap(); + + constructor(id?: string, save: boolean = true) { + super(id) + + if (save) { + Server.UpdateField(this) + } + } + + UpdateFromServer(data: [string, string][]) { + for (const key in data) { + const element = data[key]; + this._proxies.set(element[0], element[1]); + } + } + + public Width = () => { return this.GetNumber(KeyStore.Width, 0) } + public Height = () => { return this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0) * this.GetNumber(KeyStore.Width, 0) : 0) } + public Scale = () => { return this.GetNumber(KeyStore.Scale, 1) } @computed public get Title() { @@ -18,33 +40,67 @@ export class Document extends Field { Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> { let field: FieldValue<Field>; if (ignoreProto) { - if (this.fields.has(key)) { - field = this.fields.get(key); - } else if (this._proxies.has(key)) { - field = Server.GetDocumentField(this, key); + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key); + /* + The field might have been instantly filled from the cache + Maybe we want to just switch back to returning the value + from Server.GetDocumentField if it's in the cache + */ + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else { + field = FieldWaiting; + } } } else { let doc: FieldValue<Document> = this; while (doc && doc != FieldWaiting && field != FieldWaiting) { - if (!doc.fields.has(key)) { - if (doc._proxies.has(key)) { - field = Server.GetDocumentField(doc, key); + let curField = doc.fields.get(key.Id); + let curProxy = doc._proxies.get(key.Id); + if (!curField || (curProxy && curField.field.Id !== curProxy)) { + if (curProxy) { + Server.GetDocumentField(doc, key); + /* + The field might have been instantly filled from the cache + Maybe we want to just switch back to returning the value + from Server.GetDocumentField if it's in the cache + */ + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else { + field = FieldWaiting; + } break; } - if ((doc.fields.has(KeyStore.Prototype) || doc._proxies.has(KeyStore.Prototype))) { + if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) { doc = doc.GetPrototype(); - } else + } else { break; + } } else { - field = doc.fields.get(key); + field = curField.field; break; } } + if (doc == FieldWaiting) + field = FieldWaiting; } return field; } + GetAsync(key: Key, callback: (field: Field) => void): boolean { + //This currently doesn't deal with prototypes + if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key, callback); + return true; + } + return false; + } + GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue<T> { var getfield = this.Get(key, ignoreProto); if (getfield != FieldWaiting) { @@ -83,29 +139,37 @@ export class Document extends Field { @action Set(key: Key, field: Field | undefined): void { + let old = this.fields.get(key.Id); + let oldField = old ? old.field : undefined; if (field) { - this.fields.set(key, field); - Server.AddDocumentField(this, key, field); + this.fields.set(key.Id, { key, field }); + this._proxies.set(key.Id, field.Id) + // Server.AddDocumentField(this, key, field); } else { - this.fields.delete(key); - Server.DeleteDocumentField(this, key); + this.fields.delete(key.Id); + this._proxies.delete(key.Id) + // Server.DeleteDocumentField(this, key); + } + if (oldField || field) { + UndoManager.AddEvent({ + undo: () => this.Set(key, oldField), + redo: () => this.Set(key, field) + }) } + Server.UpdateField(this); } @action SetData<T, U extends Field & { Data: T }>(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 @@ -132,8 +196,8 @@ export class Document extends Field { return protos; } - MakeDelegate(): Document { - let delegate = new Document(); + MakeDelegate(id?: string): Document { + let delegate = new Document(id); delegate.Set(KeyStore.Prototype, this); @@ -148,11 +212,26 @@ export class Document extends Field { throw new Error("Method not implemented."); } GetValue() { - throw new Error("Method not implemented."); + var title = (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + "(" + this.Id + ")"; + return title; + //throw new Error("Method not implemented."); } Copy(): Field { throw new Error("Method not implemented."); } + ToJson(): { type: Types, data: [string, string][], _id: string } { + let fields: [string, string][] = [] + this._proxies.forEach((field, key) => { + if (field) { + fields.push([key, field as string]) + } + }); + return { + type: Types.Document, + data: fields, + _id: this.Id + } + } }
\ No newline at end of file diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index 983b162a3..9d3c209b4 100644 --- a/src/fields/DocumentReference.ts +++ b/src/fields/DocumentReference.ts @@ -1,6 +1,8 @@ -import { Field, Opt, FieldValue } from "./Field"; +import { Field, Opt, FieldValue, FieldId } from "./Field"; import { Document } from "./Document"; import { Key } from "./Key"; +import { Types } from "../server/Message"; +import { ObjectID } from "bson"; export class DocumentReference extends Field { get Key(): Key { @@ -15,6 +17,10 @@ export class DocumentReference extends Field { super(); } + UpdateFromServer() { + + } + Dereference(): FieldValue<Field> { return this.document.Get(this.key); } @@ -41,4 +47,11 @@ export class DocumentReference extends Field { return ""; } + ToJson(): { type: Types, data: FieldId, _id: string } { + return { + type: Types.DocumentReference, + data: this.document.Id, + _id: this.Id + } + } }
\ No newline at end of file diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 4a3968699..d48509a47 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,5 +1,7 @@ import { Utils } from "../Utils"; +import { Types } from "../server/Message"; +import { computed } from "mobx"; export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> { if (field) { @@ -19,7 +21,13 @@ export type FieldValue<T> = Opt<T> | FIELD_WAITING; export abstract class Field { //FieldUpdated: TypedEvent<Opt<FieldUpdatedArgs>> = new TypedEvent<Opt<FieldUpdatedArgs>>(); + init(callback: (res: Field) => any) { + callback(this); + } + private id: FieldId; + + @computed get Id(): FieldId { return this.id; } @@ -47,6 +55,8 @@ export abstract class Field { return this.id === other.id; } + abstract UpdateFromServer(serverData: any): void; + abstract ToScriptString(): string; abstract TrySetValue(value: any): boolean; @@ -55,4 +65,5 @@ export abstract class Field { abstract Copy(): Field; + abstract ToJson(): { _id: string, type: Types, data: any } }
\ No newline at end of file diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts new file mode 100644 index 000000000..a07326095 --- /dev/null +++ b/src/fields/HtmlField.ts @@ -0,0 +1,25 @@ +import { BasicField } from "./BasicField"; +import { Types } from "../server/Message"; +import { FieldId } from "./Field"; + +export class HtmlField extends BasicField<string> { + constructor(data: string = "<html></html>", id?: FieldId, save: boolean = true) { + super(data, save, id); + } + + ToScriptString(): string { + return `new HtmlField("${this.Data}")`; + } + + Copy() { + return new HtmlField(this.Data); + } + + ToJson(): { _id: string; type: Types; data: any; } { + return { + type: Types.Html, + data: this.Data, + _id: this.Id, + } + } +}
\ No newline at end of file diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index d82260f54..b2226d55a 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -1,9 +1,11 @@ import { BasicField } from "./BasicField"; -import { Field } from "./Field"; +import { Field, FieldId } from "./Field"; +import { Types } from "../server/Message"; +import { ObjectID } from "bson"; export class ImageField extends BasicField<URL> { - constructor(data: URL | undefined = undefined) { - super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data); + constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) { + super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id); } toString(): string { @@ -18,4 +20,11 @@ export class ImageField extends BasicField<URL> { return new ImageField(this.Data); } + ToJson(): { type: Types, data: URL, _id: string } { + return { + type: Types.Image, + data: this.Data, + _id: this.Id + } + } }
\ No newline at end of file diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 8d92b89b6..c16a00878 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -1,6 +1,9 @@ -import { Field } from "./Field" +import { Field, FieldId } from "./Field" import { Utils } from "../Utils"; import { observable } from "mobx"; +import { Types } from "../server/Message"; +import { ObjectID } from "bson"; +import { Server } from "../client/Server"; export class Key extends Field { private name: string; @@ -9,10 +12,17 @@ export class Key extends Field { return this.name; } - constructor(name: string) { - super(Utils.GenerateDeterministicGuid(name)); + constructor(name: string, id?: string, save: boolean = true) { + super(id || Utils.GenerateDeterministicGuid(name)); this.name = name; + if (save) { + Server.UpdateField(this) + } + } + + UpdateFromServer(data: string) { + this.name = data; } TrySetValue(value: any): boolean { @@ -31,27 +41,11 @@ export class Key extends Field { return name; } -} - -export namespace KeyStore { - export const Prototype = new Key("Prototype"); - export const X = new Key("X"); - export const Y = new Key("Y"); - export const Title = new Key("Title"); - export const Author = new Key("Author"); - export const PanX = new Key("PanX"); - export const PanY = new Key("PanY"); - export const Scale = new Key("Scale"); - export const NativeWidth = new Key("NativeWidth"); - export const NativeHeight = new Key("NativeHeight"); - export const Width = new Key("Width"); - export const Height = new Key("Height"); - export const ZIndex = new Key("ZIndex"); - export const Data = new Key("Data"); - export const Annotations = new Key("Annotations"); - export const Layout = new Key("Layout"); - export const BackgroundLayout = new Key("BackgroundLayout"); - export const LayoutKeys = new Key("LayoutKeys"); - export const LayoutFields = new Key("LayoutFields"); - export const ColumnsKey = new Key("SchemaColumns"); + ToJson(): { type: Types, data: string, _id: string } { + return { + type: Types.Key, + data: this.name, + _id: this.Id + } + } }
\ No newline at end of file diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts new file mode 100644 index 000000000..a3b39735d --- /dev/null +++ b/src/fields/KeyStore.ts @@ -0,0 +1,29 @@ +import { Key } from "./Key"; + +export namespace KeyStore { + export const Prototype = new Key("Prototype"); + export const X = new Key("X"); + export const Y = new Key("Y"); + export const Title = new Key("Title"); + export const Author = new Key("Author"); + export const PanX = new Key("PanX"); + export const PanY = new Key("PanY"); + export const Scale = new Key("Scale"); + export const NativeWidth = new Key("NativeWidth"); + export const NativeHeight = new Key("NativeHeight"); + export const Width = new Key("Width"); + export const Height = new Key("Height"); + export const ZIndex = new Key("ZIndex"); + export const Data = new Key("Data"); + export const Annotations = new Key("Annotations"); + export const ViewType = new Key("ViewType"); + export const Layout = new Key("Layout"); + export const BackgroundLayout = new Key("BackgroundLayout"); + export const OverlayLayout = new Key("OverlayLayout"); + export const LayoutKeys = new Key("LayoutKeys"); + export const LayoutFields = new Key("LayoutFields"); + export const ColumnsKey = new Key("SchemaColumns"); + export const Caption = new Key("Caption"); + export const ActiveFrame = new Key("ActiveFrame"); + export const DocumentText = new Key("DocumentText"); +} diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index 8843338c1..700600804 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -1,9 +1,82 @@ -import { Field } from "./Field"; +import { action, IArrayChange, IArraySplice, IObservableArray, observe, observable, Lambda } from "mobx"; +import { Server } from "../client/Server"; +import { UndoManager } from "../client/util/UndoManager"; +import { Types } from "../server/Message"; import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; export class ListField<T extends Field> extends BasicField<T[]> { - constructor(data: T[] = []) { - super(data.slice()); + private _proxies: string[] = [] + constructor(data: T[] = [], id?: FieldId, save: boolean = true) { + super(data, save, id); + this.updateProxies(); + if (save) { + Server.UpdateField(this); + } + this.observeList(); + } + + private observeDisposer: Lambda | undefined; + private observeList(): void { + this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => { + this.updateProxies() + if (change.type == "splice") { + UndoManager.AddEvent({ + undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed), + redo: () => this.Data.splice(change.index, change.removedCount, ...change.added) + }) + } else { + UndoManager.AddEvent({ + undo: () => this.Data[change.index] = change.oldValue, + redo: () => this.Data[change.index] = change.newValue + }) + } + Server.UpdateField(this); + }); + } + + protected setData(value: T[]) { + if (this.observeDisposer) { + this.observeDisposer() + } + this.data = observable(value); + this.observeList(); + } + + private updateProxies() { + this._proxies = this.Data.map(field => field.Id); + } + + UpdateFromServer(fields: string[]) { + this._proxies = fields; + } + private arraysEqual(a: any[], b: any[]) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + // Please note that calling sort on an array will modify that array. + // you might want to clone your array first. + + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; + } + + init(callback: (field: Field) => any) { + Server.GetFields(this._proxies, action((fields: { [index: string]: Field }) => { + if (!this.arraysEqual(this._proxies, this.Data.map(field => field.Id))) { + this.data = this._proxies.map(id => fields[id] as T) + observe(this.Data, () => { + this.updateProxies() + Server.UpdateField(this); + }) + } + callback(this); + })) } ToScriptString(): string { @@ -13,4 +86,18 @@ export class ListField<T extends Field> extends BasicField<T[]> { Copy(): Field { return new ListField<T>(this.Data); } + + ToJson(): { type: Types, data: string[], _id: string } { + return { + type: Types.List, + data: this._proxies, + _id: this.Id + } + } + + static FromJson(id: string, ids: string[]): ListField<Field> { + let list = new ListField([], id, false); + list._proxies = ids; + return list + } }
\ No newline at end of file diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index 03926d696..47dfc74cb 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -1,8 +1,10 @@ import { BasicField } from "./BasicField" +import { Types } from "../server/Message"; +import { FieldId } from "./Field"; export class NumberField extends BasicField<number> { - constructor(data: number = 0) { - super(data); + constructor(data: number = 0, id?: FieldId, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { @@ -12,4 +14,12 @@ export class NumberField extends BasicField<number> { Copy() { return new NumberField(this.Data); } + + ToJson(): { _id: string, type: Types, data: number } { + return { + _id: this.Id, + type: Types.Number, + data: this.Data + } + } }
\ No newline at end of file diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 4a77c669c..5efb43314 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -1,8 +1,10 @@ import { BasicField } from "./BasicField"; +import { Types } from "../server/Message"; +import { FieldId } from "./Field"; export class RichTextField extends BasicField<string> { - constructor(data: string = "") { - super(data); + constructor(data: string = "", id?: FieldId, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { @@ -13,4 +15,12 @@ export class RichTextField extends BasicField<string> { return new RichTextField(this.Data); } + ToJson(): { type: Types, data: string, _id: string } { + return { + type: Types.RichText, + data: this.Data, + _id: this.Id + } + } + }
\ No newline at end of file diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index 11d2ed7cd..71d8ea310 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -1,8 +1,10 @@ import { BasicField } from "./BasicField" +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; export class TextField extends BasicField<string> { - constructor(data: string = "") { - super(data); + constructor(data: string = "", id?: FieldId, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { @@ -12,4 +14,12 @@ export class TextField extends BasicField<string> { Copy() { return new TextField(this.Data); } -} + + ToJson(): { type: Types, data: string, _id: string } { + return { + type: Types.Text, + data: this.Data, + _id: this.Id + } + } +}
\ No newline at end of file |